diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index d6c55893..08095de8 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -55,4 +55,4 @@ jobs: rustup update cd server ./bt install --dev - ./bt test + ./bt full diff --git a/builder/Cargo.lock b/builder/Cargo.lock index c449ca69..56ec3ae1 100644 --- a/builder/Cargo.lock +++ b/builder/Cargo.lock @@ -87,7 +87,6 @@ dependencies = [ "cmd_lib", "current_platform", "dunce", - "open", "path-slash", "regex", ] @@ -100,9 +99,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" -version = "4.6.2" +version = "4.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd059f9da4f5c36b3787f65d38ccaab1cc315f07b01f89abc8359ee6a8205011" +checksum = "d91e0c145792ef73a6ad36d27c75ac09f1832222a3c209689d90f534685ee5b7" dependencies = [ "clap_builder", "clap_derive", @@ -122,14 +121,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.1" +version = "4.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.3", ] [[package]] @@ -252,25 +251,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "is-docker" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" -dependencies = [ - "once_cell", -] - -[[package]] -name = "is-wsl" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" -dependencies = [ - "is-docker", - "once_cell", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -315,9 +295,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.186" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "log" @@ -353,28 +333,12 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - [[package]] name = "once_cell_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" -[[package]] -name = "open" -version = "5.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0b3d059e795d52b8a72fef45658620edd4d9c359b338564aa14391ffa511ed5" -dependencies = [ - "is-wsl", - "libc", -] - [[package]] name = "os_pipe" version = "1.2.3" @@ -498,7 +462,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.2", + "syn 3.0.3", ] [[package]] @@ -520,9 +484,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a207d6d6a2b7fc470b80443726053f18a2481b7e1eee970597051596567987a3" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" dependencies = [ "proc-macro2", "quote", @@ -546,7 +510,7 @@ checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" dependencies = [ "proc-macro2", "quote", - "syn 3.0.2", + "syn 3.0.3", ] [[package]] diff --git a/builder/Cargo.toml b/builder/Cargo.toml index 28de841c..df465ba0 100644 --- a/builder/Cargo.toml +++ b/builder/Cargo.toml @@ -29,6 +29,9 @@ clap = { version = "4", features = ["derive"] } cmd_lib = "2.0.0" current_platform = "0.2.0" dunce = "1.0.5" -open = "5" path-slash = "0.2.1" regex = "1.11.1" + +[lints.clippy] +pedantic = { level = "warn", priority = 0 } +doc_markdown = { level = "allow", priority = 1 } diff --git a/builder/src/main.rs b/builder/src/main.rs index 7f1a489d..b261ffd9 100644 --- a/builder/src/main.rs +++ b/builder/src/main.rs @@ -13,7 +13,7 @@ // You should have received a copy of the GNU General Public License along with // the CodeChat Editor. If not, see // [http://www.gnu.org/licenses](http://www.gnu.org/licenses). -/// `main.rs` -- Entrypoint for the CodeChat Editor Builder +/// `main.rs` -- Entrypoint for the `CodeChat` Editor Builder /// ======================================================= /// /// This code uses [dist](https://opensource.axo.dev/cargo-dist/book/) as a part @@ -80,7 +80,9 @@ enum Commands { #[arg(short, long, default_value_t = false)] check: bool, }, - /// Run lints and tests. + /// Run formatters and linters, build all files, build for release, then run tests. + Full, + /// Run all tests. Test, /// Repeatedly run the `overall_*` tests until one fails. Useful for /// shaking out intermittent test failures. @@ -131,6 +133,7 @@ struct TypeScriptBuildOptions { // Constants // --------- static VSCODE_PATH: &str = "../extensions/VSCode"; +static STANDALONE_PATH: &str = "../extensions/standalone"; static CLIENT_PATH: &str = "../client"; static BUILDER_PATH: &str = "../builder"; static TEST_UTILS_PATH: &str = "../test_utils"; @@ -163,7 +166,7 @@ fn run_script, A: AsRef, P: AsRef + std::fmt::Displa process.arg("/c").arg(script); } else { process = Command::new(script); - }; + } process.args(args).current_dir(&dir); // A bit crude, but displays the command being run. println!("{dir}: {process:#?}"); @@ -172,7 +175,10 @@ fn run_script, A: AsRef, P: AsRef + std::fmt::Displa if exit_code == Some(0) || (exit_code.is_some() && !check_exit_code) { Ok(()) } else { - Err(io::Error::other("pnpm exit code indicates failure.")) + Err(io::Error::other(format!( + "{}: exit code {exit_code:?} indicates failure.", + script.to_string_lossy() + ))) } } @@ -181,7 +187,7 @@ fn run_script, A: AsRef, P: AsRef + std::fmt::Displa /// programs (`robocopy`/`rsync`) to accomplish this. Very important: the `src` /// **must** end with a `/`, otherwise the Windows and Linux copies aren't /// identical. -fn quick_copy_dir>(src: P, dest: P, files: Option

) -> io::Result<()> { +fn quick_copy_dir>(src: P, dest: P, files: Option<&P>) -> io::Result<()> { assert!(src.as_ref().to_string_lossy().ends_with('/')); let mut copy_process; let src = OsStr::new(src.as_ref()); @@ -248,7 +254,7 @@ fn quick_copy_dir>(src: P, dest: P, files: Option

) -> io::Resu } // Print the command, in case this produces and error or takes a while. - println!("{:#?}", copy_process); + println!("{copy_process:#?}"); // Check for errors. let exit_code = copy_process @@ -355,7 +361,7 @@ fn patch_client_libs() -> io::Result<()> { quick_copy_dir( format!("{CLIENT_PATH}/node_modules/mathjax/"), format!("{CLIENT_PATH}/static/mathjax"), - Some("tex-chtml.js".to_string()), + Some(&"tex-chtml.js".to_string()), )?; quick_copy_dir( format!("{CLIENT_PATH}/node_modules/@mathjax/mathjax-newcm-font/chtml/"), @@ -380,6 +386,8 @@ fn run_install(dev: bool) -> io::Result<()> { cargo fetch --manifest-path=$BUILDER_PATH/Cargo.toml; info "VSCode extension: cargo fetch"; cargo fetch --manifest-path=$VSCODE_PATH/Cargo.toml; + info "Standalone: cargo fetch"; + cargo fetch --manifest-path=$STANDALONE_PATH/Cargo.toml; info "test_utils: cargo fetch"; cargo fetch --manifest-path=$TEST_UTILS_PATH/Cargo.toml; info "cargo fetch"; @@ -421,8 +429,14 @@ fn run_install(dev: bool) -> io::Result<()> { cargo binstall cargo-sort --no-confirm; info "cargo binstall cargo-audit"; cargo binstall cargo-audit --no-confirm; - info "cargo binstall cargo-tarpaulin"; - cargo binstall cargo-tarpaulin --no-confirm; + info "cargo binstall cargo-machete"; + cargo binstall cargo-machete --no-confirm; + info "cargo binstall cargo-llvm-cov"; + cargo binstall cargo-llvm-cov --no-confirm; + // Install the required llvm-tools component used by cargo-llvm-cov + // without prompting. + info "rustup component add llvm-tools-preview"; + rustup component add llvm-tools-preview; )?; } Ok(()) @@ -437,6 +451,8 @@ fn run_update() -> io::Result<()> { cargo update --manifest-path=$BUILDER_PATH/Cargo.toml; info "VSCode extension: cargo update"; cargo update --manifest-path=$VSCODE_PATH/Cargo.toml; + info "Standalone: cargo update"; + cargo update --manifest-path=$STANDALONE_PATH/Cargo.toml; info "test_utils: cargo update"; cargo update --manifest-path=$TEST_UTILS_PATH/Cargo.toml; info "cargo update"; @@ -450,6 +466,8 @@ fn run_update() -> io::Result<()> { cargo outdated --manifest-path=$BUILDER_PATH/Cargo.toml; info "VSCode extension: cargo outdated"; cargo outdated --manifest-path=$VSCODE_PATH/Cargo.toml; + info "Standalone: cargo outdated"; + cargo outdated --manifest-path=$STANDALONE_PATH/Cargo.toml; info "test_utils: cargo outdated"; cargo outdated --manifest-path=$TEST_UTILS_PATH/Cargo.toml; info "cargo outdated"; @@ -476,6 +494,9 @@ fn run_format_and_lint(check_only: bool) -> io::Result<()> { info "VSCode extension: cargo clippy and fmt"; cargo clippy --all-targets --all-features --manifest-path=$VSCODE_PATH/Cargo.toml -- $clippy_check_only; cargo fmt --all $check --manifest-path=$VSCODE_PATH/Cargo.toml; + info "Standalone: cargo clippy and fmt"; + cargo clippy --all-targets --all-features --manifest-path=$STANDALONE_PATH/Cargo.toml -- $clippy_check_only; + cargo fmt --all $check --manifest-path=$STANDALONE_PATH/Cargo.toml; info "test_utils: cargo clippy and fmt"; cargo clippy --all-targets --all-features --manifest-path=$TEST_UTILS_PATH/Cargo.toml -- $clippy_check_only; cargo fmt --all $check --manifest-path=$TEST_UTILS_PATH/Cargo.toml; @@ -486,6 +507,8 @@ fn run_format_and_lint(check_only: bool) -> io::Result<()> { cargo audit --file=$BUILDER_PATH/Cargo.lock --no-fetch; info "VSCode extension: cargo audit"; cargo audit --file=$VSCODE_PATH/Cargo.lock --no-fetch; + info "Standalone: cargo audit"; + cargo audit --file=$STANDALONE_PATH/Cargo.lock --no-fetch; info "test_utils: cargo audit"; cargo audit --file=$TEST_UTILS_PATH/Cargo.lock --no-fetch; @@ -497,14 +520,37 @@ fn run_format_and_lint(check_only: bool) -> io::Result<()> { cd $VSCODE_PATH; info "VSCode extension: cargo sort"; cargo sort $check; + cd ../standalone; + info "Standalone: cargo sort"; + cargo sort $check; info "test_utils: cargo sort"; cd ../$TEST_UTILS_PATH; cargo sort $check; )?; + // `cargo machete` recurses into subdirectories on its own, so a single + // invocation from the repo root covers every crate. It's run outside the + // block above and branched here (rather than interpolating a `--fix` + // variable that may be empty) because passing it an empty string + // argument makes it treat the empty string as an invalid path and error + // out. The `cargo-machete` binary is invoked directly (rather than via + // `cargo machete`) since going through cargo's subcommand dispatch from + // inside this already-running `cargo run` process causes cargo-machete to + // misparse its own arguments. + if check_only { + run_cmd!( + info "cargo machete"; + cargo-machete --with-metadata --skip-target-dir ..; + )?; + } else { + run_cmd!( + info "cargo machete"; + cargo-machete --with-metadata --skip-target-dir --fix ..; + )?; + } let mut eslint_args = vec!["eslint", "src"]; if !eslint_check.is_empty() { - eslint_args.push(eslint_check) + eslint_args.push(eslint_check); } run_script("npx", &eslint_args, CLIENT_PATH, true)?; run_script("npx", &eslint_args, VSCODE_PATH, true)?; @@ -512,7 +558,7 @@ fn run_format_and_lint(check_only: bool) -> io::Result<()> { run_script("pnpm", &["audit", "--prod"], VSCODE_PATH, false) } -fn run_test() -> io::Result<()> { +fn run_full() -> io::Result<()> { run_format_and_lint(true)?; run_build()?; // Verify that compiling for release produces no errors. @@ -521,11 +567,17 @@ fn run_test() -> io::Result<()> { info "dist build"; dist build; )?; + run_test() +} + +fn run_test() -> io::Result<()> { run_cmd!( info "Builder: cargo test"; cargo test --manifest-path=$BUILDER_PATH/Cargo.toml; info "VSCode extension: cargo test"; cargo test --manifest-path=$VSCODE_PATH/Cargo.toml; + info "Standalone: cargo test"; + cargo test --manifest-path=$STANDALONE_PATH/Cargo.toml; info "test_utils: cargo test"; cargo test --manifest-path=$TEST_UTILS_PATH/Cargo.toml; info "cargo test"; @@ -534,36 +586,25 @@ fn run_test() -> io::Result<()> { Ok(()) } -/// Repeatedly run the `overall_*` integration tests until one fails, to expose -/// intermittent failures. This is a translation of `server/run_until_fail.ps1`. +/// Repeatedly run the `overall` integration test modules until one fails, to +/// expose intermittent failures. fn run_until_fail() -> io::Result<()> { // Provide a backtrace on a failing test, matching the script's // `RUST_BACKTRACE=1`. unsafe { env::set_var("RUST_BACKTRACE", "1"); } - let tests = [ - "overall_1", - "overall_2", - "overall_3", - "overall_4", - "overall_5", - ]; let mut iteration = 0; loop { iteration += 1; // Clear the screen so only the current iteration's output is visible. print!("\x1b[2J\x1b[H"); println!("--- Iteration {iteration} ---"); - for test in tests { - // `run_cmd!` returns an error if `cargo test` exits non-zero, which - // breaks out of the loop -- the same behavior as the script. - run_cmd!(cargo test --test $test).map_err(|err| { - io::Error::other(format!( - "Test {test} failed on iteration {iteration}: {err}" - )) - })?; - } + // `run_cmd!` returns an error if `cargo test` exits non-zero, which + // breaks out of the loop -- the same behavior as the script. + run_cmd!(cargo test --test overall).map_err(|err| { + io::Error::other(format!("Test failed on iteration {iteration}: {err}")) + })?; } } @@ -573,6 +614,8 @@ fn run_build() -> io::Result<()> { cargo build --manifest-path=$BUILDER_PATH/Cargo.toml; info "cargo build"; cargo build; + info "Standalone: cargo build"; + cargo build --manifest-path=$STANDALONE_PATH/Cargo.toml; )?; // Clean out all bundled files before the rebuild. remove_dir_all_if_exists(format!("{CLIENT_PATH}/static/bundled"))?; @@ -766,7 +809,7 @@ fn run_change_version(new_version: &String) -> io::Result<()> { )?; search_and_replace_file( format!("{CLIENT_PATH}/package.json5"), - r#"(\r?\n version: ')[\d.]+(?:-[a-z\d]*)?(',\r?\n)"#, + r"(\r?\n version: ')[\d.]+(?:-[a-z\d]*)?(',\r?\n)", &replacement_string, )?; Ok(()) @@ -835,15 +878,9 @@ fn run_postrelease(target: &str, tag: &str) -> io::Result<()> { fn run_coverage() -> io::Result<()> { run_cmd!( - info "cargo tarpaulin --skip-clean --out=html --target-dir=tarpaulin"; - cargo tarpaulin --skip-clean --out=html --out=json --target-dir=tarpaulin; - )?; - - // Open the resulting coverage report in the default web browser. The current - // working directory is `server/` (see `main`), so the report lives at - // `server/tarpaulin-report.html`. - let report = Path::new("tarpaulin-report.html"); - open::that(report) + info "cargo llvm-cov --open"; + cargo llvm-cov --open; + ) } // CLI implementation @@ -857,6 +894,7 @@ impl Cli { Commands::Install { dev } => run_install(*dev), Commands::Update => run_update(), Commands::Flint { check } => run_format_and_lint(*check), + Commands::Full => run_full(), Commands::Test => run_test(), Commands::RunUntilFail => run_until_fail(), Commands::Build => run_build(), diff --git a/client/eslint.config.js b/client/eslint.config.js index 3119990a..5eb7bd63 100644 --- a/client/eslint.config.js +++ b/client/eslint.config.js @@ -63,6 +63,56 @@ export default defineConfig( ignoreRestSiblings: true, }, ], + "@typescript-eslint/naming-convention": [ + "error", + { + selector: "default", + format: ["camelCase"], + leadingUnderscore: "allow", + trailingUnderscore: "allow", + }, + // Imports may keep whatever casing their source uses + // (PascalCase classes, UPPER_CASE constants, etc.), since + // we don't control the exporting library/module. + { selector: "import", format: null }, + { + selector: "variable", + format: ["camelCase", "UPPER_CASE", "PascalCase"], + leadingUnderscore: "allow", + trailingUnderscore: "allow", + }, + { + selector: ["function", "parameter"], + format: ["camelCase"], + leadingUnderscore: "allow", + trailingUnderscore: "allow", + }, + { + selector: ["memberLike", "parameterProperty"], + format: ["camelCase"], + leadingUnderscore: "allow", + trailingUnderscore: "allow", + }, + { selector: "typeLike", format: ["PascalCase"] }, + { selector: "enumMember", format: ["PascalCase"] }, + // Object/type literal properties frequently mirror + // external APIs or wire formats we don't control (DOM + // datasets, CodeMirror options, JSON payloads), so leave + // their casing unconstrained. + { + selector: ["objectLiteralProperty", "typeProperty"], + format: null, + }, + ], + }, + }, + { + // Generated by `ts-rs` from Rust struct definitions -- naming + // mirrors the Rust source and must not be changed here. + name: "rust-types", + files: ["src/rust-types/**"], + rules: { + "@typescript-eslint/naming-convention": "off", }, }, { diff --git a/client/package.json5 b/client/package.json5 index 76c5952e..f4772c00 100644 --- a/client/package.json5 +++ b/client/package.json5 @@ -64,13 +64,13 @@ '@codemirror/legacy-modes': '^6.5.3', '@codemirror/state': '^6.7.1', '@codemirror/view': '^6.43.6', - '@hpcc-js/wasm-graphviz': '^1.27.0', + '@hpcc-js/wasm-graphviz': '^1.28.0', '@mathjax/mathjax-newcm-font': '^4.1.3', codemirror: '^6.0.2', mathjax: '^4.1.3', mermaid: '^11.16.0', 'pdfjs-dist': '^6.1.200', - tinymce: '^8.8.0', + tinymce: '^8.8.1', 'toastify-js': '^1.12.0', }, devDependencies: { @@ -80,28 +80,28 @@ '@types/dom-navigation': '^1.0.7', '@types/js-beautify': '^1.14.3', '@types/mocha': '^10.0.10', - '@types/node': '^24.13.3', + '@types/node': '^26.1.1', '@types/toastify-js': '^1.12.4', - '@typescript-eslint/eslint-plugin': '^8.64.0', - '@typescript-eslint/parser': '^8.64.0', + '@typescript-eslint/eslint-plugin': '^8.65.0', + '@typescript-eslint/parser': '^8.65.0', + '@typescript/native': 'npm:typescript@^7.0.2', chai: '^6.2.2', esbuild: '^0.28.1', - eslint: '^10.7.0', + eslint: '^10.8.0', 'eslint-config-prettier': '^10.1.8', 'eslint-plugin-import': '^2.32.0', 'eslint-plugin-prettier': '^5.5.6', globals: '^17.7.0', mocha: '^11.7.6', 'npm-check-updates': '^22.2.9', - prettier: '^3.9.5', + prettier: '^3.9.6', /* Typescript 7 support for eslint is blocked per (this issue)\[https://github.com/typescript-eslint/typescript-eslint/issues/10940\]. Instead, see [Running Typescript 7.0 Side-by-Side with TypeScript 6.0](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/#running-side-by-side-with-typescript-6.0) for a workaround. */ typescript: 'npm:@typescript/typescript6@^6.0.2', - "@typescript/native": "npm:typescript@^7.0.2", - 'typescript-eslint': '^8.64.0', + 'typescript-eslint': '^8.65.0', }, scripts: { test: 'echo "Error: no test specified" && exit 1', diff --git a/client/pnpm-lock.yaml b/client/pnpm-lock.yaml index 16a88005..d0156bb2 100644 --- a/client/pnpm-lock.yaml +++ b/client/pnpm-lock.yaml @@ -66,8 +66,8 @@ importers: specifier: ^6.43.6 version: 6.43.6 '@hpcc-js/wasm-graphviz': - specifier: ^1.27.0 - version: 1.27.0 + specifier: ^1.28.0 + version: 1.28.0 '@mathjax/mathjax-newcm-font': specifier: ^4.1.3 version: 4.1.3 @@ -84,8 +84,8 @@ importers: specifier: ^6.1.200 version: 6.1.200 tinymce: - specifier: ^8.8.0 - version: 8.8.0 + specifier: ^8.8.1 + version: 8.8.1 toastify-js: specifier: ^1.12.0 version: 1.12.0 @@ -95,7 +95,7 @@ importers: version: 1.4.0 '@eslint/js': specifier: ^10.0.1 - version: 10.0.1(eslint@10.7.0(supports-color@8.1.1)) + version: 10.0.1(eslint@10.8.0(supports-color@8.1.1)) '@types/chai': specifier: ^5.2.3 version: 5.2.3 @@ -109,17 +109,17 @@ importers: specifier: ^10.0.10 version: 10.0.10 '@types/node': - specifier: ^24.13.3 - version: 24.13.3 + specifier: ^26.1.1 + version: 26.1.1 '@types/toastify-js': specifier: ^1.12.4 version: 1.12.4 '@typescript-eslint/eslint-plugin': - specifier: ^8.64.0 - version: 8.64.0(@typescript-eslint/parser@8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@8.1.1))(supports-color@8.1.1))(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@8.1.1))(supports-color@8.1.1) + specifier: ^8.65.0 + version: 8.65.0(@typescript-eslint/parser@8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@8.1.1))(supports-color@8.1.1))(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@8.1.1))(supports-color@8.1.1) '@typescript-eslint/parser': - specifier: ^8.64.0 - version: 8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@8.1.1))(supports-color@8.1.1) + specifier: ^8.65.0 + version: 8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@8.1.1))(supports-color@8.1.1) '@typescript/native': specifier: npm:typescript@^7.0.2 version: typescript@7.0.2 @@ -130,17 +130,17 @@ importers: specifier: ^0.28.1 version: 0.28.1 eslint: - specifier: ^10.7.0 - version: 10.7.0(supports-color@8.1.1) + specifier: ^10.8.0 + version: 10.8.0(supports-color@8.1.1) eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@10.7.0(supports-color@8.1.1)) + version: 10.1.8(eslint@10.8.0(supports-color@8.1.1)) eslint-plugin-import: specifier: ^2.32.0 - version: 2.32.0(@typescript-eslint/parser@8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@8.1.1))(supports-color@8.1.1))(eslint@10.7.0(supports-color@8.1.1))(supports-color@8.1.1) + version: 2.32.0(@typescript-eslint/parser@8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@8.1.1))(supports-color@8.1.1))(eslint@10.8.0(supports-color@8.1.1))(supports-color@8.1.1) eslint-plugin-prettier: specifier: ^5.5.6 - version: 5.5.6(eslint-config-prettier@10.1.8(eslint@10.7.0(supports-color@8.1.1)))(eslint@10.7.0(supports-color@8.1.1))(prettier@3.9.5) + version: 5.5.6(eslint-config-prettier@10.1.8(eslint@10.8.0(supports-color@8.1.1)))(eslint@10.8.0(supports-color@8.1.1))(prettier@3.9.6) globals: specifier: ^17.7.0 version: 17.7.0 @@ -151,14 +151,14 @@ importers: specifier: ^22.2.9 version: 22.2.9 prettier: - specifier: ^3.9.5 - version: 3.9.5 + specifier: ^3.9.6 + version: 3.9.6 typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' typescript-eslint: - specifier: ^8.64.0 - version: 8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@8.1.1))(supports-color@8.1.1) + specifier: ^8.65.0 + version: 8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@8.1.1))(supports-color@8.1.1) packages: @@ -393,8 +393,8 @@ packages: cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.9.1': - resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + '@eslint-community/eslint-utils@4.10.1': + resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 @@ -407,16 +407,16 @@ packages: resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/config-helpers@0.6.0': - resolution: {integrity: sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==} + '@eslint/config-helpers@0.7.0': + resolution: {integrity: sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@eslint/core@1.2.1': resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/css-tree@4.0.4': - resolution: {integrity: sha512-nxMparyhqVWQvadx9x8dIfubfIPOE+X2b2waua8fzdnM9vdp9rgVtwEZlG0TmCwEUz/d/f40fzvO/eqBwdxz0A==} + '@eslint/css-tree@4.0.5': + resolution: {integrity: sha512-iPmijIAq4hlIJB86PYmY/fcZORHtjphSqICDbwuw32A/JmkhZQ/K/6TjHE03zqf3n5yABpVcbRAMG8Mi9ojy8g==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@eslint/css@1.4.0': @@ -440,8 +440,8 @@ packages: resolution: {integrity: sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@hpcc-js/wasm-graphviz@1.27.0': - resolution: {integrity: sha512-akmM2zI64U2osEByxcULTQLe2PGlpsWCHQYvHPRwKA6HPEFSrnvyXtz0d2SJvlDer9RDZD91BLyFhOTJbUzp3A==} + '@hpcc-js/wasm-graphviz@1.28.0': + resolution: {integrity: sha512-KqvPy7ckxPc1xv8Kt7H3bo9cj8U4gfOXxQ8grLZvDZxyQQjnXsKxrweJLLJtEoWWtQqAk4biUB0g2qsjkTiMVA==} '@humanfs/core@0.19.2': resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} @@ -739,8 +739,8 @@ packages: '@types/mocha@10.0.10': resolution: {integrity: sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==} - '@types/node@24.13.3': - resolution: {integrity: sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==} + '@types/node@26.1.1': + resolution: {integrity: sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==} '@types/toastify-js@1.12.4': resolution: {integrity: sha512-zfZHU4tKffPCnZRe7pjv/eFKzTVHozKewFCKaCjZ4gFinKgJRz/t0bkZiMCXJxPhv/ZoeDGNOeRD09R0kQZ/nw==} @@ -748,63 +748,63 @@ packages: '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} - '@typescript-eslint/eslint-plugin@8.64.0': - resolution: {integrity: sha512-CGvQPBxN3wZLu6Rz2kFUpZeoCm78xUic92ck39KPePkO1NPOwjCqdQnm5Q87tpWw9vcBvW8XLrDXjH9PWYtJ3Q==} + '@typescript-eslint/eslint-plugin@8.65.0': + resolution: {integrity: sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.64.0 + '@typescript-eslint/parser': ^8.65.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.64.0': - resolution: {integrity: sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw==} + '@typescript-eslint/parser@8.65.0': + resolution: {integrity: sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.64.0': - resolution: {integrity: sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg==} + '@typescript-eslint/project-service@8.65.0': + resolution: {integrity: sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.64.0': - resolution: {integrity: sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==} + '@typescript-eslint/scope-manager@8.65.0': + resolution: {integrity: sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.64.0': - resolution: {integrity: sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==} + '@typescript-eslint/tsconfig-utils@8.65.0': + resolution: {integrity: sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.64.0': - resolution: {integrity: sha512-XWG4Fmmv/6SvyS9nH8jWrKs6terwJvE8cyRt1CzYYqzp9OrPhCT4cMc/f7C6RZCwG+qMmiffJS1/qJP8G1URtg==} + '@typescript-eslint/type-utils@8.65.0': + resolution: {integrity: sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.64.0': - resolution: {integrity: sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==} + '@typescript-eslint/types@8.65.0': + resolution: {integrity: sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.64.0': - resolution: {integrity: sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA==} + '@typescript-eslint/typescript-estree@8.65.0': + resolution: {integrity: sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.64.0': - resolution: {integrity: sha512-aJUGVB3+U0htrrCjoA8qukw8cm8fNCGAxK/tVoS70k8aeb7DETKeFozRiVFIwEeN9WJLsjaP3ph8I60tY2XZoQ==} + '@typescript-eslint/utils@8.65.0': + resolution: {integrity: sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.64.0': - resolution: {integrity: sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==} + '@typescript-eslint/visitor-keys@8.65.0': + resolution: {integrity: sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@typescript/typescript-aix-ppc64@7.0.2': @@ -1015,9 +1015,9 @@ packages: brace-expansion@2.1.2: resolution: {integrity: sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==} - brace-expansion@5.0.7: - resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==} - engines: {node: 18 || 20 || >=22} + brace-expansion@5.0.8: + resolution: {integrity: sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==} + engines: {node: 20 || >=22} browser-stdout@1.3.1: resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} @@ -1350,8 +1350,8 @@ packages: resolution: {integrity: sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==} engines: {node: '>= 0.4'} - es-toolkit@1.49.0: - resolution: {integrity: sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g==} + es-toolkit@1.50.0: + resolution: {integrity: sha512-OyZKhUVvEep9ITEiwHn8GKnMRQIVqoSIX7WnRbkWgJkllCujilqP2rD0u979tkl8wqyc8ICwlc1UBVv/Sl1G6w==} esbuild@0.28.1: resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} @@ -1432,8 +1432,8 @@ packages: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@10.7.0: - resolution: {integrity: sha512-GVTD7s1vdIl6UYvAfriOPeY1Df8LIZjfofLvHwde+erDHGGuHyuM6xoxRxmHiebhYuD2p1vN4wWh0XzPARSGDQ==} + eslint@10.8.0: + resolution: {integrity: sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: @@ -1499,8 +1499,8 @@ packages: resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} hasBin: true - flatted@3.4.2: - resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + flatted@3.4.3: + resolution: {integrity: sha512-/zipXxyO6rGvuNGDiULY9MvEGSkb2gaG4GGH4ygMi0ZZzyMHdUZBmntJmx5x1G2VuPytCwGN4xsJP6cw+sK+vQ==} for-each@0.3.5: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} @@ -1813,8 +1813,8 @@ packages: mathjax@4.1.3: resolution: {integrity: sha512-BN/8Pkgn7G1pIDYJqd9md+JHsE/jydSYbyOZnSdSA0WziuVO8mRxdYiWFumkVVly/8U+hm9DpIIoWuvySverzw==} - mdn-data@2.28.1: - resolution: {integrity: sha512-U9w+PzSZ00Z5m9rZ5ARVFL5xOfuCHdKYi/1RRwDCJsboFgJDNT3zT6PIPD7mZQYaQLhsZM3GfDRgSMRHhSmVng==} + mdn-data@2.29.0: + resolution: {integrity: sha512-pVxQFCcaYUEAH853+v7yoI/qzhxXSq1bTb9obMYGYAN1c3Hen+XDCEvr296XhstrwlSTNgOR7mCSD4JPjbJe5A==} mermaid@11.16.0: resolution: {integrity: sha512-Zvm3kbstgdpvIJPPItlL7fppIZ3kibvc1oZIGxdvk9t6UFz6flv+Jw7FtRGKwfcI8OckmH04LqG6LlS6X4B1pA==} @@ -1889,8 +1889,8 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} - own-keys@1.0.1: - resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + own-keys@1.0.2: + resolution: {integrity: sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==} engines: {node: '>= 0.4'} p-limit@3.1.0: @@ -1904,8 +1904,8 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - package-manager-detector@1.7.0: - resolution: {integrity: sha512-xg1eHpwYL/D/HEdWw2goFZP6vV0FH7W+PZ5rFkGjdIDLtxq7EkzBUeT3m+lndYCt8wKbmofUu1MUdMCXkCk9ZQ==} + package-manager-detector@1.8.0: + resolution: {integrity: sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==} path-data-parser@0.1.0: resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==} @@ -1954,8 +1954,8 @@ packages: resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==} engines: {node: '>=6.0.0'} - prettier@3.9.5: - resolution: {integrity: sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==} + prettier@3.9.6: + resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==} engines: {node: '>=14'} hasBin: true @@ -2140,8 +2140,8 @@ packages: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} - tinymce@8.8.0: - resolution: {integrity: sha512-rdhwXLHDjUTJ42Cvq1tKRDvS4h8oeQSiWeNGx82AKNdOm8FGAqKObeEPa6amFGobTXHcoFtPZRtyLHO5zV5mZA==} + tinymce@8.8.1: + resolution: {integrity: sha512-hPOMOsF0PyMNu3ExjbqUidxHUUjmcIJp1k0e8Q45q0WwW93alxunysYPpEJ2Q2PoAYsDiRCWOowJhW6Rx0xZRw==} toastify-js@1.12.0: resolution: {integrity: sha512-HeMHCO9yLPvP9k0apGSdPUWrUbLnxUKNFzgUoZp1PHCLploIX/4DSQ7V8H25ef+h4iO9n0he7ImfcndnN6nDrQ==} @@ -2179,8 +2179,8 @@ packages: resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==} engines: {node: '>= 0.4'} - typescript-eslint@8.64.0: - resolution: {integrity: sha512-0qg+pDNMnqYzqH9AnNK+39tejHvsShUOUUoRUgtnTGE7QuMZhiFDnozq8nHJVq+Wae6NMLKNWLg5WmkcC/ndyQ==} + typescript-eslint@8.65.0: + resolution: {integrity: sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -2200,8 +2200,8 @@ packages: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} - undici-types@7.18.2: - resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + undici-types@8.3.0: + resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -2273,7 +2273,7 @@ snapshots: '@antfu/install-pkg@1.1.0': dependencies: - package-manager-detector: 1.7.0 + package-manager-detector: 1.8.0 tinyexec: 1.2.4 '@braintree/sanitize-url@7.1.2': {} @@ -2520,9 +2520,9 @@ snapshots: '@esbuild/win32-x64@0.28.1': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@10.7.0(supports-color@8.1.1))': + '@eslint-community/eslint-utils@4.10.1(eslint@10.8.0(supports-color@8.1.1))': dependencies: - eslint: 10.7.0(supports-color@8.1.1) + eslint: 10.8.0(supports-color@8.1.1) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -2535,7 +2535,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/config-helpers@0.6.0': + '@eslint/config-helpers@0.7.0': dependencies: '@eslint/core': 1.2.1 @@ -2543,20 +2543,20 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/css-tree@4.0.4': + '@eslint/css-tree@4.0.5': dependencies: - mdn-data: 2.28.1 + mdn-data: 2.29.0 source-map-js: 1.2.1 '@eslint/css@1.4.0': dependencies: '@eslint/core': 1.2.1 - '@eslint/css-tree': 4.0.4 + '@eslint/css-tree': 4.0.5 '@eslint/plugin-kit': 0.7.2 - '@eslint/js@10.0.1(eslint@10.7.0(supports-color@8.1.1))': + '@eslint/js@10.0.1(eslint@10.8.0(supports-color@8.1.1))': optionalDependencies: - eslint: 10.7.0(supports-color@8.1.1) + eslint: 10.8.0(supports-color@8.1.1) '@eslint/object-schema@3.0.5': {} @@ -2565,7 +2565,7 @@ snapshots: '@eslint/core': 1.2.1 levn: 0.4.1 - '@hpcc-js/wasm-graphviz@1.27.0': {} + '@hpcc-js/wasm-graphviz@1.28.0': {} '@humanfs/core@0.19.2': dependencies: @@ -2890,24 +2890,24 @@ snapshots: '@types/mocha@10.0.10': {} - '@types/node@24.13.3': + '@types/node@26.1.1': dependencies: - undici-types: 7.18.2 + undici-types: 8.3.0 '@types/toastify-js@1.12.4': {} '@types/trusted-types@2.0.7': optional: true - '@typescript-eslint/eslint-plugin@8.64.0(@typescript-eslint/parser@8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@8.1.1))(supports-color@8.1.1))(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@8.1.1))(supports-color@8.1.1)': + '@typescript-eslint/eslint-plugin@8.65.0(@typescript-eslint/parser@8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@8.1.1))(supports-color@8.1.1))(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@8.1.1))(supports-color@8.1.1) - '@typescript-eslint/scope-manager': 8.64.0 - '@typescript-eslint/type-utils': 8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@8.1.1))(supports-color@8.1.1) - '@typescript-eslint/utils': 8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@8.1.1))(supports-color@8.1.1) - '@typescript-eslint/visitor-keys': 8.64.0 - eslint: 10.7.0(supports-color@8.1.1) + '@typescript-eslint/parser': 8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@8.1.1))(supports-color@8.1.1) + '@typescript-eslint/scope-manager': 8.65.0 + '@typescript-eslint/type-utils': 8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@8.1.1))(supports-color@8.1.1) + '@typescript-eslint/utils': 8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@8.1.1))(supports-color@8.1.1) + '@typescript-eslint/visitor-keys': 8.65.0 + eslint: 10.8.0(supports-color@8.1.1) ignore: 7.0.6 natural-compare: 1.4.0 ts-api-utils: 2.5.0(@typescript/typescript6@6.0.2) @@ -2915,56 +2915,56 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@8.1.1))(supports-color@8.1.1)': + '@typescript-eslint/parser@8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@typescript-eslint/scope-manager': 8.64.0 - '@typescript-eslint/types': 8.64.0 - '@typescript-eslint/typescript-estree': 8.64.0(@typescript/typescript6@6.0.2)(supports-color@8.1.1) - '@typescript-eslint/visitor-keys': 8.64.0 + '@typescript-eslint/scope-manager': 8.65.0 + '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/typescript-estree': 8.65.0(@typescript/typescript6@6.0.2)(supports-color@8.1.1) + '@typescript-eslint/visitor-keys': 8.65.0 debug: 4.4.3(supports-color@8.1.1) - eslint: 10.7.0(supports-color@8.1.1) + eslint: 10.8.0(supports-color@8.1.1) typescript: '@typescript/typescript6@6.0.2' transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.64.0(@typescript/typescript6@6.0.2)(supports-color@8.1.1)': + '@typescript-eslint/project-service@8.65.0(@typescript/typescript6@6.0.2)(supports-color@8.1.1)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.64.0(@typescript/typescript6@6.0.2) - '@typescript-eslint/types': 8.64.0 + '@typescript-eslint/tsconfig-utils': 8.65.0(@typescript/typescript6@6.0.2) + '@typescript-eslint/types': 8.65.0 debug: 4.4.3(supports-color@8.1.1) typescript: '@typescript/typescript6@6.0.2' transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.64.0': + '@typescript-eslint/scope-manager@8.65.0': dependencies: - '@typescript-eslint/types': 8.64.0 - '@typescript-eslint/visitor-keys': 8.64.0 + '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/visitor-keys': 8.65.0 - '@typescript-eslint/tsconfig-utils@8.64.0(@typescript/typescript6@6.0.2)': + '@typescript-eslint/tsconfig-utils@8.65.0(@typescript/typescript6@6.0.2)': dependencies: typescript: '@typescript/typescript6@6.0.2' - '@typescript-eslint/type-utils@8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@8.1.1))(supports-color@8.1.1)': + '@typescript-eslint/type-utils@8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@typescript-eslint/types': 8.64.0 - '@typescript-eslint/typescript-estree': 8.64.0(@typescript/typescript6@6.0.2)(supports-color@8.1.1) - '@typescript-eslint/utils': 8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@8.1.1))(supports-color@8.1.1) + '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/typescript-estree': 8.65.0(@typescript/typescript6@6.0.2)(supports-color@8.1.1) + '@typescript-eslint/utils': 8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@8.1.1))(supports-color@8.1.1) debug: 4.4.3(supports-color@8.1.1) - eslint: 10.7.0(supports-color@8.1.1) + eslint: 10.8.0(supports-color@8.1.1) ts-api-utils: 2.5.0(@typescript/typescript6@6.0.2) typescript: '@typescript/typescript6@6.0.2' transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.64.0': {} + '@typescript-eslint/types@8.65.0': {} - '@typescript-eslint/typescript-estree@8.64.0(@typescript/typescript6@6.0.2)(supports-color@8.1.1)': + '@typescript-eslint/typescript-estree@8.65.0(@typescript/typescript6@6.0.2)(supports-color@8.1.1)': dependencies: - '@typescript-eslint/project-service': 8.64.0(@typescript/typescript6@6.0.2)(supports-color@8.1.1) - '@typescript-eslint/tsconfig-utils': 8.64.0(@typescript/typescript6@6.0.2) - '@typescript-eslint/types': 8.64.0 - '@typescript-eslint/visitor-keys': 8.64.0 + '@typescript-eslint/project-service': 8.65.0(@typescript/typescript6@6.0.2)(supports-color@8.1.1) + '@typescript-eslint/tsconfig-utils': 8.65.0(@typescript/typescript6@6.0.2) + '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/visitor-keys': 8.65.0 debug: 4.4.3(supports-color@8.1.1) minimatch: 10.2.5 semver: 7.8.5 @@ -2974,20 +2974,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@8.1.1))(supports-color@8.1.1)': + '@typescript-eslint/utils@8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.7.0(supports-color@8.1.1)) - '@typescript-eslint/scope-manager': 8.64.0 - '@typescript-eslint/types': 8.64.0 - '@typescript-eslint/typescript-estree': 8.64.0(@typescript/typescript6@6.0.2)(supports-color@8.1.1) - eslint: 10.7.0(supports-color@8.1.1) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.0(supports-color@8.1.1)) + '@typescript-eslint/scope-manager': 8.65.0 + '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/typescript-estree': 8.65.0(@typescript/typescript6@6.0.2)(supports-color@8.1.1) + eslint: 10.8.0(supports-color@8.1.1) typescript: '@typescript/typescript6@6.0.2' transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.64.0': + '@typescript-eslint/visitor-keys@8.65.0': dependencies: - '@typescript-eslint/types': 8.64.0 + '@typescript-eslint/types': 8.65.0 eslint-visitor-keys: 5.0.1 '@typescript/typescript-aix-ppc64@7.0.2': @@ -3155,7 +3155,7 @@ snapshots: dependencies: balanced-match: 1.0.2 - brace-expansion@5.0.7: + brace-expansion@5.0.8: dependencies: balanced-match: 4.0.4 @@ -3540,7 +3540,7 @@ snapshots: object-inspect: 1.13.4 object-keys: 1.1.1 object.assign: 4.1.7 - own-keys: 1.0.1 + own-keys: 1.0.2 regexp.prototype.flags: 1.5.4 safe-array-concat: 1.1.4 safe-push-apply: 1.0.0 @@ -3585,7 +3585,7 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 - es-toolkit@1.49.0: {} + es-toolkit@1.50.0: {} esbuild@0.28.1: optionalDependencies: @@ -3620,9 +3620,9 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-prettier@10.1.8(eslint@10.7.0(supports-color@8.1.1)): + eslint-config-prettier@10.1.8(eslint@10.8.0(supports-color@8.1.1)): dependencies: - eslint: 10.7.0(supports-color@8.1.1) + eslint: 10.8.0(supports-color@8.1.1) eslint-import-resolver-node@0.3.10(supports-color@8.1.1): dependencies: @@ -3632,17 +3632,17 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.14.0(@typescript-eslint/parser@8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@8.1.1))(supports-color@8.1.1))(eslint-import-resolver-node@0.3.10(supports-color@8.1.1))(eslint@10.7.0(supports-color@8.1.1))(supports-color@8.1.1): + eslint-module-utils@2.14.0(@typescript-eslint/parser@8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@8.1.1))(supports-color@8.1.1))(eslint-import-resolver-node@0.3.10(supports-color@8.1.1))(eslint@10.8.0(supports-color@8.1.1))(supports-color@8.1.1): dependencies: debug: 3.2.7(supports-color@8.1.1) optionalDependencies: - '@typescript-eslint/parser': 8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@8.1.1))(supports-color@8.1.1) - eslint: 10.7.0(supports-color@8.1.1) + '@typescript-eslint/parser': 8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@8.1.1))(supports-color@8.1.1) + eslint: 10.8.0(supports-color@8.1.1) eslint-import-resolver-node: 0.3.10(supports-color@8.1.1) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@8.1.1))(supports-color@8.1.1))(eslint@10.7.0(supports-color@8.1.1))(supports-color@8.1.1): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@8.1.1))(supports-color@8.1.1))(eslint@10.8.0(supports-color@8.1.1))(supports-color@8.1.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -3651,9 +3651,9 @@ snapshots: array.prototype.flatmap: 1.3.3 debug: 3.2.7(supports-color@8.1.1) doctrine: 2.1.0 - eslint: 10.7.0(supports-color@8.1.1) + eslint: 10.8.0(supports-color@8.1.1) eslint-import-resolver-node: 0.3.10(supports-color@8.1.1) - eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@8.1.1))(supports-color@8.1.1))(eslint-import-resolver-node@0.3.10(supports-color@8.1.1))(eslint@10.7.0(supports-color@8.1.1))(supports-color@8.1.1) + eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@8.1.1))(supports-color@8.1.1))(eslint-import-resolver-node@0.3.10(supports-color@8.1.1))(eslint@10.8.0(supports-color@8.1.1))(supports-color@8.1.1) hasown: 2.0.4 is-core-module: 2.16.2 is-glob: 4.0.3 @@ -3665,20 +3665,20 @@ snapshots: string.prototype.trimend: 1.0.10 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@8.1.1))(supports-color@8.1.1) + '@typescript-eslint/parser': 8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@8.1.1))(supports-color@8.1.1) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-prettier@5.5.6(eslint-config-prettier@10.1.8(eslint@10.7.0(supports-color@8.1.1)))(eslint@10.7.0(supports-color@8.1.1))(prettier@3.9.5): + eslint-plugin-prettier@5.5.6(eslint-config-prettier@10.1.8(eslint@10.8.0(supports-color@8.1.1)))(eslint@10.8.0(supports-color@8.1.1))(prettier@3.9.6): dependencies: - eslint: 10.7.0(supports-color@8.1.1) - prettier: 3.9.5 + eslint: 10.8.0(supports-color@8.1.1) + prettier: 3.9.6 prettier-linter-helpers: 1.0.1 synckit: 0.11.13 optionalDependencies: - eslint-config-prettier: 10.1.8(eslint@10.7.0(supports-color@8.1.1)) + eslint-config-prettier: 10.1.8(eslint@10.8.0(supports-color@8.1.1)) eslint-scope@9.1.2: dependencies: @@ -3691,12 +3691,12 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.7.0(supports-color@8.1.1): + eslint@10.8.0(supports-color@8.1.1): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.7.0(supports-color@8.1.1)) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.0(supports-color@8.1.1)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.23.5(supports-color@8.1.1) - '@eslint/config-helpers': 0.6.0 + '@eslint/config-helpers': 0.7.0 '@eslint/core': 1.2.1 '@eslint/plugin-kit': 0.7.2 '@humanfs/node': 0.16.8 @@ -3767,12 +3767,12 @@ snapshots: flat-cache@4.0.1: dependencies: - flatted: 3.4.2 + flatted: 3.4.3 keyv: 4.5.4 flat@5.0.2: {} - flatted@3.4.2: {} + flatted@3.4.3: {} for-each@0.3.5: dependencies: @@ -4079,7 +4079,7 @@ snapshots: dependencies: '@mathjax/mathjax-newcm-font': 4.1.3 - mdn-data@2.28.1: {} + mdn-data@2.29.0: {} mermaid@11.16.0: dependencies: @@ -4096,7 +4096,7 @@ snapshots: dagre-d3-es: 7.0.14 dayjs: 1.11.21 dompurify: 3.4.12 - es-toolkit: 1.49.0 + es-toolkit: 1.50.0 katex: 0.16.47 khroma: 2.1.0 marked: 16.4.2 @@ -4107,7 +4107,7 @@ snapshots: minimatch@10.2.5: dependencies: - brace-expansion: 5.0.7 + brace-expansion: 5.0.8 minimatch@3.1.5: dependencies: @@ -4207,8 +4207,9 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 - own-keys@1.0.1: + own-keys@1.0.2: dependencies: + call-bound: 1.0.4 get-intrinsic: 1.3.0 object-keys: 1.1.1 safe-push-apply: 1.0.0 @@ -4223,7 +4224,7 @@ snapshots: package-json-from-dist@1.0.1: {} - package-manager-detector@1.7.0: {} + package-manager-detector@1.8.0: {} path-data-parser@0.1.0: {} @@ -4261,7 +4262,7 @@ snapshots: dependencies: fast-diff: 1.3.0 - prettier@3.9.5: {} + prettier@3.9.6: {} punycode@2.3.1: {} @@ -4482,7 +4483,7 @@ snapshots: fdir: 6.5.0(picomatch@4.0.5) picomatch: 4.0.5 - tinymce@8.8.0: {} + tinymce@8.8.1: {} toastify-js@1.12.0: {} @@ -4536,13 +4537,13 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript-eslint@8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@8.1.1))(supports-color@8.1.1): + typescript-eslint@8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@8.1.1))(supports-color@8.1.1): dependencies: - '@typescript-eslint/eslint-plugin': 8.64.0(@typescript-eslint/parser@8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@8.1.1))(supports-color@8.1.1))(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@8.1.1))(supports-color@8.1.1) - '@typescript-eslint/parser': 8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@8.1.1))(supports-color@8.1.1) - '@typescript-eslint/typescript-estree': 8.64.0(@typescript/typescript6@6.0.2)(supports-color@8.1.1) - '@typescript-eslint/utils': 8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@8.1.1))(supports-color@8.1.1) - eslint: 10.7.0(supports-color@8.1.1) + '@typescript-eslint/eslint-plugin': 8.65.0(@typescript-eslint/parser@8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@8.1.1))(supports-color@8.1.1))(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@8.1.1))(supports-color@8.1.1) + '@typescript-eslint/parser': 8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@8.1.1))(supports-color@8.1.1) + '@typescript-eslint/typescript-estree': 8.65.0(@typescript/typescript6@6.0.2)(supports-color@8.1.1) + '@typescript-eslint/utils': 8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@8.1.1))(supports-color@8.1.1) + eslint: 10.8.0(supports-color@8.1.1) typescript: '@typescript/typescript6@6.0.2' transitivePeerDependencies: - supports-color @@ -4579,7 +4580,7 @@ snapshots: has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 - undici-types@7.18.2: {} + undici-types@8.3.0: {} uri-js@4.4.1: dependencies: diff --git a/client/src/CodeChatEditor-test.mts b/client/src/CodeChatEditor-test.mts index d90e1535..ca1083fe 100644 --- a/client/src/CodeChatEditor-test.mts +++ b/client/src/CodeChatEditor-test.mts @@ -18,7 +18,7 @@ // ================================================================= // // To run tests, change to the `server/` directory then run `./bt client-build` -// to build any changes to the Client, followed by `cargo test --test overall_1 +// to build any changes to the Client, followed by `cargo test --test overall // test_client`. Or, add a `?test` parameter to any web page served by the // CodeChat Editor Server. // @@ -32,7 +32,7 @@ import { ChangeSpec, EditorState, EditorSelection } from "@codemirror/state"; import { CodeMirror, CodeMirrorDocBlockTuple } from "./shared.mjs"; import { DocBlockPlugin, - CodeMirror_JSON_fields, + codeMirrorJsonFields, } from "./CodeMirror-integration.mjs"; // Re-export everything that [CodeChatEditor.mts](CodeChatEditor.mts) exports. @@ -80,9 +80,9 @@ window.CodeChatEditor_test = () => { // On teardown, put the Mocha div at the beginning of the body. // Testing causes body to be wiped, so don't do this until all // tests are done. - const mocha_div = document.getElementById("mocha")!; + const mochaDiv = document.getElementById("mocha")!; const ccb = document.getElementById("CodeChat-body")!; - ccb.insertBefore(mocha_div, ccb.firstChild); + ccb.insertBefore(mochaDiv, ccb.firstChild); }, ], }); @@ -93,23 +93,20 @@ window.CodeChatEditor_test = () => { suite("CodeMirror checks", function () { test("insert/delete/replace expectations", function () { // Create a div to hold an editor. - const codechat_body = document.getElementById( + const codechatBody = document.getElementById( "CodeChat-body", ) as HTMLDivElement; - const testing_div = document.createElement("div"); - testing_div.id = "testing-div"; - codechat_body.insertBefore( - testing_div, - codechat_body.firstChild, - ); + const testingDiv = document.createElement("div"); + testingDiv.id = "testing-div"; + codechatBody.insertBefore(testingDiv, codechatBody.firstChild); // Test insert at beginning of doc block. - const after_state = run_CodeMirror_test( + const afterState = runCodeMirrorTest( "a\nbcd", [[1, 2, "", "#", "test"]], { from: 1, insert: "\n" }, ); - assert.deepEqual(after_state, { + assert.deepEqual(afterState, { doc: "a\n\nbcd", doc_blocks: [[1, 3, "", "#", "test"]], }); @@ -167,23 +164,23 @@ window.CodeChatEditor_test = () => { mocha.run(); }; -const run_CodeMirror_test = ( +const runCodeMirrorTest = ( doc: string, - doc_blocks: [CodeMirrorDocBlockTuple], + docBlocks: [CodeMirrorDocBlockTuple], changes: ChangeSpec, ): CodeMirror => { // Create the CodeChat Editor for testing. - const editor_state_json = { + const editorStateJson = { doc, selection: EditorSelection.single(0).toJSON(), - doc_blocks, + doc_blocks: docBlocks, }; const state = EditorState.fromJSON( - editor_state_json, + editorStateJson, { extensions: [DocBlockPlugin], }, - CodeMirror_JSON_fields, + codeMirrorJsonFields, ); const view = new EditorView({ parent: document.getElementById("testing-div")!, @@ -192,7 +189,7 @@ const run_CodeMirror_test = ( // Run a transaction, then extract at the results. view.dispatch({ changes }); - const after_state = view.state.toJSON(CodeMirror_JSON_fields); - delete after_state.selection; - return after_state; + const afterState = view.state.toJSON(codeMirrorJsonFields); + delete afterState.selection; + return afterState; }; diff --git a/client/src/CodeChatEditor.mts b/client/src/CodeChatEditor.mts index 3a46c87c..fbeac078 100644 --- a/client/src/CodeChatEditor.mts +++ b/client/src/CodeChatEditor.mts @@ -48,15 +48,15 @@ import "./third-party/wc-mermaid/wc-mermaid.js"; // #### Local import { assert } from "./assert.mjs"; -import { console_log, DEBUG_ENABLED } from "./debug_enabled.mjs"; +import { consoleLog, DEBUG_ENABLED } from "./debug_enabled.mjs"; import { - apply_diff_str, - CodeMirror_load, - CodeMirror_save, + applyDiffStr, + codeMirrorLoad, + codeMirrorSave, mathJaxTypeset, mathJaxUnTypeset, - scroll_to_line as codemirror_scroll_to_line, - set_CodeMirror_positions, + scrollToLine as codemirrorScrollToLine, + setCodeMirrorPositions, } from "./CodeMirror-integration.mjs"; import "./graphviz-webcomponent-setup.mjs"; // This must be imported *after* the previous setup import, so it's placed here, @@ -74,10 +74,10 @@ import { CodeMirrorDiffable, UpdateMessageContents, CodeMirror, - auto_update_timeout_ms, + autoUpdateTimeoutMs, rand, } from "./shared.mjs"; -import { show_toast } from "./show_toast.mjs"; +import { showToast } from "./show_toast.mjs"; // ### CSS import "./css/CodeChatEditor.css"; @@ -89,6 +89,12 @@ import { CursorPosition } from "./rust-types/CursorPosition.js"; // Define all possible editor modes; these are passed as // a [query string](https://en.wikipedia.org/wiki/Query_string) // (`http://path/to/foo.py?mode=toc`, for example) to the page's URL. +// +// The member names below are looked up by the string value of the `mode` +// query parameter (see the numeric-enum reverse-mapping trick used where this +// is read), so they must stay exactly these lowercase strings rather than +// following the usual PascalCase member naming convention. +/* eslint-disable @typescript-eslint/naming-convention */ enum EditorMode { // Display the source code using CodeChat, but disallow editing. view, @@ -100,25 +106,26 @@ enum EditorMode { // Show only raw source code; ignore doc blocks, treating them also as code. raw, } +/* eslint-enable @typescript-eslint/naming-convention */ // Tell TypeScript about the global namespace this program defines. declare global { interface Window { CodeChatEditor: { // Called by the Client Framework. - open_lp: ( - codechat_for_web: CodeChatForWeb, - is_re_translation: boolean, - cursor_position?: CursorPosition, - scroll_line?: number, + openLp: ( + codechatForWeb: CodeChatForWeb, + isReTranslation: boolean, + cursorPosition?: CursorPosition, + scrollLine?: number, ) => Promise; - do_debug: () => void; - send_update: (_only_if_dirty: boolean) => Promise; - scroll_to_line: ( - cursor_position?: CursorPosition, - scroll_line?: number, + doDebug: () => void; + sendUpdate: (_only_if_dirty: boolean) => Promise; + scrollToLine: ( + cursorPosition?: CursorPosition, + scrollLine?: number, ) => void; - show_toast: (text: string) => void; + showToast: (text: string) => void; allow_navigation: boolean; }; CodeChatEditor_test: unknown; @@ -134,9 +141,9 @@ let autoUpdateTimeoutId: null | number = null; // Store the lexer info for the currently-loaded language. // -// This mirrors the data provided by the server -- +// This mirrors the data provided by the server -- // see [SourceFileMetadata](../../server/src/webserver.rs#SourceFileMetadata). -let current_metadata: { +let currentMetadata: { mode: string; }; @@ -147,40 +154,40 @@ const webSocketComm = () => parent.window.CodeChatEditorFramework.webSocketComm; let ignoreTinyMceDirty = false; // True if the document is dirty (needs saving). -let is_dirty = false; +let isDirty = false; -export const set_is_dirty = (value: boolean = true) => { - is_dirty = value; +export const setIsDirty = (value: boolean = true) => { + isDirty = value; }; -export const get_is_dirty = () => is_dirty; +export const getIsDirty = () => isDirty; // ### TinyMCE dynamic import // // TinyMCE is dynamically imported when `init` is called. export const init = async (options: RawEditorOptions) => { - const tinymce_config = await import("./tinymce-config.mjs"); - tinymce = tinymce_config.tinymce; - return await tinymce_config.init(options); + const tinymceConfig = await import("./tinymce-config.mjs"); + tinymce = tinymceConfig.tinymce; + return await tinymceConfig.init(options); }; // The imported module is stored in this variable. export let tinymce: undefined | TinyMCE = undefined; // A single TinyMCE instance is used for all doc blocks. Avoid accessing this // through `tinymce.activeEditor`, which fails if the editor isn't active. -export const tinymce_instance = () => tinymce?.get(0); +export const tinymceInstance = () => tinymce?.get(0); // Page initialization // ------------------- // This is copied from // [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Document/DOMContentLoaded_event#checking_whether_loading_is_already_complete). -export const on_dom_content_loaded = (on_load_func: () => void) => { +export const onDomContentLoaded = (onLoadFunc: () => void) => { if (document.readyState === "loading") { // Loading hasn't finished yet. - document.addEventListener("DOMContentLoaded", on_load_func); + document.addEventListener("DOMContentLoaded", onLoadFunc); } else { // `DOMContentLoaded` has already fired. - on_load_func(); + onLoadFunc(); } }; @@ -188,26 +195,26 @@ export const on_dom_content_loaded = (on_load_func: () => void) => { // ------------- // // True if this is a CodeChat Editor document (not a source file). -const is_doc_only = () => { +const isDocOnly = () => { // This might be called by the framework before a document is loaded. So, - // make sure `current_metadata` exists first. - return current_metadata?.["mode"] === "markdown"; + // make sure `currentMetadata` exists first. + return currentMetadata?.["mode"] === "markdown"; }; -const open_lp = async ( - codechat_for_web: CodeChatForWeb, - is_re_translation: boolean, - cursor_position?: CursorPosition, - scroll_line?: number, +const openLp = async ( + codechatForWeb: CodeChatForWeb, + isReTranslation: boolean, + cursorPosition?: CursorPosition, + scrollLine?: number, ) => // Wait for the DOM to load before opening the file. await new Promise((resolve) => - on_dom_content_loaded(async () => { - await _open_lp( - codechat_for_web, - is_re_translation, - cursor_position, - scroll_line, + onDomContentLoaded(async () => { + await _openLp( + codechatForWeb, + isReTranslation, + cursorPosition, + scrollLine, ); resolve(); }), @@ -219,13 +226,13 @@ const open_lp = async ( // since this modifies the content based on cleanup rules before returning it -- // which causes applying diffs to this unexpectedly modified content to produce // incorrect results. This text is the unmodified content sent from the IDE. -let doc_content = ""; +let docContent = ""; // For debugging, allow the extension or server to run this routine by sending // the appropriate message. -const do_debug = () => { +const doDebug = () => { if (DEBUG_ENABLED) { - tinymce_instance()?.save({ format: "raw" }); + tinymceInstance()?.save({ format: "raw" }); } }; @@ -233,20 +240,20 @@ const do_debug = () => { // server has already lexed the source file into code and doc blocks; this // function transforms the code and doc blocks into HTML and updates the current // web page with the results. -const _open_lp = async ( +const _openLp = async ( // A data structure provided by the server, containing the source and // associated metadata. See [`AllSource`](#AllSource). - codechat_for_web: CodeChatForWeb, - is_re_translation: boolean, - cursor_position?: CursorPosition, - scroll_line?: number, + codechatForWeb: CodeChatForWeb, + isReTranslation: boolean, + cursorPosition?: CursorPosition, + scrollLine?: number, ) => { - // Note that globals, such as `is_dirty` and document contents, may change + // Note that globals, such as `isDirty` and document contents, may change // between `await` calls. The only call to `await` is based on TinyMCE init, // which should only cause an async delay on its first execution. So, we // should be OK for the rest of this function. // - // Now, make all decisions about `is_dirty`: if the text is dirty, do some + // Now, make all decisions about `isDirty`: if the text is dirty, do some // special processing; simply applying the update could cause either data // loss (overwriting edits made since the last autosave) or data corruption // (applying a diff to updated text, causing the diff to be mis-applied). @@ -263,8 +270,8 @@ const _open_lp = async ( // 2. In normal mode, we don't have a backup copy of the full text. // Report an `OutOfSync` error, which causes the IDE to send the full // text which will then overwrite changes made in the Client. - if (get_is_dirty() && is_re_translation) { - console_log(`Ignoring re-translation because Client is dirty.`); + if (getIsDirty() && isReTranslation) { + consoleLog(`Ignoring re-translation because Client is dirty.`); return; } @@ -283,32 +290,32 @@ const _open_lp = async ( const _editorMode: EditorMode = EditorMode[mode as keyof typeof EditorMode] ?? EditorMode.edit; - // Get the [current_metadata](#current_metadata) from the + // Get the [currentMetadata](#currentMetadata) from the // provided `code_chat_for_web` struct and store it as a global // variable. - current_metadata = codechat_for_web["metadata"]; - const source = codechat_for_web["source"]; - const codechat_body = document.getElementById("CodeChat-body"); - assert(codechat_body instanceof HTMLDivElement); - if (is_doc_only()) { + currentMetadata = codechatForWeb["metadata"]; + const source = codechatForWeb["source"]; + const codechatBody = document.getElementById("CodeChat-body"); + assert(codechatBody instanceof HTMLDivElement); + if (isDocOnly()) { // Per the // [docs](https://docs.mathjax.org/en/latest/web/typeset.html#updating-previously-typeset-content), // "If you modify the page to remove content that contains typeset // mathematics, you will need to tell MathJax about that so that it // knows the typeset math that you are removing is no longer on the // page." - window.MathJax?.typesetClear?.(codechat_body); + window.MathJax?.typesetClear?.(codechatBody); // Note that `==` is intentional: `null` (no editor instance) or // `undefined` (TinyMCE not loaded). - if (tinymce_instance() == null) { + if (tinymceInstance() == null) { // We shouldn't have a diff if the editor hasn't been // initialized. assert("Plain" in source); // Special case: a CodeChat Editor document's HTML is stored // in`source.doc`. We don't need the CodeMirror editor at all; // instead, treat it like a single doc block contents div. - doc_content = source.Plain.doc; - codechat_body.innerHTML = `

${doc_content}
`; + docContent = source.Plain.doc; + codechatBody.innerHTML = `
${docContent}
`; await init({ selector: ".CodeChat-doc-contents", // In the doc-only mode, add auto update functionality. @@ -321,14 +328,14 @@ const _open_lp = async ( setup: (editor: Editor) => { editor.on("Dirty", () => { if (!ignoreTinyMceDirty) { - set_is_dirty(true); + setIsDirty(true); startAutoUpdateTimer(); } }); editor.on("input", () => { ignoreTinyMceDirty = true; - set_is_dirty(true); + setIsDirty(true); startAutoUpdateTimer(); }); @@ -345,29 +352,29 @@ const _open_lp = async ( ); }, }); - tinymce_instance()!.focus(); + tinymceInstance()!.focus(); } else { // Save the cursor location before the update, then restore it // afterwards, if TinyMCE has focus. - const sel = tinymce_instance()!.hasFocus() + const sel = tinymceInstance()!.hasFocus() ? saveSelection() : undefined; - doc_content = + docContent = "Plain" in source ? source.Plain.doc - : apply_diff_str(doc_content, source.Diff.doc); - tinymce_instance()!.setContent(doc_content); + : applyDiffStr(docContent, source.Diff.doc); + tinymceInstance()!.setContent(docContent); if (sel !== undefined) { restoreSelection(sel); } } - await mathJaxTypeset(codechat_body); - scroll_to_line(cursor_position, scroll_line); + await mathJaxTypeset(codechatBody); + scrollToLine(cursorPosition, scrollLine); } else { - if (get_is_dirty() && "Diff" in source) { + if (getIsDirty() && "Diff" in source) { // Send an `OutOfSync` response, so that the IDE will send the // full text to overwrite these changes with. - webSocketComm().send_result( + webSocketComm().sendResult( // Pick a rarely-used ID, since we're not responding to a // specific message. 0, @@ -376,12 +383,12 @@ const _open_lp = async ( { OutOfSync: [0, 0] }, ); } else { - await CodeMirror_load( - codechat_body, - codechat_for_web, + await codeMirrorLoad( + codechatBody, + codechatForWeb, [], - cursor_position, - scroll_line, + cursorPosition, + scrollLine, ); } } @@ -392,7 +399,7 @@ const _open_lp = async ( // contents have been overwritten by contents from the IDE. By the same // reasoning, restart the auto update timer. clearAutoUpdateTimer(); - set_is_dirty(false); + setIsDirty(false); // If tests should be run, then the // [following global variable](CodeChatEditor-test.mts#CodeChatEditor_test) @@ -403,25 +410,25 @@ const _open_lp = async ( } }; -const save_lp = async ( - // Avoid relying on the global `is_dirty`, which may change during an +const saveLp = async ( + // Avoid relying on the global `isDirty`, which may change during an // `await`. - is_dirty_now: boolean, + isDirtyNow: boolean, ) => { const update: UpdateMessageContents = { // The Framework will fill in this value. file_path: "", is_re_translation: false, }; - if (is_doc_only()) { + if (isDocOnly()) { const location = saveSelection(); // If there's a selection (cursor location), send it to the server, // which will locate the corresponding line. - if (location.selection_offset !== undefined) { + if (location.selectionOffset !== undefined) { update.cursor_position = { DomLocation: { - dom_path: location.selection_path, - dom_offset: location.selection_offset, + dom_path: location.selectionPath, + dom_offset: location.selectionOffset, // Use this since it's a Markdown-only file; the server will // ignore this value. from: 0, @@ -429,48 +436,48 @@ const save_lp = async ( }; } } else { - set_CodeMirror_positions(update); + setCodeMirrorPositions(update); } // Add the contents only if the document is dirty. - if (is_dirty_now) { + if (isDirtyNow) { /// @ts-expect-error("Declare here; it will be completed later.") - let code_mirror_diffable: CodeMirrorDiffable = {}; - if (is_doc_only()) { + let codeMirrorDiffable: CodeMirrorDiffable = {}; + if (isDocOnly()) { // Untypeset all math before saving the document. - const codechat_body = document.getElementById("CodeChat-body"); - assert(codechat_body instanceof HTMLDivElement); - mathJaxUnTypeset(codechat_body); + const codechatBody = document.getElementById("CodeChat-body"); + assert(codechatBody instanceof HTMLDivElement); + mathJaxUnTypeset(codechatBody); // Use a try/finally to ensure that the document is retypeset even // if errors occur. try { // To save a document only, simply get the HTML from the only // Tiny MCE div. Update the `doc_contents` to stay in sync with // the Server. - doc_content = tinymce_instance()!.save({ format: "raw" }); + docContent = tinymceInstance()!.save({ format: "raw" }); // The `save()` flushes any duplicate `Dirty` events. After // this, following `Dirty` events are genuine. ignoreTinyMceDirty = false; ( - code_mirror_diffable as { + codeMirrorDiffable as { Plain: CodeMirror; } ).Plain = { - doc: doc_content, + doc: docContent, doc_blocks: [], }; } finally { // Retypeset all math after saving the document. - await mathJaxTypeset(codechat_body); + await mathJaxTypeset(codechatBody); } } else { - code_mirror_diffable = CodeMirror_save(); - assert("Plain" in code_mirror_diffable); + codeMirrorDiffable = codeMirrorSave(); + assert("Plain" in codeMirrorDiffable); } update.contents = { - metadata: current_metadata, + metadata: currentMetadata, version: rand(), - source: code_mirror_diffable, + source: codeMirrorDiffable, }; } @@ -484,100 +491,99 @@ export const saveSelection = () => { // 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`.) + // in this node is placed in `selectionOffset`.) const sel = window.getSelection(); - const selection_path = []; - const selection_offset = sel?.anchorOffset; + const selectionPath = []; + const selectionOffset = sel?.anchorOffset; if (sel?.anchorNode) { // Find a path from the selection back to the containing div. for ( - let current_node = sel.anchorNode; + let currentNode = sel.anchorNode; // Continue until we find the div which contains the doc block // contents: either it's not an element (such as a div), ... - !(current_node instanceof Element) || + !(currentNode instanceof Element) || // or it's not the doc block contents div. - (!current_node.classList.contains("CodeChat-doc-contents") && + (!currentNode.classList.contains("CodeChat-doc-contents") && // Sometimes, the parent of a custom node (`wc-mermaid`) skips // the TinyMCE div and returns the overall div. I don't know // why. - !current_node.classList.contains("CodeChat-doc")); - current_node = current_node.parentNode! + !currentNode.classList.contains("CodeChat-doc")); + currentNode = currentNode.parentNode! ) { // Store the index of this node in its' parent list of child // nodes/children. - const p = current_node.parentNode; + const p = currentNode.parentNode; // In case we go off the rails, give up if there are no more // parents. if (p === null) { return { - selection_path: [], - selection_offset: undefined, + selectionPath: [], + selectionOffset: undefined, }; } - selection_path.unshift( - Array.prototype.indexOf.call(p.childNodes, current_node), + selectionPath.unshift( + Array.prototype.indexOf.call(p.childNodes, currentNode), ); } } - return { selection_path, selection_offset }; + return { selectionPath, selectionOffset }; }; // Restore the selection produced by `saveSelection` to the active TinyMCE // instance. export const restoreSelection = ({ - selection_path, - selection_offset, + selectionPath, + selectionOffset, }: { - selection_path: number[]; - selection_offset?: number; + selectionPath: number[]; + selectionOffset?: 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: Node = - tinymce_instance()!.getContentAreaContainer(); - // Avoid mutating `selection_path` by making a copy of it. - const selection_path_copy = [...selection_path]; - while (selection_path_copy.length) { - const new_selection_node = - selection_node.childNodes[selection_path_copy.shift()!]; + if (selectionPath.length && typeof selectionOffset === "number") { + let selectionNode: Node = tinymceInstance()!.getContentAreaContainer(); + // Avoid mutating `selectionPath` by making a copy of it. + const selectionPathCopy = [...selectionPath]; + while (selectionPathCopy.length) { + const newSelectionNode = + selectionNode.childNodes[selectionPathCopy.shift()!]; // If we get lost during the descent, then stop just before that. - if (!(new_selection_node instanceof Node)) { + if (!(newSelectionNode instanceof Node)) { break; } - selection_node = new_selection_node; + selectionNode = newSelectionNode; } // In case of edits, avoid an offset past the end of the node. Note that // the maximum value is `length`, not `length - 1`, which represents a // selection at the very end of the text node. - const final_selection_offset = Math.min( - selection_offset, - selection_node.nodeValue?.length ?? 0, + const finalSelectionOffset = Math.min( + selectionOffset, + selectionNode.nodeValue?.length ?? 0, ); // Use that to set the selection. - tinymce_instance()!.selection.setCursorLocation( - selection_node, - final_selection_offset, + tinymceInstance()!.selection.setCursorLocation( + selectionNode, + finalSelectionOffset, ); } }; // Save CodeChat Editor contents if dirty; send the current selection and scroll // position. -const send_update = async (only_if_dirty: boolean = false) => { - if (only_if_dirty && !is_dirty) { +const sendUpdate = async (onlyIfDirty: boolean = false) => { + if (onlyIfDirty && !isDirty) { return; } clearAutoUpdateTimer(); // Save the provided contents back to the filesystem, by // sending an update message over the websocket. - console_log( + consoleLog( "CodeChat Editor Client: sent Update - saving document/updating cursor location.", ); - // Don't wait for a response to change `is_dirty`; this boogers up logic. - webSocketComm().send_message({ Update: await save_lp(is_dirty) }); - is_dirty = false; + // Don't wait for a response to change `isDirty`; this boogers up logic. + webSocketComm().sendMessage({ Update: await saveLp(isDirty) }); + isDirty = false; }; // ### Auto update feature @@ -591,9 +597,9 @@ export const startAutoUpdateTimer = () => { clearAutoUpdateTimer(); // ...then start another timeout which updates the document when it expires. autoUpdateTimeoutId = window.setTimeout(() => { - console_log("CodeChat Editor Client: auto updating."); - send_update(); - }, auto_update_timeout_ms); + consoleLog("CodeChat Editor Client: auto updating."); + sendUpdate(); + }, autoUpdateTimeoutMs); }; const clearAutoUpdateTimer = () => { @@ -608,7 +614,7 @@ const clearAutoUpdateTimer = () => { // // The TOC and this page calls this when a hyperlink is clicked. This saves the // current document before navigating. -const on_navigate = (navigateEvent: NavigateEvent) => { +const onNavigate = (navigateEvent: NavigateEvent) => { if ( // Some of this was copied from // [Modern client-side routing: the Navigation API](https://developer.chrome.com/docs/web-platform/navigation-api/#deciding_how_to_handle_a_navigation). @@ -638,14 +644,14 @@ const on_navigate = (navigateEvent: NavigateEvent) => { // Intercept this navigation so we can save the document first. navigateEvent.intercept(); - console_log("CodeChat Editor Client: saving document before navigation."); - save_then_navigate(new URL(navigateEvent.destination.url)); + consoleLog("CodeChat Editor Client: saving document before navigation."); + saveThenNavigate(new URL(navigateEvent.destination.url)); }; // This is able to intercept clicks on links that the Navigation API doesn't, // specifically those that TinyMCE generates (since they're always set to open // in a new tab). -const on_click = (event: MouseEvent) => { +const onClick = (event: MouseEvent) => { // TinyMCE by default tries to open all links in a new tab. Look for and fix // these. if ( @@ -662,7 +668,7 @@ const on_click = (event: MouseEvent) => { // being edited in the CodeChat Editor. If two tabs are open, which // is the current file for the IDE? event.preventDefault(); - save_then_navigate(new URL(url)); + saveThenNavigate(new URL(url)); } else { // This is navigation to some external link. Let that proceed // without interruption in a pure browser environment. However, @@ -672,7 +678,7 @@ const on_click = (event: MouseEvent) => { // requested link. if (window.location.pathname.startsWith("/vsc")) { event.preventDefault(); - parent.window.CodeChatEditorFramework.webSocketComm.send_message( + parent.window.CodeChatEditorFramework.webSocketComm.sendMessage( { OpenUrl: url }, ); } @@ -681,43 +687,40 @@ const on_click = (event: MouseEvent) => { }; // Save the current document, then navigate to the provided URL, which must be a // reference to another CodeChat Editor document. -const save_then_navigate = (codeChatEditorUrl: URL) => { +const saveThenNavigate = (codeChatEditorUrl: URL) => { const navigate = () => { // Avoid recursion! - window.navigation.removeEventListener("navigate", on_navigate); - parent.window.CodeChatEditorFramework.webSocketComm.current_file( + window.navigation.removeEventListener("navigate", onNavigate); + parent.window.CodeChatEditorFramework.webSocketComm.currentFile( codeChatEditorUrl, ); }; // Navigate after the save completes. If the save fails, still navigate -- // otherwise the user is stranded on the current page with only a generic // error toast -- but report the failure so the lost save isn't silent. - send_update(true).then(navigate, (reason) => { - show_toast(`Error saving before navigation: ${reason}`); + sendUpdate(true).then(navigate, (reason) => { + showToast(`Error saving before navigation: ${reason}`); navigate(); }); }; // This can be called by the framework. Therefore, make no assumptions about // variables being valid; it be called before a file is loaded, etc. -const scroll_to_line = ( - cursor_position?: CursorPosition, - scroll_line?: number, -) => { - if (is_doc_only()) { +const scrollToLine = (cursorPosition?: CursorPosition, scrollLine?: number) => { + if (isDocOnly()) { // TODO. } else { - codemirror_scroll_to_line(cursor_position, scroll_line); + codemirrorScrollToLine(cursorPosition, scrollLine); } }; // A global error handler: this is called on any uncaught exception. -export const on_error = (event: Event) => { - let err_str: string; +export const onError = (event: Event) => { + let errStr: string; if (event instanceof ErrorEvent) { - err_str = `${event.filename}:${event.lineno}: ${event.message}`; + errStr = `${event.filename}:${event.lineno}: ${event.message}`; if (event.error?.stack) { - err_str += `\n${event.error.stack}`; + errStr += `\n${event.error.stack}`; } } else if (event instanceof PromiseRejectionEvent) { const reason = event.reason; @@ -726,45 +729,45 @@ export const on_error = (event: Event) => { // A simple `reason instanceof Error` fails here. Better would be // [Error.isError()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/isError), // but this requires es2027. - if (typeof reason.message === "string") { + if (typeof reason?.message === "string") { // Extracts the text from `reject(new Error('Your text'))`. userMessage = reason.message; } else if (typeof reason === "string") { // Extracts the text from `reject('Your text')`. userMessage = reason; } - err_str = `Promise rejected: ${userMessage}`; + errStr = `Promise rejected: ${userMessage}`; if (reason instanceof Error && reason.stack) { - err_str += `\n${reason.stack}`; + errStr += `\n${reason.stack}`; } } else { - err_str = `Unexpected error ${typeof event}: ${event}`; + errStr = `Unexpected error ${typeof event}: ${event}`; } - show_toast(err_str); + showToast(errStr); console.error(event); }; // Load the dynamic content into the static page. Place this last, since we need // functions above defined before assigning them to the `CodeChatEditor` // namespace. -on_dom_content_loaded(async () => { +onDomContentLoaded(async () => { // Intercept links in this document to save before following the link. - window.navigation.addEventListener("navigate", on_navigate); + window.navigation.addEventListener("navigate", onNavigate); const ccb = document.getElementById("CodeChat-sidebar"); if (ccb instanceof HTMLIFrameElement) { - ccb.contentWindow?.navigation.addEventListener("navigate", on_navigate); + ccb.contentWindow?.navigation.addEventListener("navigate", onNavigate); } - document.addEventListener("click", on_click); + document.addEventListener("click", onClick); // Provide basic error reporting for uncaught errors. - window.addEventListener("unhandledrejection", on_error); - window.addEventListener("error", on_error); + window.addEventListener("unhandledrejection", onError); + window.addEventListener("error", onError); window.CodeChatEditor = { - do_debug, - open_lp, - send_update, - scroll_to_line, - show_toast, + doDebug, + openLp, + sendUpdate, + scrollToLine, + showToast, allow_navigation: false, }; }); diff --git a/client/src/CodeChatEditorFramework.mts b/client/src/CodeChatEditorFramework.mts index 01f7b63a..7a0ce102 100644 --- a/client/src/CodeChatEditorFramework.mts +++ b/client/src/CodeChatEditorFramework.mts @@ -28,12 +28,12 @@ // // ### Third-party import ReconnectingWebSocket from "./third-party/ReconnectingWebSocket.cjs"; -import { show_toast as show_toast_core } from "./show_toast.mjs"; +import { showToast as showToastCore } from "./show_toast.mjs"; // ### Local import { assert } from "./assert.mjs"; import { - console_log, + consoleLog, DEBUG_ENABLED, MAX_MESSAGE_LENGTH, } from "./debug_enabled.mjs"; @@ -45,7 +45,7 @@ import { MessageResult, UpdateMessageContents, } from "./shared.mjs"; -import { on_error, on_dom_content_loaded } from "./CodeChatEditor.mjs"; +import { onError, onDomContentLoaded } from "./CodeChatEditor.mjs"; import { ResultErrTypes } from "./rust-types/ResultErrTypes.js"; import { CursorPosition } from "./rust-types/CursorPosition.js"; @@ -64,24 +64,24 @@ let webSocketComm: WebSocketComm; class WebSocketComm { // Use a unique ID for each websocket message sent. See the Implementation // section on Message IDs for more information. - ws_id = 4; + wsId = 4; // The websocket used by this class. Really a `ReconnectingWebSocket`, but // that's not a type. ws: WebSocket; // A map of message id to (timer id, callback) for all pending messages. - pending_messages: Record< + pendingMessages: Record< number, { - timer_id: number; + timerId: number; callback: () => void; } > = {}; // The current filename of the file being edited. This is provided by the // IDE and passed back to it, but not otherwise used by the Framework. - current_filename: string | undefined = undefined; + currentFilename: string | undefined = undefined; // The version number of the current file. This default value will be // overwritten when the first `Update` is sent. @@ -90,7 +90,7 @@ class WebSocketComm { // 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. - is_loading = false; + isLoading = false; // A promise to serialize calls to and from the Client. This is important: a // `CurrentFile` requires the Client to save, then switch to a new web page. @@ -98,73 +98,84 @@ class WebSocketComm { // finished executing. promise = Promise.resolve(); - constructor(ws_url: string) { + constructor(wsUrl: string) { // The `ReconnectingWebSocket` doesn't provide ALL the `WebSocket` // methods. Ignore this, since we can't use `ReconnectingWebSocket` as a // type. /// @ts-expect-error("This is legacy, third-party code.") - this.ws = new ReconnectingWebSocket!(ws_url); + this.ws = new ReconnectingWebSocket!(wsUrl); // Identify this client on connection. this.ws.onopen = () => { - console_log( + consoleLog( `CodeChat Editor Client: websocket to CodeChat Server open.`, ); }; // Provide logging to help track down errors. this.ws.onerror = (event: Event) => { - report_error(`CodeChat Editor Client: websocket error.`, event); + reportError(`CodeChat Editor Client: websocket error.`, event); }; this.ws.onclose = (event: CloseEvent) => { - console_log( + consoleLog( `CodeChat Editor Client: websocket ${event.wasClean ? "" : "*NOT*"} cleanly closed ${event.reason}. This should only happen on shutdown.`, ); - console_log(event); + consoleLog(event); }; // Handle websocket messages. this.ws.onmessage = (event: MessageEvent) => { // Parse the received message, which must be a single element of a // dictionary representing an `EditorMessage`. - const joint_message = JSON.parse(event.data) as EditorMessage; - const { id, message } = joint_message; - console_log( - `CodeChat Editor Client: received data id = ${id}, message = ${format_struct(message)}`, + const jointMessage = JSON.parse(event.data) as EditorMessage; + const { id, message } = jointMessage; + consoleLog( + `CodeChat Editor Client: received data id = ${id}, message = ${formatStruct(message)}`, ); assert(id !== undefined); assert(message !== undefined); - const keys = Object.keys(message); - assert(keys.length === 1); - const key = keys[0] as KeysOfRustEnum; - const value = Object.values(message)[0]; + // A Rust enum variant with no data (e.g. `RequestClose`) is + // serialized as a bare string rather than a single-key object; a + // variant with data is serialized as a single-key object. See + // `KeysOfRustEnum` for the same convention used by its type. + let key: KeysOfRustEnum; + let value: unknown; + if (typeof message === "string") { + key = message as KeysOfRustEnum; + value = undefined; + } else { + const keys = Object.keys(message); + assert(keys.length === 1); + key = keys[0] as KeysOfRustEnum; + value = Object.values(message)[0]; + } // Process this message. switch (key) { case "Update": { // Load this data in. - const current_update = value as UpdateMessageContents; + const currentUpdate = value as UpdateMessageContents; // The rest of this should run after all other messages have // been processed. this.promise = this.promise.finally(async () => { - // Check or update the `current_filename`. - if (this.current_filename === undefined) { - this.current_filename = current_update.file_path; + // Check or update the `currentFilename`. + if (this.currentFilename === undefined) { + this.currentFilename = currentUpdate.file_path; } else if ( - current_update.file_path !== this.current_filename + currentUpdate.file_path !== this.currentFilename ) { - 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, { + const msg = `Ignoring update for ${currentUpdate.file_path} because it's not the current file ${this.currentFilename}.`; + reportError(msg); + this.sendResult(id, { IgnoredUpdate: [ - current_update.file_path, - this.current_filename, + currentUpdate.file_path, + this.currentFilename, ], }); return; } - const contents = current_update.contents; - const cursor_position = current_update.cursor_position; + const contents = currentUpdate.contents; + const cursorPosition = currentUpdate.cursor_position; if (contents !== undefined) { // Check and update the version. If this is a diff, // ensure the diff was made against the version of @@ -174,15 +185,15 @@ class WebSocketComm { contents.source.Diff.version !== this.version ) { - if (current_update.is_re_translation) { - console_log( + if (currentUpdate.is_re_translation) { + consoleLog( `Ignoring out-of-sync re-translation update.`, ); } else { - report_error( + reportError( `Out of sync: Client version ${this.version} !== incoming version ${contents.source.Diff.version}.`, ); - this.send_result(id, { + this.sendResult(id, { OutOfSync: [ this.version, contents.source.Diff.version, @@ -200,15 +211,15 @@ class WebSocketComm { // load of the iframe; it doesn't change when the // iframe's `src` attribute is changed. So, we have // to track this manually instead. - if (!this.is_loading) { + if (!this.isLoading) { // Wait until after the DOM is ready, since we // rely on content set in - // `on_dom_content_loaded` in the Client. - await set_content( + // `onDomContentLoaded` in the Client. + await setContent( contents, - current_update.is_re_translation, - current_update.cursor_position, - current_update.scroll_position, + currentUpdate.is_re_translation, + currentUpdate.cursor_position, + currentUpdate.scroll_position, ); } else { // If the page is still loading, wait until the @@ -219,13 +230,13 @@ class WebSocketComm { // `onload` callback to be set immediately. await new Promise( (resolve) => - (root_iframe!.onload = async () => { - this.is_loading = false; - await set_content( + (rootIframe!.onload = async () => { + this.isLoading = false; + await setContent( contents, - current_update.is_re_translation, - current_update.cursor_position, - current_update.scroll_position, + currentUpdate.is_re_translation, + currentUpdate.cursor_position, + currentUpdate.scroll_position, ); resolve(); }), @@ -233,15 +244,15 @@ class WebSocketComm { } } else { // We might receive a message while the Client is - // reloading; during this period, `scroll_to_line` + // reloading; during this period, `scrollToLine` // isn't defined. - root_iframe!.contentWindow?.CodeChatEditor?.scroll_to_line?.( - cursor_position, - current_update.scroll_position, + rootIframe!.contentWindow?.CodeChatEditor?.scrollToLine?.( + cursorPosition, + currentUpdate.scroll_position, ); } - this.send_result(id); + this.sendResult(id); }); break; } @@ -249,11 +260,11 @@ class WebSocketComm { case "CurrentFile": { // Note that we can ignore `value[1]` (if the file is text // or binary); the server only sends text files here. - const current_file = value[0] as string; + const currentFile = (value as [string, boolean | null])[0]; const testSuffix = testMode ? // Append the test parameter correctly, depending if // there are already parameters or not. - current_file.indexOf("?") === -1 + currentFile.indexOf("?") === -1 ? "?test" : "&test" : ""; @@ -262,22 +273,22 @@ class WebSocketComm { this.promise = this.promise.finally(async () => { // If the page is still loading, then don't save. // Otherwise, save the editor contents if necessary. - const cce = get_client(); - await cce?.send_update(true); + const cce = getClient(); + await cce?.sendUpdate(true); // Now, it's safe to load a new file. Tell the client to // allow this navigation -- the document it contains has // already been saved. if (cce !== undefined) { cce.allow_navigation = true; } - this.set_root_iframe_src(current_file + testSuffix); - // The `current_file` is a URL-encoded path, not a + this.setRootIframeSrc(currentFile + testSuffix); + // The `currentFile` is a URL-encoded path, not a // filesystem path. So, we can't use it for - // `current_filename`. Instead, signal that the - // `current_filename` should be set on the next `Update` + // `currentFilename`. Instead, signal that the + // `currentFilename` should be set on the next `Update` // message. - this.current_filename = undefined; - this.send_result(id); + this.currentFilename = undefined; + this.sendResult(id); }); break; } @@ -286,37 +297,37 @@ class WebSocketComm { // If the result has the magic ID, then call a debug // routine. if (id === 1e6 && DEBUG_ENABLED) { - root_iframe!.contentWindow?.CodeChatEditor?.do_debug(); + rootIframe!.contentWindow?.CodeChatEditor?.doDebug(); break; } // Cancel the timer for this message and remove it from - // `pending_messages`. - const pending_message = this.pending_messages[id]; - if (pending_message !== undefined) { - const { timer_id, callback } = pending_message; - clearTimeout(timer_id); + // `pendingMessages`. + const pendingMessage = this.pendingMessages[id]; + if (pendingMessage !== undefined) { + const { timerId, callback } = pendingMessage; + clearTimeout(timerId); callback(); - delete this.pending_messages[id]; + delete this.pendingMessages[id]; } // Report if this was an error. - const result_contents = value as MessageResult; - if ("Err" in result_contents) { - report_error( - `Error in message ${id}: ${JSON.stringify(result_contents.Err)}.`, - result_contents.Err, + const resultContents = value as MessageResult; + if ("Err" in resultContents) { + reportError( + `Error in message ${id}: ${JSON.stringify(resultContents.Err)}.`, + resultContents.Err, ); } break; } default: { - const msg = `Received unhandled message ${key}(${format_struct( + const msg = `Received unhandled message ${key}(${formatStruct( value, )})`; - report_error(msg); - this.send_result(id, { - ClientIllegalMessageReceived: `${key}(${format_struct( + reportError(msg); + this.sendResult(id, { + ClientIllegalMessageReceived: `${key}(${formatStruct( value, )})`, }); @@ -330,50 +341,50 @@ class WebSocketComm { /*eslint-disable-next-line @typescript-eslint/no-explicit-any */ close = (...args: any) => this.ws.close(...args); - set_root_iframe_src = (url: string) => { + setRootIframeSrc = (url: string) => { // Set the new src to (re)load content. At startup, the `srcdoc` // attribute shows some welcome text. Remove it so that we can now // assign the `src` attribute. - root_iframe!.removeAttribute("srcdoc"); - root_iframe!.src = url; - // Track the `is_loading` status. - this.is_loading = true; - root_iframe!.onload = () => (this.is_loading = false); + rootIframe!.removeAttribute("srcdoc"); + rootIframe!.src = url; + // Track the `isLoading` status. + this.isLoading = true; + rootIframe!.onload = () => (this.isLoading = false); }; // Report an error from the server. - report_server_timeout = (message_id: number) => { - delete this.pending_messages[message_id]; - report_error(`Error: server timeout for message id ${message_id}`); + reportServerTimeout = (messageId: number) => { + delete this.pendingMessages[messageId]; + reportError(`Error: server timeout for message id ${messageId}`); }; // Send a message expecting a result to the server. - send_message = ( + sendMessage = ( message: EditorMessageContents, callback: () => void = () => 0, ) => { - const id = this.ws_id; + const id = this.wsId; // The Client gets every third ID -- the IDE gets another third, while // the Server gets the final third. - this.ws_id += 3; + this.wsId += 3; // Add in the current filename to the message, if it's an `Update`. if (typeof message == "object" && "Update" in message) { - assert(this.current_filename !== undefined); - message.Update.file_path = this.current_filename!; + assert(this.currentFilename !== undefined); + message.Update.file_path = this.currentFilename!; // 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)}`, + consoleLog( + `CodeChat Editor Client: sent message ${id}, ${formatStruct(message)}`, ); const jm: EditorMessage = { id: id, message: message, }; this.ws.send(JSON.stringify(jm)); - this.pending_messages[id] = { - timer_id: window.setTimeout( - this.report_server_timeout, + this.pendingMessages[id] = { + timerId: window.setTimeout( + this.reportServerTimeout, RESPONSE_TIMEOUT_MS, id, ), @@ -382,7 +393,7 @@ class WebSocketComm { }; // This is called by the Client when the user navigates to another webpage. - current_file = (url: URL) => { + currentFile = (url: URL) => { // TODO: should we delay execution of user navigation until all previous // actions have finished, or ignore them and immediately perform the // user navigation? @@ -390,31 +401,31 @@ class WebSocketComm { if (url.host === window.location.host) { // If this points to the Server, then tell the IDE to load a new // file. - this.send_message( + this.sendMessage( { CurrentFile: [url.toString(), null] }, () => { - this.set_root_iframe_src(url.toString()); + this.setRootIframeSrc(url.toString()); }, ); } else { // Otherwise, navigate to the provided page. - this.set_root_iframe_src(url.toString()); + this.setRootIframeSrc(url.toString()); } - // Read the `current_filename` from the next `Update` message. - this.current_filename = undefined; + // Read the `currentFilename` from the next `Update` message. + this.currentFilename = undefined; }); }; // Send a result (a response to a message from the server) back to the // server. - send_result = (id: number, result?: ResultErrTypes) => { + sendResult = (id: number, result?: ResultErrTypes) => { const message: EditorMessageContents = { Result: result === undefined ? { Ok: "Void" } : { Err: result }, }; - console_log( - `CodeChat Editor Client: sending result id = ${id}, message = ${format_struct(message)}`, + consoleLog( + `CodeChat Editor Client: sending result id = ${id}, message = ${formatStruct(message)}`, ); - // We can't simply call `send_message` because that function expects a + // We can't simply call `sendMessage` because that function expects a // result message back from the server. const jm: EditorMessage = { id, @@ -424,74 +435,74 @@ class WebSocketComm { }; } -// Return the `CodeChatEditor` object if the `root_iframe` contains the Client; +// Return the `CodeChatEditor` object if the `rootIframe` contains the Client; // otherwise, this is `undefined`. -const get_client = () => root_iframe?.contentWindow?.CodeChatEditor; +const getClient = () => rootIframe?.contentWindow?.CodeChatEditor; // Assign content to either the Client (if it's loaded) or the webpage (if not) -// in the `root_iframe`. -const set_content = async ( +// in the `rootIframe`. +const setContent = async ( contents: CodeChatForWeb, - is_re_translation: boolean, - cursor_position?: CursorPosition, - scroll_line?: number, + isReTranslation: boolean, + cursorPosition?: CursorPosition, + scrollLine?: number, ) => { - const client = get_client(); + const client = getClient(); if (client === undefined) { // See if this is the [simple viewer](#Client-simple-viewer). Otherwise, // it's just the bare document to replace. const contentsElement = - root_iframe!.contentDocument?.getElementById("CodeChat-contents"); + rootIframe!.contentDocument?.getElementById("CodeChat-contents"); const cw = (contentsElement instanceof HTMLIFrameElement ? contentsElement.contentWindow - : undefined) ?? root_iframe!.contentWindow!; + : undefined) ?? rootIframe!.contentWindow!; cw.document.open(); assert("Plain" in contents.source); cw.document.write(contents.source.Plain.doc); cw.document.close(); } else { - await root_iframe!.contentWindow!.CodeChatEditor.open_lp( + await rootIframe!.contentWindow!.CodeChatEditor.openLp( contents, - is_re_translation, - cursor_position, - scroll_line, + isReTranslation, + cursorPosition, + scrollLine, ); } }; // The iframe element which composes this page. -let root_iframe: HTMLIFrameElement | undefined; +let rootIframe: HTMLIFrameElement | undefined; // True when in test mode. let testMode = false; // Load the dynamic content into the static page. -export const page_init = ( +export const pageInit = ( // The pathname for the websocket to use. The remainder of the URL is // derived from the hosting page's URL. See the // [Location docs](https://developer.mozilla.org/en-US/docs/Web/API/Location) // for a nice, interactive definition of the components of a URL. - ws_pathname: string, + wsPathname: string, // Test mode flag testMode_: boolean, ) => { testMode = testMode_; - on_dom_content_loaded(() => { + onDomContentLoaded(() => { // Provide basic error reporting for uncaught errors. - window.addEventListener("unhandledrejection", on_error); - window.addEventListener("error", on_error); + window.addEventListener("unhandledrejection", onError); + window.addEventListener("error", onError); // If the hosting page uses HTTPS, then use a secure websocket (WSS // protocol); otherwise, use an insecure websocket (WS). const protocol = window.location.protocol === "http:" ? "ws:" : "wss:"; // Build a websocket address based on the URL of the current page. webSocketComm = new WebSocketComm( - `${protocol}//${window.location.host}/${ws_pathname}`, + `${protocol}//${window.location.host}/${wsPathname}`, ); - const iframe_element = document.getElementById("CodeChat-iframe"); - assert(iframe_element instanceof HTMLIFrameElement); - root_iframe = iframe_element; + const iframeElement = document.getElementById("CodeChat-iframe"); + assert(iframeElement instanceof HTMLIFrameElement); + rootIframe = iframeElement; window.CodeChatEditorFramework = { webSocketComm, }; @@ -508,29 +519,26 @@ declare global { } } -const show_toast = (text: string) => { - if (get_client() === undefined) { - show_toast_core(text); +const showToast = (text: string) => { + if (getClient() === undefined) { + showToastCore(text); } else { - root_iframe!.contentWindow!.CodeChatEditor.show_toast(text); + rootIframe!.contentWindow!.CodeChatEditor.showToast(text); } }; // Format a complex data structure as a string when in debug mode. /*eslint-disable-next-line @typescript-eslint/no-explicit-any */ -export const format_struct = (complex_data_structure: any): string => +export const formatStruct = (complexDataStructure: any): string => DEBUG_ENABLED - ? JSON.stringify(complex_data_structure).substring( - 0, - MAX_MESSAGE_LENGTH, - ) + ? JSON.stringify(complexDataStructure).substring(0, MAX_MESSAGE_LENGTH) : ""; /*eslint-disable-next-line @typescript-eslint/no-explicit-any */ -const report_error = (text: string, ...objs: any) => { +const reportError = (text: string, ...objs: any) => { console.error(text); if (objs.length > 0) { console.log(...objs); } - show_toast(text); + showToast(text); }; diff --git a/client/src/CodeMirror-integration.mts b/client/src/CodeMirror-integration.mts index ddb86c62..8b479561 100644 --- a/client/src/CodeMirror-integration.mts +++ b/client/src/CodeMirror-integration.mts @@ -78,11 +78,11 @@ import type { Editor, EditorEvent, Events } from "tinymce"; // ### Local import { - set_is_dirty, + setIsDirty, startAutoUpdateTimer, saveSelection, restoreSelection, - tinymce_instance, + tinymceInstance, tinymce, init, } from "./CodeChatEditor.mjs"; @@ -95,14 +95,14 @@ import { UpdateMessageContents, } from "./shared.mjs"; import { assert } from "./assert.mjs"; -import { show_toast } from "./show_toast.mjs"; +import { showToast } from "./show_toast.mjs"; import { CursorPosition } from "./rust-types/CursorPosition"; // Globals // ------- -let current_view: EditorView; -// This indicates that a call to `on_dirty` is scheduled, but hasn't run yet. -let on_dirty_scheduled = false; +let currentView: EditorView; +// This indicates that a call to `onDirty` is scheduled, but hasn't run yet. +let onDirtyScheduled = false; // This set when an `input` event occurs, which usually produces a duplicate // `Dirty` event which should be ignored. let ignoreTinyMceDirty = false; @@ -140,7 +140,7 @@ const stayInCodeBlockAnnotation = Annotation.define(); // Define a facet called when extensions produce an error. const exceptionSink = EditorView.exceptionSink.of((exception) => { - show_toast(`Error: ${exception}`); + showToast(`Error: ${exception}`); console.error(exception); }); @@ -176,14 +176,14 @@ export const docBlockField = StateField.define({ // [Update](https://codemirror.net/docs/ref/#state.StateField^define^config.update) // computes a new value for this field from the field's previous value and // the provided transaction. - update(doc_blocks: DecorationSet, tr: Transaction) { + update(docBlocks: DecorationSet, tr: Transaction) { // If there's a freeze annotation, then ignore the mapping update. if (tr.annotation(docBlockFreezeAnnotation) === undefined) { // [Map](https://codemirror.net/docs/ref/#state.RangeSet.map) these // changes through the provided transaction, which updates the // offsets of the range so the doc blocks is still anchored to the // same location in the document after this transaction completes. - doc_blocks = doc_blocks.map(tr.changes); + docBlocks = docBlocks.map(tr.changes); } // See [is](https://codemirror.net/docs/ref/#state.StateEffect.is). Add // a doc block, as requested by this effect. @@ -194,12 +194,12 @@ export const docBlockField = StateField.define({ .slice(effect.value.from, effect.value.to) .toString(); if (newlines !== "\n".repeat(newlines.length)) { - halt_on_error(`Attempt to overwrite text: "${newlines}".`); + haltOnError(`Attempt to overwrite text: "${newlines}".`); } // Perform an // [update](https://codemirror.net/docs/ref/#state.RangeSet.update) // by adding the requested doc block. - doc_blocks = doc_blocks.update({ + docBlocks = docBlocks.update({ // See // [updateSpec](https://codemirror.net/docs/ref/#state.RangeSet.update^updateSpec) add: [ @@ -231,10 +231,10 @@ export const docBlockField = StateField.define({ // provided, so don't use it. let prev: Decoration | undefined; let to: number | undefined; - doc_blocks.between( + docBlocks.between( effect.value.from, effect.value.from, - (from, to_found, value) => { + (from, toFound, value) => { // Only look for blocks whose from is as specified. // `between` will also return blocks whose to matches -- // for example, given from = 1, one doc block of \[0, @@ -244,13 +244,13 @@ export const docBlockField = StateField.define({ // For the given `from`, there should be exactly one // doc block. if (prev !== undefined) { - console.error({ doc_blocks, effect }); - halt_on_error( + console.error({ docBlocks, effect }); + haltOnError( "More than one doc block at one location found.", ); } prev = value; - to = to_found; + to = toFound; // We could return `false` here to stop the search // for efficiency. However, we let it continue in @@ -260,8 +260,8 @@ export const docBlockField = StateField.define({ }, ); if (prev === undefined) { - console.error({ doc_blocks, effect }); - halt_on_error("No doc block found."); + console.error({ docBlocks, effect }); + haltOnError("No doc block found."); assert(false); } // Determine the final from/to values. @@ -270,11 +270,11 @@ export const docBlockField = StateField.define({ // Check that we're not overwriting text. const newlines = tr.newDoc.slice(from, to).toString(); if (newlines !== "\n".repeat(newlines.length)) { - halt_on_error(`Attempt to overwrite text: "${newlines}".`); + haltOnError(`Attempt to overwrite text: "${newlines}".`); } - const prev_widget = prev.spec.widget; - assert(prev_widget instanceof DocBlockWidget); - doc_blocks = doc_blocks.update({ + const prevWidget = prev.spec.widget; + assert(prevWidget instanceof DocBlockWidget); + docBlocks = docBlocks.update({ // Remove the old doc block. We assume there's only one // block in the provided from/to range. filter: (from, _to, _value) => from !== effect.value.from, @@ -285,12 +285,12 @@ export const docBlockField = StateField.define({ add: [ Decoration.replace({ widget: new DocBlockWidget( - effect.value.indent ?? prev_widget.indent, - effect.value.delimiter ?? prev_widget.delimiter, + effect.value.indent ?? prevWidget.indent, + effect.value.delimiter ?? prevWidget.delimiter, typeof effect.value.contents === "string" ? effect.value.contents - : apply_diff_str( - prev_widget.contents, + : applyDiffStr( + prevWidget.contents, effect.value.contents, ), // If autosave is allowed (meaning no autosave @@ -303,13 +303,13 @@ export const docBlockField = StateField.define({ ], }); } else if (effect.is(deleteDocBlock)) { - doc_blocks = doc_blocks.update({ + docBlocks = docBlocks.update({ filter: (from, _to, _value) => from !== effect.value.from, filterFrom: effect.value.from, filterTo: effect.value.from, }); } - return doc_blocks; + return docBlocks; }, // Register this `DecorationSet` as a source of decorations for the editor @@ -327,11 +327,11 @@ export const docBlockField = StateField.define({ // contents (including these doc blocks) to JSON, which can then be sent // back to the server for reassembly into a source file. toJSON: (value: DecorationSet, _state: EditorState) => { - const json_result = []; + const jsonResult = []; for (const iter = value.iter(); iter.value !== null; iter.next()) { const w = iter.value.spec.widget; assert(w instanceof DocBlockWidget); - json_result.push([ + jsonResult.push([ iter.from, iter.to, w.indent, @@ -339,7 +339,7 @@ export const docBlockField = StateField.define({ w.contents, ]); } - return json_result; + return jsonResult; }, // For loading a file from the server back into the editor, use @@ -367,7 +367,7 @@ export const docBlockField = StateField.define({ ), }); -export const CodeMirror_JSON_fields = { doc_blocks: docBlockField }; +export const codeMirrorJsonFields = { doc_blocks: docBlockField }; // Per the [docs](https://codemirror.net/docs/ref/#state.StateEffect^define), // "State effects can be used to represent additional effects associated with a @@ -393,7 +393,7 @@ export const addDocBlock = StateEffect.define<{ }), }); -type updateDocBlockType = { +type UpdateDocBlockType = { from: number; from_new?: number; to?: number; @@ -403,12 +403,12 @@ type updateDocBlockType = { }; // Define an update. -export const updateDocBlock = StateEffect.define({ +export const updateDocBlock = StateEffect.define({ map: ( { from, from_new: fromNew, to, indent, delimiter, contents }, change: ChangeDesc, ) => { - const ret: updateDocBlockType = { + const ret: UpdateDocBlockType = { // Update the position of this doc block due to the transaction's // changes. from: change.mapPos(from), @@ -442,7 +442,7 @@ class DocBlockWidget extends WidgetType { readonly indent: string, readonly delimiter: string, readonly contents: string, - readonly is_user_change: boolean, + readonly isUserChange: boolean, ) { // [Typescript parameter properties](https://www.typescriptlang.org/docs/handbook/2/classes.html#parameter-properties) // means these parameters are automatically promoted to class @@ -453,7 +453,7 @@ class DocBlockWidget extends WidgetType { eq(other: DocBlockWidget) { // Order these to do the fastest comparisons first. return ( - other.is_user_change == this.is_user_change && + other.isUserChange == this.isUserChange && other.delimiter === this.delimiter && other.indent === this.indent && other.contents === this.contents @@ -505,8 +505,8 @@ class DocBlockWidget extends WidgetType { updateDOM(dom: HTMLElement, _view: EditorView): boolean { // If this change was produced by a user edit and the DOM to "update" is // a TinyMCE editor, then the DOM was already updated. Stop here. - const [contents_div, is_tinymce] = get_contents(dom); - if (this.is_user_change && is_tinymce) { + const [contentsDiv, isTinymce] = getContents(dom); + if (this.isUserChange && isTinymce) { return true; } @@ -514,10 +514,10 @@ class DocBlockWidget extends WidgetType { // sanitized: the server only allows whitespace for the indent; only // specific, safe delimiters are allowed. The Client only allows editing // the indent, and only whitespace is allowed there as well. - const dom_indent = dom.childNodes[0]; - assert(dom_indent instanceof HTMLDivElement); - dom_indent.innerHTML = this.indent; - dom_indent.dataset.delimiter = this.delimiter; + const domIndent = dom.childNodes[0]; + assert(domIndent instanceof HTMLDivElement); + domIndent.innerHTML = this.indent; + domIndent.dataset.delimiter = this.delimiter; // Update the contents. The contents div could be a TinyMCE instance, or // just a plain div. Handle both cases. Again, we assume sanitized @@ -525,21 +525,21 @@ class DocBlockWidget extends WidgetType { // TinyMCE (which uses a // [sanitizer](https://www.tiny.cloud/docs/tinymce/latest/security/#sanitizing-html-input-to-protect-against-xss-attacks) // for all user input). - window.MathJax?.typesetClear?.([contents_div]); - if (is_tinymce) { + window.MathJax?.typesetClear?.([contentsDiv]); + if (isTinymce) { // Save the cursor location before the update, then restore it // afterwards, if TinyMCE has focus. - const sel = tinymce_instance()!.hasFocus() + const sel = tinymceInstance()!.hasFocus() ? saveSelection() : undefined; - tinymce_instance()!.setContent(this.contents); + tinymceInstance()!.setContent(this.contents); if (sel !== undefined) { restoreSelection(sel); } } else { - contents_div.innerHTML = this.contents; + contentsDiv.innerHTML = this.contents; } - mathJaxTypeset(contents_div); + mathJaxTypeset(contentsDiv); // Indicate the update was successful. TODO: but, contents are still // pending if it contains math... @@ -560,18 +560,18 @@ class DocBlockWidget extends WidgetType { // "This is called when the an instance of the widget is removed from the // editor view." destroy(dom: HTMLElement) { - const [contents_div, is_tinymce] = get_contents(dom); + const [contentsDiv, isTinymce] = getContents(dom); // Forget about any typeset math in this node. - window.MathJax?.typesetClear?.([contents_div]); + window.MathJax?.typesetClear?.([contentsDiv]); // If this is the TinyMCE editor, save it. - if (is_tinymce) { - const codechat_body = document.getElementById("CodeChat-body")!; - const tinymce_div = document.getElementById(TINYMCE_INST)!; - codechat_body.insertBefore(tinymce_div, null); + if (isTinymce) { + const codechatBody = document.getElementById("CodeChat-body")!; + const tinymceDiv = document.getElementById(TINYMCE_INST)!; + codechatBody.insertBefore(tinymceDiv, null); // Make TinyMCE invisible, since it's placed below the body of the // page. - tinymce_instance()!.dom.addClass(tinymce_div, CODECHAT_DOC_HIDDEN); - tinymce_instance()!.resetContent(); + tinymceInstance()!.dom.addClass(tinymceDiv, CODECHAT_DOC_HIDDEN); + tinymceInstance()!.resetContent(); } } } @@ -655,7 +655,7 @@ export const mathJaxTypeset = async ( script.async = true; script.onload = resolve; script.onerror = () => { - report_error(`Failed to load script: ${script.src}`); + reportError(`Failed to load script: ${script.src}`); // We've already reported the error; don't `reject()`, which // would propagate this error up the call chain and further // break things. @@ -675,7 +675,7 @@ export const mathJaxTypeset = async ( // this case. await window.MathJax.typesetPromise?.([node]); } catch (err: unknown) { - report_error( + reportError( `Typeset failed: ${err instanceof Error ? err.message : "unknown"}`, ); } @@ -694,20 +694,49 @@ export const mathJaxUnTypeset = (node: HTMLElement) => { // Given a doc block div element, return the contents div and if TinyMCE is // attached to that div. -const get_contents = (element: Element): [HTMLDivElement, boolean] => { - const contents_div = element.childNodes[1]; - assert(contents_div instanceof HTMLDivElement); - const tinymce_inst = tinymce?.get(contents_div.id); +const getContents = (element: Element): [HTMLDivElement, boolean] => { + const contentsDiv = element.childNodes[1]; + assert(contentsDiv instanceof HTMLDivElement); + const tinymceInst = tinymce?.get(contentsDiv.id); // Note the use of `!=` to check both `undefined` (TinyMCE not loaded) and // `null`. - return [contents_div, tinymce_inst != null]; + return [contentsDiv, tinymceInst != null]; +}; + +// Allow only spaces and delete/backspaces when editing the indent of a doc +// block. This is registered (see `DocBlockPlugin`'s `focusin` handler below) +// as a `beforeinput` listener on a doc block's indent div. It's defined here, +// as a single stable function reference, rather than as an inline arrow +// function created inside the `focusin` handler: `addEventListener` only +// skips re-adding a listener that's reference-equal to one already +// registered on the same target, and `focusin` can fire repeatedly on the +// same (unchanged, and therefore DOM-preserving -- see `DocBlockWidget.eq`) +// indent div, e.g. by blurring then refocusing it. A fresh arrow function +// per `focusin` call would defeat that dedup and accumulate listeners. +const onIndentBeforeinput = (event: InputEvent) => { + // Only modify the behavior of inserts. + if (event.data) { + // Block any insert that's not an insert of spaces. + // TODO: need to support tabs. + if (event.data !== " ".repeat(event.data.length)) { + event.preventDefault(); + } + } +}; + +// Signal that a doc block's indent is dirty. See `onIndentBeforeinput` +// above for why this must be a stable function reference rather than an +// inline closure. +const onIndentInput = (event: Event) => { + const target = event.target; + if (target instanceof HTMLElement) { + onDirty(target); + } }; // Determine if the element which generated the provided event was in a doc // block or not. If not, return false; if so, return the doc block div. -const element_is_in_doc_block = ( - target: EventTarget | null, -): boolean | Element => { +const elementIsInDocBlock = (target: EventTarget | null): boolean | Element => { if (target instanceof HTMLElement) { // Look for either a CodeMirror ancestor or a CodeChat doc block // ancestor. @@ -741,56 +770,56 @@ const element_is_in_doc_block = ( // on\_dirty, but then another doc block is modified, then modifications to the // first doc block would be lost. However, I doubt the user can switch doc // blocks this fast. -const on_dirty = ( +const onDirty = ( // The div that's dirty. It must be a child of the doc block div. - event_target: HTMLElement, + eventTarget: HTMLElement, ) => { - if (on_dirty_scheduled) { + if (onDirtyScheduled) { return; } - set_is_dirty(); - on_dirty_scheduled = true; + setIsDirty(); + onDirtyScheduled = true; // Only run this after typesetting is done, if MathJax is loaded; otherwise, // run this immediately. const whenReady = window.MathJax?.whenReady ?? (async (f: () => void) => f()); whenReady(async () => { - on_dirty_scheduled = false; + onDirtyScheduled = false; // Find the doc block parent div. - const target = event_target.closest(".CodeChat-doc")!; + const target = eventTarget.closest(".CodeChat-doc")!; // We can only get the position (the `from` value) for the doc block. // Use this to find the `to` value for the doc block. let from; try { - from = current_view.posAtDOM(target); + from = currentView.posAtDOM(target); } catch (_e) { console.error("Unable to get position from DOM.", target); return; } // Send an update to the state field associated with this DOM element. - const indent_div = target.childNodes[0]; - assert(indent_div instanceof HTMLDivElement); - const indent = indent_div.innerHTML; - const delimiter = indent_div.getAttribute("data-delimiter")!; - const [contents_div, is_tinymce] = get_contents(target); + const indentDiv = target.childNodes[0]; + assert(indentDiv instanceof HTMLDivElement); + const indent = indentDiv.innerHTML; + const delimiter = indentDiv.getAttribute("data-delimiter")!; + const [contentsDiv, isTinymce] = getContents(target); // I'd like to extract this string, then untypeset only that string, not // the actual div. But I don't know how. - mathJaxUnTypeset(contents_div); + mathJaxUnTypeset(contentsDiv); // Use the raw format; see the implementation notes. - const contents = is_tinymce - ? tinymce_instance()!.save({ format: "raw" }) - : contents_div.innerHTML; + const contents = isTinymce + ? tinymceInstance()!.save({ format: "raw" }) + : contentsDiv.innerHTML; // The `save()` flushes any duplicate `Dirty` events. After this, // following `Dirty` events are genuine. ignoreTinyMceDirty = false; - await mathJaxTypeset(contents_div); + await mathJaxTypeset(contentsDiv); // When editing large doc blocks, they may be deleted then re-created by // CodeMirror, which causes unexpected scrolling. To avoid this, save // then restore the scroll after updating CodeMirror. - const currentScrollTop = current_view.scrollDOM.scrollTop; - current_view.dispatch({ + const currentScrollTop = currentView.scrollDOM.scrollTop; + currentView.dispatch({ effects: [ updateDocBlock.of({ from, @@ -801,7 +830,7 @@ const on_dirty = ( ], }); requestAnimationFrame( - () => (current_view.scrollDOM.scrollTop = currentScrollTop), + () => (currentView.scrollDOM.scrollTop = currentScrollTop), ); }); }; @@ -849,7 +878,7 @@ const on_dirty = ( // so this looks for an exact match on `from` rather than any block that merely // touches `pos` -- otherwise, at a shared boundary, the block ending at `pos` // could be returned instead of the one starting there. -const doc_block_starting_at = ( +const docBlockStartingAt = ( // The CodeMirror view whose doc blocks are searched. view: EditorView, // The document position to check for a doc block starting there. @@ -865,9 +894,9 @@ const doc_block_starting_at = ( return found; }; -// Same as `doc_block_starting_at`, but looks for a doc block that ends exactly +// Same as `docBlockStartingAt`, but looks for a doc block that ends exactly // at `pos`. -const doc_block_ending_at = ( +const docBlockEndingAt = ( view: EditorView, pos: number, ): { from: number; to: number } | null => { @@ -884,7 +913,7 @@ const doc_block_ending_at = ( // Move the CodeMirror selection to `pos` (an edge of a doc block range). The // `DocBlockPlugin.update` handler reacts to the resulting selection change by // focusing the block. Returns `true` so the keymap reports the key as handled. -const select_doc_block_edge = (view: EditorView, pos: number): boolean => { +const selectDocBlockEdge = (view: EditorView, pos: number): boolean => { view.dispatch({ selection: { anchor: pos } }); return true; }; @@ -905,10 +934,10 @@ export const docBlockNavKeymap = keymap.of([ key: "ArrowDown", run: (view) => { const { main } = view.state.selection; - const search_pos = view.state.doc.lineAt(main.head).to + 1; - const range = doc_block_starting_at(view, search_pos); + const searchPos = view.state.doc.lineAt(main.head).to + 1; + const range = docBlockStartingAt(view, searchPos); return range !== null - ? select_doc_block_edge(view, range.from) + ? selectDocBlockEdge(view, range.from) : false; }, }, @@ -921,11 +950,9 @@ export const docBlockNavKeymap = keymap.of([ key: "ArrowUp", run: (view) => { const { main } = view.state.selection; - const search_pos = view.state.doc.lineAt(main.head).from; - const range = doc_block_ending_at(view, search_pos); - return range !== null - ? select_doc_block_edge(view, range.to) - : false; + const searchPos = view.state.doc.lineAt(main.head).from; + const range = docBlockEndingAt(view, searchPos); + return range !== null ? selectDocBlockEdge(view, range.to) : false; }, }, { @@ -941,9 +968,9 @@ export const docBlockNavKeymap = keymap.of([ if (main.head !== line.to) { return false; } - const range = doc_block_starting_at(view, main.head + 1); + const range = docBlockStartingAt(view, main.head + 1); return range !== null - ? select_doc_block_edge(view, range.from) + ? selectDocBlockEdge(view, range.from) : false; }, }, @@ -970,7 +997,7 @@ export const docBlockNavKeymap = keymap.of([ // straight into it; land the cursor at the line's start // instead, so a further ArrowLeft press is needed to enter the // doc block. - if (doc_block_ending_at(view, line.from) !== null) { + if (docBlockEndingAt(view, line.from) !== null) { view.dispatch({ selection: { anchor: line.from }, annotations: stayInCodeBlockAnnotation.of(true), @@ -982,10 +1009,8 @@ export const docBlockNavKeymap = keymap.of([ if (main.head !== line.from) { return false; } - const range = doc_block_ending_at(view, main.head); - return range !== null - ? select_doc_block_edge(view, range.to) - : false; + const range = docBlockEndingAt(view, main.head); + return range !== null ? selectDocBlockEdge(view, range.to) : false; }, }, { @@ -1005,7 +1030,7 @@ export const docBlockNavKeymap = keymap.of([ return false; } const line = view.state.doc.lineAt(main.head); - if (doc_block_ending_at(view, line.from) !== null) { + if (docBlockEndingAt(view, line.from) !== null) { view.dispatch({ selection: { anchor: line.from }, annotations: stayInCodeBlockAnnotation.of(true), @@ -1049,29 +1074,29 @@ export const DocBlockPlugin = ViewPlugin.fromClass( return; } // See if the new main selection falls within a doc block. - const main_selection = update.state.selection.main; + const mainSelection = update.state.selection.main; update.state .field(docBlockField) .between( - main_selection.from, - main_selection.to, + mainSelection.from, + mainSelection.to, (from: number, to: number, _value: Decoration) => { // Is this range contained within this doc block? If // the ranges also contains element outside it, then // don't capture focus. TODO: not certain on the // bounds -- should I use <= or <, etc.? if ( - main_selection.from < from || - main_selection.to > to + mainSelection.from < from || + mainSelection.to > to ) { return; } // Ensure we have a valid dom. This also checks for // undefined. - const dom_at_pos = update.view.domAtPos(from); + const domAtPos = update.view.domAtPos(from); const dom = - dom_at_pos.node.childNodes[dom_at_pos.offset]; + domAtPos.node.childNodes[domAtPos.offset]; if ( !(dom instanceof HTMLElement) || dom.className !== "CodeChat-doc" @@ -1092,7 +1117,7 @@ export const DocBlockPlugin = ViewPlugin.fromClass( // the end. Once TinyMCE initializes it preserves // this selection, so the edge placement carries // over. - const at_end = main_selection.head >= to; + const atEnd = mainSelection.head >= to; const range = document.createRange(); // Walk to the first/last actual text node under // `contents`, rather than using @@ -1105,27 +1130,25 @@ export const DocBlockPlugin = ViewPlugin.fromClass( // *ancestor* with the `CodeChat-doc-contents` // class; if the anchor node already *is* that div, // the walk's loop body never runs and it returns an - // empty `selection_path`, silently dropping this + // empty `selectionPath`, silently dropping this // edge placement and leaving the caret wherever // TinyMCE's own init happens to put it (its start). // Anchoring on a text node instead keeps the walk // -- and thus the edge placement -- intact. - let edge_node: Node = contents; + let edgeNode: Node = contents; while ( - at_end - ? edge_node.lastChild - : edge_node.firstChild + atEnd ? edgeNode.lastChild : edgeNode.firstChild ) { - edge_node = at_end - ? edge_node.lastChild! - : edge_node.firstChild!; + edgeNode = atEnd + ? edgeNode.lastChild! + : edgeNode.firstChild!; } - if (edge_node.nodeType === Node.TEXT_NODE) { - const offset = at_end - ? (edge_node.textContent?.length ?? 0) + if (edgeNode.nodeType === Node.TEXT_NODE) { + const offset = atEnd + ? (edgeNode.textContent?.length ?? 0) : 0; - range.setStart(edge_node, offset); - range.setEnd(edge_node, offset); + range.setStart(edgeNode, offset); + range.setEnd(edgeNode, offset); } else { // No text node found (e.g. an empty doc block); // fall back to the previous, element-anchored @@ -1133,7 +1156,7 @@ export const DocBlockPlugin = ViewPlugin.fromClass( range.selectNodeContents(contents); // `collapse(true)` -> start, `collapse(false)` // -> end. - range.collapse(!at_end); + range.collapse(!atEnd); } const sel = window.getSelection(); sel?.removeAllRanges(); @@ -1150,15 +1173,15 @@ export const DocBlockPlugin = ViewPlugin.fromClass( // update() method above, but this is VERY slow, since update is // called frequently. focusin: (event: FocusEvent, _view: EditorView) => { - const event_target = event.target; - const target_or_false = element_is_in_doc_block(event_target); - if (!(target_or_false instanceof HTMLDivElement)) { + const eventTarget = event.target; + const targetOrFalse = elementIsInDocBlock(eventTarget); + if (!(targetOrFalse instanceof HTMLDivElement)) { return false; } // Set up for editing the indent of doc blocks. - const target = target_or_false; - const indent_div = target.childNodes[0]; - assert(indent_div instanceof HTMLDivElement); + const target = targetOrFalse; + const indentDiv = target.childNodes[0]; + assert(indentDiv instanceof HTMLDivElement); // Use the // [beforeinput](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/beforeinput_event) // event to allow only whitespace in the indent. Note that @@ -1167,45 +1190,28 @@ export const DocBlockPlugin = ViewPlugin.fromClass( // event listeners for this target, the function or object is // not added a second time." So, we can just add it here without // needing to check if it's already present. - indent_div.addEventListener( + indentDiv.addEventListener( "beforeinput", - // Allow only spaces and delete/backspaces when editing the - // indent of a doc block. - (event: InputEvent) => { - // Only modify the behavior of inserts. - if (event.data) { - // Block any insert that's not an insert of spaces. - // TODO: need to support tabs. - if (event.data !== " ".repeat(event.data.length)) { - event.preventDefault(); - } - } - }, + onIndentBeforeinput as EventListener, ); - indent_div.addEventListener("input", (event) => { - // Signal that this indent is dirty. - const target = event.target; - if (target instanceof HTMLElement) { - on_dirty(target); - } - }); + indentDiv.addEventListener("input", onIndentInput); // If the target is in the indent, not the contents, then the // following code isn't needed. if ( - !(event_target instanceof HTMLDivElement) || - event_target.closest(".CodeChat-doc-contents") === null + !(eventTarget instanceof HTMLDivElement) || + eventTarget.closest(".CodeChat-doc-contents") === null ) { return false; } - const [contents_div, is_tinymce] = get_contents(target); + const [contentsDiv, isTinymce] = getContents(target); // Send updated cursor/scroll info. startAutoUpdateTimer(); // See if this is already a TinyMCE instance; if not, move it // here. - if (is_tinymce) { + if (isTinymce) { // Nothing to do. } else { // Wait until the focus event completes; this causes the @@ -1213,26 +1219,26 @@ export const DocBlockPlugin = ViewPlugin.fromClass( // contenteditable div. Then, save that location. setTimeout(async () => { // In case this node was modified during the timeout. - if (!contents_div.isConnected) { + if (!contentsDiv.isConnected) { return; } // Note whether this doc block still genuinely has focus // before any of the DOM surgery below runs (which - // removes `contents_div` from the document, making + // removes `contentsDiv` from the document, making // `document.activeElement` an unreliable way to answer // this question afterwards). If the user has since // clicked or navigated elsewhere while this promotion // was in flight, don't steal focus back to this (now // stale) doc block once the promotion finishes -- see // the check below. - const still_focused = target.contains( + const stillFocused = target.contains( document.activeElement, ); // Create the TinyMCE instance if necessary. Note the // use of `==` here to check for `null` (TinyMCE is // loaded, but no instance exists) and `undefined` // (TinyMCE isn't loaded). - if (tinymce_instance() == null) { + if (tinymceInstance() == null) { await init({ selector: "#TinyMCE-inst", setup: (editor: Editor) => { @@ -1314,7 +1320,7 @@ export const DocBlockPlugin = ViewPlugin.fromClass( return; } if (!ignoreTinyMceDirty) { - on_dirty(target); + onDirty(target); } }, ); @@ -1324,7 +1330,7 @@ export const DocBlockPlugin = ViewPlugin.fromClass( // Sometimes, I see non-elements here. if (target instanceof HTMLElement) { ignoreTinyMceDirty = true; - on_dirty(target); + onDirty(target); } }); @@ -1352,10 +1358,10 @@ export const DocBlockPlugin = ViewPlugin.fromClass( ); // Untypeset math in the old doc block and the current // doc block before moving its contents around. - const tinymce_div = + const tinymceDiv = document.getElementById(TINYMCE_INST)!; - mathJaxUnTypeset(tinymce_div); - mathJaxUnTypeset(contents_div); + mathJaxUnTypeset(tinymceDiv); + mathJaxUnTypeset(contentsDiv); // The code which moves TinyMCE into this div disturbs // all the nodes, which causes it to loose a selection // tied to a specific node. @@ -1369,29 +1375,28 @@ export const DocBlockPlugin = ViewPlugin.fromClass( // wasn't in use (currently hidden, since no previous // doc block was being edited). if ( - !tinymce_div.classList.contains(CODECHAT_DOC_HIDDEN) + !tinymceDiv.classList.contains(CODECHAT_DOC_HIDDEN) ) { - const old_contents_div = + const oldContentsDiv = document.createElement("div"); - old_contents_div.className = - "CodeChat-doc-contents"; + oldContentsDiv.className = "CodeChat-doc-contents"; // If the contents aren't editable, then the div // won't receive a `focusin` message (it instead // goes to a CodeMirror layer). - old_contents_div.tabIndex = 0; - old_contents_div.innerHTML = - tinymce_instance()!.save(); - tinymce_div.parentNode!.insertBefore( - old_contents_div, + oldContentsDiv.tabIndex = 0; + oldContentsDiv.innerHTML = + tinymceInstance()!.save(); + tinymceDiv.parentNode!.insertBefore( + oldContentsDiv, null, ); // The previous content edited by TinyMCE is now a // div. Retypeset this after the transition. - await mathJaxTypeset(old_contents_div); + await mathJaxTypeset(oldContentsDiv); } // Move TinyMCE to the new location, then remove the old // div it will replace. - target.insertBefore(tinymce_div, null); + target.insertBefore(tinymceDiv, null); // Calling `setContent()` instead produces spurious // `Dirty` events, observed after receiving a @@ -1400,27 +1405,25 @@ export const DocBlockPlugin = ViewPlugin.fromClass( // edits to the previous doc block no longer apply here. // TODO: Eventually, we need a way to chain TinyMCE's // undo history with CodeMirror's undo history. - tinymce_instance()!.resetContent( - contents_div.innerHTML, - ); - contents_div.remove(); - tinymce_instance()!.dom.removeClass( - tinymce_div, + tinymceInstance()!.resetContent(contentsDiv.innerHTML); + contentsDiv.remove(); + tinymceInstance()!.dom.removeClass( + tinymceDiv, CODECHAT_DOC_HIDDEN, ); // The new div is now a TinyMCE editor. Retypeset this. - await mathJaxTypeset(tinymce_div); + await mathJaxTypeset(tinymceDiv); // This process causes TinyMCE to lose focus. Restore // that -- but only if focus was still genuinely in this // doc block just before the DOM surgery above began - // (see `still_focused`). Unconditionally focusing here + // (see `stillFocused`). Unconditionally focusing here // would otherwise steal focus back to this (now stale) // doc block even after the user clicked or navigated // elsewhere while this promotion was in flight. // Restoring the selection is skipped too, since it's // meaningless once focus has moved on. - if (!still_focused) { + if (!stillFocused) { return; } // However, this causes TinyMCE to lose the selection, @@ -1428,7 +1431,7 @@ export const DocBlockPlugin = ViewPlugin.fromClass( // doc block is longer than a screen, omitting the // `preventScroll` parameter causes this to scroll to // the top of the doc block, which is incorrect. - tinymce_div.focus({ preventScroll: true }); + tinymceDiv.focus({ preventScroll: true }); // Copy the selection over to TinyMCE by indexing the // selection path to find the selected node. @@ -1451,9 +1454,9 @@ export const DocBlockPlugin = ViewPlugin.fromClass( focusout: (event: FocusEvent, _view: EditorView) => { const target = event.target; if (target instanceof HTMLElement) { - const indent_div = target.closest(".CodeChat-doc-indent"); - if (indent_div instanceof HTMLElement) { - indent_div.contentEditable = "false"; + const indentDiv = target.closest(".CodeChat-doc-indent"); + if (indentDiv instanceof HTMLElement) { + indentDiv.contentEditable = "false"; } } return false; @@ -1512,7 +1515,7 @@ const autosaveExtension = EditorView.updateListener.of( } } if (isChanged) { - set_is_dirty(); + setIsDirty(); startAutoUpdateTimer(); } else if (v.selectionSet) { // Send an update if only the selection changed. @@ -1522,50 +1525,56 @@ const autosaveExtension = EditorView.updateListener.of( ); // Wrap a stream language dynamic import. -const import_stream_language = async (lang: StreamParser) => +const importStreamLanguage = async (lang: StreamParser) => (await import("@codemirror/language")).StreamLanguage.define(lang); // Given source code in a CodeMirror-friendly JSON format, load it into the // provided div. -export const CodeMirror_load = async ( +export const codeMirrorLoad = async ( // The div to place the loaded document in. - codechat_body: HTMLDivElement, + codechatBody: HTMLDivElement, // The document to load. - codechat_for_web: CodeChatForWeb, + codechatForWeb: CodeChatForWeb, // Additional extensions. extensions: Array, - cursor_position?: CursorPosition, - scroll_line?: number, + cursorPosition?: CursorPosition, + scrollLine?: number, ) => { - if ("Plain" in codechat_for_web.source) { + if ("Plain" in codechatForWeb.source) { // Although the // [docs](https://codemirror.net/docs/ref/#state.EditorState^fromJSON) // specify a // [EditorStateConfig](https://codemirror.net/docs/ref/#state.EditorStateConfig) // which contains `doc` and `selection`, the implementation requires // these to be present in the `json` (first) argument. Therefore: - const editor_state_json = { - doc: codechat_for_web.source.Plain.doc, + const editorStateJson = { + doc: codechatForWeb.source.Plain.doc, selection: EditorSelection.single(0).toJSON(), - doc_blocks: codechat_for_web.source.Plain.doc_blocks, + doc_blocks: codechatForWeb.source.Plain.doc_blocks, }; // Save the current scroll position, to prevent the view from scrolling // back to the top after an update/reload. let scrollSnapshot; - if (current_view !== undefined) { - scrollSnapshot = current_view.scrollSnapshot(); + if (currentView !== undefined) { + scrollSnapshot = currentView.scrollSnapshot(); // For reloads, we need to remove previous instances; otherwise, Bad // Things happen. tinymce?.remove(); + // Per the [docs](https://codemirror.net/docs/ref/#view.EditorView.destroy), + // this must be called to clean up the view (DOM-external state + // such as global event listeners) before discarding it; simply + // replacing `codechatBody`'s contents below doesn't do this and + // would otherwise leak the old view on every reload. + currentView.destroy(); } - codechat_body.innerHTML = `
`; + codechatBody.innerHTML = `
`; let parser; // Dynamically load the parser. - switch (codechat_for_web.metadata.mode) { + switch (codechatForWeb.metadata.mode) { // Languages with a parser. case "sh": - parser = await import_stream_language( + parser = await importStreamLanguage( (await import("@codemirror/legacy-modes/mode/shell")).shell, ); break; @@ -1596,7 +1605,7 @@ export const CodeMirror_load = async ( break; // Octave is an open-source MATLAB-ish clone. case "matlab": - parser = await import_stream_language( + parser = await importStreamLanguage( (await import("@codemirror/legacy-modes/mode/octave")) .octave, ); @@ -1611,12 +1620,12 @@ export const CodeMirror_load = async ( parser = (await import("@codemirror/lang-sql")).sql(); break; case "swift": - parser = await import_stream_language( + parser = await importStreamLanguage( (await import("@codemirror/legacy-modes/mode/swift")).swift, ); break; case "toml": - parser = await import_stream_language( + parser = await importStreamLanguage( (await import("@codemirror/legacy-modes/mode/toml")).toml, ); break; @@ -1626,12 +1635,12 @@ export const CodeMirror_load = async ( ).javascript({ typescript: true }); break; case "vhdl": - parser = await import_stream_language( + parser = await importStreamLanguage( (await import("@codemirror/legacy-modes/mode/vhdl")).vhdl, ); break; case "verilog": - parser = await import_stream_language( + parser = await importStreamLanguage( (await import("@codemirror/legacy-modes/mode/verilog")) .verilog, ); @@ -1657,13 +1666,13 @@ export const CodeMirror_load = async ( parser = ( await import("@codemirror/lang-javascript") ).javascript(); - report_error( - `Unknown lexer name ${codechat_for_web.metadata.mode}`, + reportError( + `Unknown lexer name ${codechatForWeb.metadata.mode}`, ); break; } const state = EditorState.fromJSON( - editor_state_json, + editorStateJson, { extensions: [ DocBlockPlugin, @@ -1698,12 +1707,12 @@ export const CodeMirror_load = async ( ...extensions, ], }, - CodeMirror_JSON_fields, + codeMirrorJsonFields, ); - const codechat_div = codechat_body.childNodes[0]; - assert(codechat_div instanceof HTMLDivElement); - current_view = new EditorView({ - parent: codechat_div, + const codechatDiv = codechatBody.childNodes[0]; + assert(codechatDiv instanceof HTMLDivElement); + currentView = new EditorView({ + parent: codechatDiv, state, scrollTo: scrollSnapshot, }); @@ -1711,15 +1720,15 @@ export const CodeMirror_load = async ( // When editing large doc blocks, they may be deleted then re-created by // CodeMirror, which causes unexpected scrolling. To avoid this, save // then restore the scroll after updating CodeMirror. - const currentScrollTop = current_view.scrollDOM.scrollTop; + const currentScrollTop = currentView.scrollDOM.scrollTop; // This contains a diff, instead of plain text. Apply the text diff. // // First, apply just the text edits. Use an annotation so that the doc // blocks aren't changed; without this, the diff won't work (since // from/to values of doc blocks are changed by unfrozen text edits). - current_view.dispatch({ - changes: codechat_for_web.source.Diff.doc, + currentView.dispatch({ + changes: codechatForWeb.source.Diff.doc, annotations: [ docBlockFreezeAnnotation.of(true), noAutosaveAnnotation.of(true), @@ -1730,7 +1739,7 @@ export const CodeMirror_load = async ( // the doc block effects, even when changes to the doc block state is // frozen. const stateEffects: StateEffect[] = []; - for (const transaction of codechat_for_web.source.Diff.doc_blocks) { + for (const transaction of codechatForWeb.source.Diff.doc_blocks) { if ("Add" in transaction) { const add = transaction.Add; stateEffects.push( @@ -1751,85 +1760,85 @@ export const CodeMirror_load = async ( } } // Update the view with these changes to the state. - current_view.dispatch({ + currentView.dispatch({ effects: stateEffects, annotations: noAutosaveAnnotation.of(true), }); // Restore the scroll position. requestAnimationFrame( - () => (current_view.scrollDOM.scrollTop = currentScrollTop), + () => (currentView.scrollDOM.scrollTop = currentScrollTop), ); } - scroll_to_line(cursor_position, scroll_line); + scrollToLine(cursorPosition, scrollLine); }; -// Scroll to the provided `scroll_line`; place the cursor at `cursor_line`. -export const scroll_to_line = ( - cursor_position?: CursorPosition, - scroll_line?: number, +// Scroll to the provided `scrollLine`; place the cursor at `cursor_line`. +export const scrollToLine = ( + cursorPosition?: CursorPosition, + scrollLine?: number, ) => { - if (cursor_position === undefined && scroll_line === undefined) { + if (cursorPosition === undefined && scrollLine === undefined) { return; } // 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 = { + const dispatchData: TransactionSpec = { annotations: noAutosaveAnnotation.of(true), }; - if (cursor_position !== undefined) { + if (cursorPosition !== undefined) { // Translate the line numbers to a position. - if ("Line" in cursor_position) { - const cursor_pos = current_view?.state.doc.line( - cursor_position.Line, + if ("Line" in cursorPosition) { + const cursorPos = currentView?.state.doc.line( + cursorPosition.Line, ).from; - dispatch_data.selection = { - anchor: cursor_pos, - head: cursor_pos, + dispatchData.selection = { + anchor: cursorPos, + head: cursorPos, }; } else { - report_error("Not supported."); + reportError("Not supported."); } // If a scroll position is provided, use it; otherwise, scroll the // cursor into the current view. - if (scroll_line === undefined) { - dispatch_data.scrollIntoView = true; + if (scrollLine === undefined) { + dispatchData.scrollIntoView = true; } } - if (scroll_line !== undefined) { - const scroll_pos = current_view?.state.doc.line(scroll_line).from; - dispatch_data.effects = EditorView.scrollIntoView(scroll_pos, { + if (scrollLine !== undefined) { + const scrollPos = currentView?.state.doc.line(scrollLine).from; + dispatchData.effects = EditorView.scrollIntoView(scrollPos, { y: "start", }); } // Run it. - current_view?.dispatch(dispatch_data); + currentView?.dispatch(dispatchData); // Restore the previous horizontal scroll position, overriding whatever // `scrollIntoView` set. Defer to the next frame so this runs after // CodeMirror has applied its own scroll from the transaction above. - if (scroll_line !== undefined) { + if (scrollLine !== undefined) { // With line wrapping enabled, the only source of horizontal scroll is a // doc block containing a long, non-wrapping line. CodeMirror's // `scrollIntoView` can't measure a position inside such a block // reliably and pins `scrollLeft` to its maximum regardless of the `x` // option. We only want to scroll vertically, so capture the horizontal // position now and restore it after the dispatch. - const prev_scroll_left = current_view?.scrollDOM.scrollLeft; + const prevScrollLeft = currentView?.scrollDOM.scrollLeft; requestAnimationFrame(() => { - if (current_view) { - current_view.scrollDOM.scrollLeft = prev_scroll_left; + if (currentView) { + currentView.scrollDOM.scrollLeft = prevScrollLeft; } }); } }; // Apply a `StringDiff` to the before string to produce the after string. -export const apply_diff_str = (before: string, diffs: StringDiff[]) => { +export const applyDiffStr = (before: string, diffs: StringDiff[]) => { // Walk from the last diff to the first. JavaScript doesn't have reverse // iteration AFAIK. let after = before; @@ -1847,71 +1856,71 @@ export const apply_diff_str = (before: string, diffs: StringDiff[]) => { }; // Return the JSON data to save from the current CodeMirror-based document. -export const CodeMirror_save = (): CodeMirrorDiffable => { +export const codeMirrorSave = (): CodeMirrorDiffable => { // This is the data to write — the source code. First, transform the HTML // back into code and doc blocks. - const code_mirror: CodeMirror = current_view.state.toJSON( - CodeMirror_JSON_fields, - ); - /// @ts-expect-error("This does exist.") - delete code_mirror.selection; + const codeMirror: CodeMirror = + currentView.state.toJSON(codeMirrorJsonFields); + // @ts-expect-error -- `state.toJSON()` includes `selection` even though + // it's not part of the `CodeMirror` type; delete it before returning. + delete codeMirror.selection; - return { Plain: code_mirror }; + return { Plain: codeMirror }; }; -export const set_CodeMirror_positions = ( - update_message_contents: UpdateMessageContents, +export const setCodeMirrorPositions = ( + updateMessageContents: UpdateMessageContents, ) => { // If a doc block has focus, then the CodeMirror selection reports line 1. // Use the starting line number of the doc block instead. - const doc_block = document.activeElement?.closest(".CodeChat-doc"); - let cursor_position: CursorPosition; - if (doc_block) { - const from = current_view.posAtDOM(doc_block); + const docBlock = document.activeElement?.closest(".CodeChat-doc"); + let cursorPosition: CursorPosition; + if (docBlock) { + const from = currentView.posAtDOM(docBlock); const location = saveSelection(); // If there's a selection in the doc block, pass the DOM location; // otherwise, pass the line where the doc block starts. - if (location.selection_offset === undefined) { - cursor_position = { - Line: current_view.state.doc.lineAt(from).number, + if (location.selectionOffset === undefined) { + cursorPosition = { + Line: currentView.state.doc.lineAt(from).number, }; } else { - cursor_position = { + cursorPosition = { DomLocation: { - dom_path: location.selection_path, - dom_offset: location.selection_offset, + dom_path: location.selectionPath, + dom_offset: location.selectionOffset, from, }, }; } } else { // For a code block, we can simply retrieve the line number. - cursor_position = { - Line: current_view.state.doc.lineAt( - current_view.state.selection.main.from, + cursorPosition = { + Line: currentView.state.doc.lineAt( + currentView.state.selection.main.from, ).number, }; } - update_message_contents.cursor_position = cursor_position; + updateMessageContents.cursor_position = cursorPosition; - // `current_view.viewport.from` isn't accurate, since it's not really the + // `currentView.viewport.from` isn't accurate, since it's not really the // top line, but a margin before it; see the // [docs](https://codemirror.net/docs/ref/#view.EditorView.viewport). // Instead, use // [this approach](https://discuss.codemirror.net/t/how-can-i-get-the-top-line-number-in-real-time/9404). // This value still seems a bit off, probably because CodeMirror doesn't // account for doc block sizing? - update_message_contents.scroll_position = current_view.state.doc.lineAt( - current_view.lineBlockAtHeight(-current_view.documentTop).from, + updateMessageContents.scroll_position = currentView.state.doc.lineAt( + currentView.lineBlockAtHeight(-currentView.documentTop).from, ).number; }; -const report_error = (text: string) => { +const reportError = (text: string) => { console.error(text); - show_toast(text); + showToast(text); }; -const halt_on_error = (text: string): never => { +const haltOnError = (text: string): never => { document.getElementById("error-overlay")!.style.display = "block"; console.error(text); // The error handler will make this a toast. diff --git a/client/src/debug_enabled.mts b/client/src/debug_enabled.mts index 965284f5..dc20adad 100644 --- a/client/src/debug_enabled.mts +++ b/client/src/debug_enabled.mts @@ -25,7 +25,7 @@ export const DEBUG_ENABLED = false; export const MAX_MESSAGE_LENGTH = 50000; /*eslint-disable-next-line @typescript-eslint/no-explicit-any */ -export const console_log = (...args: any) => { +export const consoleLog = (...args: any) => { if (DEBUG_ENABLED) { console.log(...args); } diff --git a/client/src/shared.mts b/client/src/shared.mts index caadb971..163f971c 100644 --- a/client/src/shared.mts +++ b/client/src/shared.mts @@ -19,7 +19,7 @@ // // The time, in ms, to wait between the last user edit and sending updated data // to the Server. -export const auto_update_timeout_ms = 300; +export const autoUpdateTimeoutMs = 300; // Produce a whole random number. Fractional numbers aren't consistently // converted to the same number across JavaScript and Rust. Note that the mantissa of a JavaScript `Number` diff --git a/client/src/show_toast.mts b/client/src/show_toast.mts index 070bdd1b..0e204b0b 100644 --- a/client/src/show_toast.mts +++ b/client/src/show_toast.mts @@ -14,12 +14,12 @@ // the CodeChat Editor. If not, see // [http://www.gnu.org/licenses](http://www.gnu.org/licenses). // -// `show_toast.mts` -- Show a toast message +// `showToast.mts` -- Show a toast message // ============================================================================= import Toastify from "toastify-js"; import "toastify-js/src/toastify.css"; -export const show_toast = (text: string) => +export const showToast = (text: string) => Toastify({ text, duration: 20000, diff --git a/client/src/tinymce-config.mts b/client/src/tinymce-config.mts index 7cc72221..bb891605 100644 --- a/client/src/tinymce-config.mts +++ b/client/src/tinymce-config.mts @@ -73,13 +73,13 @@ export const init = async ( // below can't be overridden by these options. The only exception is a value // for `setup`, which will be combined with the setup defined in this // function. - addiitionalOptions: RawEditorOptions, + additionalOptions: RawEditorOptions, ) => { // Merge the provided options with this basic setup, giving priority to the // settings below. const combinedOptions: RawEditorOptions = Object.assign( {}, - addiitionalOptions, + additionalOptions, { // See the list of // [plugins](https://www.tiny.cloud/docs/tinymce/latest/plugins/). diff --git a/dist-workspace.toml b/dist-workspace.toml index cff67f34..8187fba5 100644 --- a/dist-workspace.toml +++ b/dist-workspace.toml @@ -20,7 +20,7 @@ # [cargo-dist](https://opensource.axo.dev/cargo-dist/) # ============================================================================== [workspace] -members = ["cargo:server/"] +members = ["cargo:extensions/standalone/"] # Config for 'dist' [dist] diff --git a/extensions/VSCode/Cargo.lock b/extensions/VSCode/Cargo.lock index 07d2d4db..8a443d61 100644 --- a/extensions/VSCode/Cargo.lock +++ b/extensions/VSCode/Cargo.lock @@ -302,9 +302,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "ammonia" -version = "4.1.3" +version = "4.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b9d3370580a12f4b7a10fdcc18b28942c083ba570e3d954fe59d10951b85a2" +checksum = "dc6d763210e2eb7670d1a5183a08bebefa3f97db2a738a684f2ce00bd49f681d" dependencies = [ "cssparser", "html5ever", @@ -321,56 +321,12 @@ dependencies = [ "libc", ] -[[package]] -name = "anstream" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - [[package]] name = "anstyle" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" -[[package]] -name = "anstyle-parse" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys 0.61.2", -] - [[package]] name = "anyhow" version = "1.0.104" @@ -559,9 +515,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" dependencies = [ "find-msvc-tools", "jobserver", @@ -600,46 +556,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "clap" -version = "4.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd059f9da4f5c36b3787f65d38ccaab1cc315f07b01f89abc8359ee6a8205011" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "clap_lex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" - [[package]] name = "cobs" version = "0.3.0" @@ -654,18 +570,14 @@ name = "codechat-editor-server" version = "0.2.0" dependencies = [ "actix-files", - "actix-http", "actix-rt", "actix-server", - "actix-service", "actix-web", "actix-web-httpauth", "actix-ws", "ammonia", - "anyhow", "bytes", "chrono", - "clap", "dprint-plugin-markdown", "dunce", "futures-util", @@ -674,7 +586,6 @@ dependencies = [ "htmlize", "imara-diff", "indoc", - "lazy_static", "log", "log4rs", "markup5ever_rcdom", @@ -683,7 +594,6 @@ dependencies = [ "minify-html", "minreq", "normalize-line-endings", - "notify-debouncer-full", "path-slash", "pest", "pest_derive", @@ -704,7 +614,6 @@ dependencies = [ "url", "urlencoding", "webbrowser", - "windows", ] [[package]] @@ -719,12 +628,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "colorchoice" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" - [[package]] name = "combine" version = "4.6.7" @@ -907,9 +810,9 @@ dependencies = [ [[package]] name = "ctor" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a394189d59f9befacce833f337f7b1eca5e9a91221bcdd4d28e0114d96e597b3" +checksum = "e2e30e509674ef0ec91e21a7735766db37d163d46151b6a361d8b83dd79116bd" [[package]] name = "dashmap" @@ -1069,9 +972,9 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "either" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" [[package]] name = "embedded-io" @@ -1116,15 +1019,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" -[[package]] -name = "file-id" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1fc6a637b6dc58414714eddd9170ff187ecb0933d4c7024d1abbd23a3cc26e9" -dependencies = [ - "windows-sys 0.60.2", -] - [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -1168,15 +1062,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fsevent-sys" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" -dependencies = [ - "libc", -] - [[package]] name = "funty" version = "2.0.0" @@ -1384,12 +1269,6 @@ dependencies = [ "allocator-api2", ] -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - [[package]] name = "htmd" version = "0.5.4" @@ -1593,9 +1472,9 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.30" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b009b6744c1445efd7244084e25e498636412effb6760b55067553baa925cc7" +checksum = "7f8a7b8211e695a1d0cd91cace480d4d0bd57667ab10277cc412c5f7f4884f83" dependencies = [ "crossbeam-deque", "globset", @@ -1619,9 +1498,9 @@ dependencies = [ [[package]] name = "impl-more" -version = "0.3.1" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35a84fd5aa25fae5c0f4a33d9cac2ca017fc622cbd089be2229993514990f870" +checksum = "277ff51754a3f68f12f58446c5d006aa8baa4914ea273cce24a599cfaff33d4f" [[package]] name = "indexmap" @@ -1644,32 +1523,6 @@ dependencies = [ "rustversion", ] -[[package]] -name = "inotify" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "153be1941a183ec9ccd095ddbe17a8b8d435ef6c76e9e02451b933c3999af2c8" -dependencies = [ - "bitflags", - "inotify-sys", - "libc", -] - -[[package]] -name = "inotify-sys" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c033f80b2c113cdf91ab7a33faa9cbc014726dcad99880c8609af2a370edf37d" -dependencies = [ - "libc", -] - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - [[package]] name = "itertools" version = "0.10.5" @@ -1770,26 +1623,6 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c22a2041e3874a055a4eb03ea2395aaccdefa84ce75b31d542d72a741c3c6ad3" -[[package]] -name = "kqueue" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "273c0752728918e0ac4976f2b275b6fefb9ecd400585dec929419f3844cd87b5" -dependencies = [ - "kqueue-sys", - "libc", -] - -[[package]] -name = "kqueue-sys" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" -dependencies = [ - "bitflags", - "libc", -] - [[package]] name = "language-tags" version = "0.3.2" @@ -1804,9 +1637,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.186" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libloading" @@ -2083,9 +1916,9 @@ checksum = "9bb517913cfcfb9eeda59f36020269075a152701a01606c612f547e4890be399" [[package]] name = "napi" -version = "3.10.5" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6826e5ddc15589b2d68c8ad5321c18e85d40488e93e32962f362e572669bccf6" +checksum = "de33522036981030a75c231829566bc63414e08101a6f5ff4ac6cef19c8e0941" dependencies = [ "bitflags", "ctor", @@ -2105,9 +1938,9 @@ checksum = "c9c366d2c8c60b86fa632df75f745509b52f9128f91a6bad4c796e44abb505e1" [[package]] name = "napi-derive" -version = "3.5.10" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fe526e81c105d3640516fcde83909dd1afe757c0d7a15af58830b5bc0fb9a1" +checksum = "a49c513341a61a16a10af6efcce46b30d0822ba2d4fb197d24d33dfc199c78d5" dependencies = [ "convert_case 0.11.0", "ctor", @@ -2119,9 +1952,9 @@ dependencies = [ [[package]] name = "napi-derive-backend" -version = "5.1.2" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514281397bcddd9ea9a876c7a21a57bff2374237a000ca9a64ea0211ec1993e2" +checksum = "4747005fa3e2c9989ac45a723a514c5db2411238b72981a3cda4c701a9dfea17" dependencies = [ "convert_case 0.11.0", "proc-macro2", @@ -2132,9 +1965,9 @@ dependencies = [ [[package]] name = "napi-sys" -version = "3.2.3" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73e43cf2eb0bd1bf95a43c07c076ebd2da5d1e015a71c3d201faeffffcc0ecac" +checksum = "85fbf1fa9f1babfe396d74bbbf52b3643770243e8f5b0b46715d4caf7f0dfc9a" dependencies = [ "libloading", ] @@ -2169,46 +2002,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" -[[package]] -name = "notify" -version = "8.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" -dependencies = [ - "bitflags", - "fsevent-sys", - "inotify", - "kqueue", - "libc", - "log", - "mio", - "notify-types", - "walkdir", - "windows-sys 0.60.2", -] - -[[package]] -name = "notify-debouncer-full" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02b49179cfebc9932238d04d6079912d26de0379328872846118a0fa0dbb302" -dependencies = [ - "file-id", - "log", - "notify", - "notify-types", - "walkdir", -] - -[[package]] -name = "notify-types" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" -dependencies = [ - "bitflags", -] - [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -2283,12 +2076,6 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - [[package]] name = "ordered-float" version = "2.10.1" @@ -2318,9 +2105,9 @@ checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" [[package]] name = "oxc-browserslist" -version = "3.0.11" +version = "3.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50a403a3c6be65be7bc7730d07d959ec6c143e4ff8117da485df78cd5582260a" +checksum = "6967692b4c51de292573c3f5eed236b6df29ac47d122379ad3a9d209776a9cf3" dependencies = [ "miniz_oxide 0.9.1", "postcard", @@ -2756,9 +2543,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.7" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47627dd7305c6a2d6c8c6bcd24c5a4c17dbbf425f4f9c5313e724b38fc9782e9" +checksum = "7df728be843c7070fab6ab7c328c4e9e9d78e23bf749c0669c86ee7ebfa050a2" dependencies = [ "memchr", "ucd-trie", @@ -2766,9 +2553,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.7" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b4254325ecad416ab689e27ba51da03ba01a9632bc6e108f5fe7c3c4ad29d58" +checksum = "9e2dd6fc3b26b3462ee188aac870f5a41d398f1cd5e2408d16531bd71c9591fd" dependencies = [ "pest", "pest_generator", @@ -2776,9 +2563,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.7" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c4c0e91ead7a8f7acecbca6f003fc2e8282b1dbe2dd9c9d2f16aba42995e0a7" +checksum = "6a7a9205cfb6f596a9e8b689c0a15f9ceb7a1aafae7aaf788150ac65b29975b6" dependencies = [ "pest", "pest_meta", @@ -2789,9 +2576,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.8.7" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9744bc48116fee06334924bb5f2bad41eed5e89bd26e29b0b799f9a3f82c210" +checksum = "85abd351c0de1e8384fc791a0737111a350394937e92b956b743dac12429f57c" dependencies = [ "pest", ] @@ -3384,7 +3171,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.2", + "syn 3.0.3", ] [[package]] @@ -3584,12 +3371,6 @@ dependencies = [ "quote", ] -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - [[package]] name = "syn" version = "1.0.109" @@ -3614,9 +3395,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a207d6d6a2b7fc470b80443726053f18a2481b7e1eee970597051596567987a3" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" dependencies = [ "proc-macro2", "quote", @@ -3713,7 +3494,7 @@ checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" dependencies = [ "proc-macro2", "quote", - "syn 3.0.2", + "syn 3.0.3", ] [[package]] @@ -3792,9 +3573,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.53.0" +version = "1.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d988bcd52dbe076d3d46903332f58c912b87a2c49b1428419a5845154762ffee" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" dependencies = [ "bytes", "libc", @@ -3820,14 +3601,15 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" dependencies = [ "bytes", "futures-core", "futures-sink", "futures-util", + "libc", "pin-project-lite", "tokio", ] @@ -4018,12 +3800,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - [[package]] name = "uuid" version = "1.24.0" @@ -4203,27 +3979,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" -dependencies = [ - "windows-collections", - "windows-core", - "windows-future", - "windows-numerics", -] - -[[package]] -name = "windows-collections" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" -dependencies = [ - "windows-core", -] - [[package]] name = "windows-core" version = "0.62.2" @@ -4237,17 +3992,6 @@ dependencies = [ "windows-strings", ] -[[package]] -name = "windows-future" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" -dependencies = [ - "windows-core", - "windows-link", - "windows-threading", -] - [[package]] name = "windows-implement" version = "0.60.2" @@ -4276,16 +4020,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-numerics" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" -dependencies = [ - "windows-core", - "windows-link", -] - [[package]] name = "windows-result" version = "0.4.1" @@ -4310,16 +4044,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", + "windows-targets", ] [[package]] @@ -4337,40 +4062,14 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - -[[package]] -name = "windows-threading" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" -dependencies = [ - "windows-link", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] @@ -4379,96 +4078,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "wit-bindgen" version = "0.57.1" @@ -4525,18 +4176,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.54" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" +checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.54" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" +checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" dependencies = [ "proc-macro2", "quote", diff --git a/extensions/VSCode/Cargo.toml b/extensions/VSCode/Cargo.toml index 22ab5fd7..fb712ea1 100644 --- a/extensions/VSCode/Cargo.toml +++ b/extensions/VSCode/Cargo.toml @@ -34,12 +34,17 @@ readme = "../README.md" repository = "https://github.com/bjones1/CodeChat_Editor" version = "0.2.0" +# `cargo machete` doesn't scan `build.rs`, so it can't see `napi-build` used +# there. +[package.metadata.cargo-machete] +ignored = ["napi-build"] + [lib] crate-type = ["cdylib"] [dependencies] codechat-editor-server = { path = "../../server" } -log = "0.4.28" +log = { version = "0.4", features = ["release_max_level_warn"] } napi = { version = "3.0.0", features = ["tokio_rt"] } napi-derive = "3.0.0" serde_json = "1.0.145" @@ -52,3 +57,8 @@ codegen-units = 1 lto = true panic = "abort" strip = "symbols" + +[lints.clippy] +pedantic = { level = "warn", priority = 0 } +missing_errors_doc = { level = "allow", priority = 1 } +doc_markdown = { level = "allow", priority = 1 } diff --git a/extensions/VSCode/package.json b/extensions/VSCode/package.json index 3ab7f80f..d6cc5914 100644 --- a/extensions/VSCode/package.json +++ b/extensions/VSCode/package.json @@ -118,26 +118,26 @@ "@emnapi/core": "^1.11.2", "@emnapi/runtime": "^1.11.2", "@eslint/js": "^10.0.1", - "@napi-rs/cli": "^3.7.3", + "@napi-rs/cli": "^3.7.4", "@tybys/wasm-util": "^0.10.3", "@types/escape-html": "^1.0.4", - "@types/node": "^24.13.3", + "@types/node": "^26.1.1", "@types/vscode": "1.61.0", - "@typescript-eslint/eslint-plugin": "^8.64.0", - "@typescript-eslint/parser": "^8.64.0", + "@typescript-eslint/eslint-plugin": "^8.65.0", + "@typescript-eslint/parser": "^8.65.0", + "@typescript/native": "npm:typescript@^7.0.2", "@vscode/vsce": "^3.9.2", "chalk": "^5.6.2", "esbuild": "^0.28.1", - "eslint": "^10.7.0", + "eslint": "^10.8.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-import": "^2.32.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^5.5.6", "npm-run-all2": "^9.0.2", - "prettier": "^3.9.5", + "prettier": "^3.9.6", "typescript": "npm:@typescript/typescript6@^6.0.2", - "@typescript/native": "npm:typescript@^7.0.2", - "typescript-eslint": "^8.64.0" + "typescript-eslint": "^8.65.0" }, "optionalDependencies": { "bufferutil": "^4.1.0" diff --git a/extensions/VSCode/pnpm-lock.yaml b/extensions/VSCode/pnpm-lock.yaml index c7fe48c6..710e1f07 100644 --- a/extensions/VSCode/pnpm-lock.yaml +++ b/extensions/VSCode/pnpm-lock.yaml @@ -20,10 +20,10 @@ importers: version: 1.11.2 '@eslint/js': specifier: ^10.0.1 - version: 10.0.1(eslint@10.7.0(supports-color@7.2.0)) + version: 10.0.1(eslint@10.8.0(supports-color@7.2.0)) '@napi-rs/cli': - specifier: ^3.7.3 - version: 3.7.3(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.13.3)(supports-color@7.2.0) + specifier: ^3.7.4 + version: 3.7.4(@emnapi/runtime@1.11.2)(@types/node@26.1.1)(supports-color@7.2.0) '@tybys/wasm-util': specifier: ^0.10.3 version: 0.10.3 @@ -31,17 +31,17 @@ importers: specifier: ^1.0.4 version: 1.0.4 '@types/node': - specifier: ^24.13.3 - version: 24.13.3 + specifier: ^26.1.1 + version: 26.1.1 '@types/vscode': specifier: 1.61.0 version: 1.61.0 '@typescript-eslint/eslint-plugin': - specifier: ^8.64.0 - version: 8.64.0(@typescript-eslint/parser@8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@7.2.0))(supports-color@7.2.0))(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@7.2.0))(supports-color@7.2.0) + specifier: ^8.65.0 + version: 8.65.0(@typescript-eslint/parser@8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@7.2.0))(supports-color@7.2.0))(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@7.2.0))(supports-color@7.2.0) '@typescript-eslint/parser': - specifier: ^8.64.0 - version: 8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@7.2.0))(supports-color@7.2.0) + specifier: ^8.65.0 + version: 8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@7.2.0))(supports-color@7.2.0) '@typescript/native': specifier: npm:typescript@^7.0.2 version: typescript@7.0.2 @@ -55,32 +55,32 @@ importers: specifier: ^0.28.1 version: 0.28.1 eslint: - specifier: ^10.7.0 - version: 10.7.0(supports-color@7.2.0) + specifier: ^10.8.0 + version: 10.8.0(supports-color@7.2.0) eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@10.7.0(supports-color@7.2.0)) + version: 10.1.8(eslint@10.8.0(supports-color@7.2.0)) eslint-plugin-import: specifier: ^2.32.0 - version: 2.32.0(@typescript-eslint/parser@8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@7.2.0))(supports-color@7.2.0))(eslint@10.7.0(supports-color@7.2.0))(supports-color@7.2.0) + version: 2.32.0(@typescript-eslint/parser@8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@7.2.0))(supports-color@7.2.0))(eslint@10.8.0(supports-color@7.2.0))(supports-color@7.2.0) eslint-plugin-node: specifier: ^11.1.0 - version: 11.1.0(eslint@10.7.0(supports-color@7.2.0)) + version: 11.1.0(eslint@10.8.0(supports-color@7.2.0)) eslint-plugin-prettier: specifier: ^5.5.6 - version: 5.5.6(eslint-config-prettier@10.1.8(eslint@10.7.0(supports-color@7.2.0)))(eslint@10.7.0(supports-color@7.2.0))(prettier@3.9.5) + version: 5.5.6(eslint-config-prettier@10.1.8(eslint@10.8.0(supports-color@7.2.0)))(eslint@10.8.0(supports-color@7.2.0))(prettier@3.9.6) npm-run-all2: specifier: ^9.0.2 version: 9.0.2 prettier: - specifier: ^3.9.5 - version: 3.9.5 + specifier: ^3.9.6 + version: 3.9.6 typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' typescript-eslint: - specifier: ^8.64.0 - version: 8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@7.2.0))(supports-color@7.2.0) + specifier: ^8.65.0 + version: 8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@7.2.0))(supports-color@7.2.0) optionalDependencies: bufferutil: specifier: ^4.1.0 @@ -134,8 +134,8 @@ packages: resolution: {integrity: sha512-yDhtBOGDCdK9ipQ9g3+wmlMEPnZx2pXaDicDd9jYyR1L+7lEbvEohTDmF5qejZDutZY3m9pWPxeYxzNC701A2w==} engines: {node: '>=0.8.0'} - '@azure/msal-node@5.4.1': - resolution: {integrity: sha512-yqgoyOIMCH7TNaSLMBTP+4LUlbMMf1zgC8nzOFG95lmW82CmsAEtUT0J93e4BdqDcnX5qle/9X+yb7A8Mw9M0g==} + '@azure/msal-node@5.4.2': + resolution: {integrity: sha512-fnqOpGYAV+i0RH4W5HB6Oy1IhqGZoCdnp7Y2Sa9k18FlT8aBkCA7L8Hv19hUHLDUK6kVjUO29AfnGX6wgAHyNg==} engines: {node: '>=20'} '@babel/code-frame@7.29.7': @@ -149,9 +149,18 @@ packages: '@emnapi/core@1.11.2': resolution: {integrity: sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==} + '@emnapi/core@1.9.2': + resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} + '@emnapi/runtime@1.11.2': resolution: {integrity: sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==} + '@emnapi/runtime@1.9.2': + resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@emnapi/wasi-threads@1.2.2': resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} @@ -311,8 +320,8 @@ packages: cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.9.1': - resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + '@eslint-community/eslint-utils@4.10.1': + resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 @@ -325,8 +334,8 @@ packages: resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/config-helpers@0.6.0': - resolution: {integrity: sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==} + '@eslint/config-helpers@0.7.0': + resolution: {integrity: sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@eslint/core@1.2.1': @@ -504,8 +513,8 @@ packages: '@types/node': optional: true - '@napi-rs/cli@3.7.3': - resolution: {integrity: sha512-iu5BOoYjYVixp5jwE7JniHvg72XuKWXUfXteu+6Gt/XY4/mslsS+Qbipleg1+3CAUGHkWc+ebaMJj7Pc93BXSQ==} + '@napi-rs/cli@3.7.4': + resolution: {integrity: sha512-idELIUceJ9zPgx/shcMt1fSSsteFG68v56RIXi4qUm7OYuJOt7CoMj2KnHVmbOqXgUHWQU1lCULrtQdZDG4F5A==} engines: {node: '>= 16'} hasBin: true peerDependencies: @@ -772,90 +781,90 @@ packages: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 - '@napi-rs/wasm-tools-android-arm-eabi@1.0.1': - resolution: {integrity: sha512-lr07E/l571Gft5v4aA1dI8koJEmF1F0UigBbsqg9OWNzg80H3lDPO+auv85y3T/NHE3GirDk7x/D3sLO57vayw==} - engines: {node: '>= 10'} + '@napi-rs/wasm-tools-android-arm-eabi@1.1.0': + resolution: {integrity: sha512-p6J8PB59I8d/XItXB/go5JH6nKW+xIbpzaL43EBTV0hi7mrS/Z4gs+MsB04ZrlqZN29BdZV8fChRyasuXLhRaA==} + engines: {node: '>= 12.22.0'} cpu: [arm] os: [android] - '@napi-rs/wasm-tools-android-arm64@1.0.1': - resolution: {integrity: sha512-WDR7S+aRLV6LtBJAg5fmjKkTZIdrEnnQxgdsb7Cf8pYiMWBHLU+LC49OUVppQ2YSPY0+GeYm9yuZWW3kLjJ7Bg==} - engines: {node: '>= 10'} + '@napi-rs/wasm-tools-android-arm64@1.1.0': + resolution: {integrity: sha512-lWoKN3suypeBSCIRPIw+++sH9V2K6nQkhtdt1opu7XY3v9JwLs6Gw063HWRqkNjphlYpkd/Qy8XcfSPGbJj7nQ==} + engines: {node: '>= 12.22.0'} cpu: [arm64] os: [android] - '@napi-rs/wasm-tools-darwin-arm64@1.0.1': - resolution: {integrity: sha512-qWTI+EEkiN0oIn/N2gQo7+TVYil+AJ20jjuzD2vATS6uIjVz+Updeqmszi7zq7rdFTLp6Ea3/z4kDKIfZwmR9g==} - engines: {node: '>= 10'} + '@napi-rs/wasm-tools-darwin-arm64@1.1.0': + resolution: {integrity: sha512-jfw5vyNDUf6oe0kP8lMveFN9U7cLk1cUosS7uMIfw/xmqmopYfKQ198DAx2g/6aEF7Tm+CqER2gpMpYKui30LA==} + engines: {node: '>= 12.22.0'} cpu: [arm64] os: [darwin] - '@napi-rs/wasm-tools-darwin-x64@1.0.1': - resolution: {integrity: sha512-bA6hubqtHROR5UI3tToAF/c6TDmaAgF0SWgo4rADHtQ4wdn0JeogvOk50gs2TYVhKPE2ZD2+qqt7oBKB+sxW3A==} - engines: {node: '>= 10'} + '@napi-rs/wasm-tools-darwin-x64@1.1.0': + resolution: {integrity: sha512-R+pjeudAB7BYdH1vKkOJM61Tfv5jB6uXkxmFscYd+KKpdUpWBlNG+s4hr0w4i1rMBM91VhIAETZn2pz+MDHK9A==} + engines: {node: '>= 12.22.0'} cpu: [x64] os: [darwin] - '@napi-rs/wasm-tools-freebsd-x64@1.0.1': - resolution: {integrity: sha512-90+KLBkD9hZEjPQW1MDfwSt5J1L46EUKacpCZWyRuL6iIEO5CgWU0V/JnEgFsDOGyyYtiTvHc5bUdUTWd4I9Vg==} - engines: {node: '>= 10'} + '@napi-rs/wasm-tools-freebsd-x64@1.1.0': + resolution: {integrity: sha512-hQJTe+aazrT++Vgm6I4lUd9099ItUCFYdd+aKg6Ys6nax6d/cZ1barDLTwA2lwOoVDsXMekJI/FOL6ZvVlIYBg==} + engines: {node: '>= 12.22.0'} cpu: [x64] os: [freebsd] - '@napi-rs/wasm-tools-linux-arm64-gnu@1.0.1': - resolution: {integrity: sha512-rG0QlS65x9K/u3HrKafDf8cFKj5wV2JHGfl8abWgKew0GVPyp6vfsDweOwHbWAjcHtp2LHi6JHoW80/MTHm52Q==} - engines: {node: '>= 10'} + '@napi-rs/wasm-tools-linux-arm64-gnu@1.1.0': + resolution: {integrity: sha512-1TAXJxUHsWGar90k3W/MknavvBMwOWzjh7Q6Spxo8twRcWJbBD5Kow/Q2KhhDq5hxh2sKGDXn3uLc1tdtz4WUg==} + engines: {node: '>= 12.22.0'} cpu: [arm64] os: [linux] libc: [glibc] - '@napi-rs/wasm-tools-linux-arm64-musl@1.0.1': - resolution: {integrity: sha512-jAasbIvjZXCgX0TCuEFQr+4D6Lla/3AAVx2LmDuMjgG4xoIXzjKWl7c4chuaD+TI+prWT0X6LJcdzFT+ROKGHQ==} - engines: {node: '>= 10'} + '@napi-rs/wasm-tools-linux-arm64-musl@1.1.0': + resolution: {integrity: sha512-7rw3nlubTjNAVRH2LwphCxHy1b/N2/TerXocQ6XRn4Q+buaY1Z7P/hbdALy1i1ex2yfOU2Xcij7ib7ZLi/lKfw==} + engines: {node: '>= 12.22.0'} cpu: [arm64] os: [linux] libc: [musl] - '@napi-rs/wasm-tools-linux-x64-gnu@1.0.1': - resolution: {integrity: sha512-Plgk5rPqqK2nocBGajkMVbGm010Z7dnUgq0wtnYRZbzWWxwWcXfZMPa8EYxrK4eE8SzpI7VlZP1tdVsdjgGwMw==} - engines: {node: '>= 10'} + '@napi-rs/wasm-tools-linux-x64-gnu@1.1.0': + resolution: {integrity: sha512-1sel0t9MRjI/tdT89M8Dd6gPfANeeFP24Xa46R11WeHNwhjsXXZh+xUk50uWCRTSGcaCy3ugm3AMK/lmHYQJkg==} + engines: {node: '>= 12.22.0'} cpu: [x64] os: [linux] libc: [glibc] - '@napi-rs/wasm-tools-linux-x64-musl@1.0.1': - resolution: {integrity: sha512-GW7AzGuWxtQkyHknHWYFdR0CHmW6is8rG2Rf4V6GNmMpmwtXt/ItWYWtBe4zqJWycMNazpfZKSw/BpT7/MVCXQ==} - engines: {node: '>= 10'} + '@napi-rs/wasm-tools-linux-x64-musl@1.1.0': + resolution: {integrity: sha512-o2jH5AMfor4EKF2HII1LBnMQxoWu7+usPifTEY8Zk6e9OiSi4EJkAXf9v3ANlX7TI2V/cUEV34OEW7r10GiVIA==} + engines: {node: '>= 12.22.0'} cpu: [x64] os: [linux] libc: [musl] - '@napi-rs/wasm-tools-wasm32-wasi@1.0.1': - resolution: {integrity: sha512-/nQVSTrqSsn7YdAc2R7Ips/tnw5SPUcl3D7QrXCNGPqjbatIspnaexvaOYNyKMU6xPu+pc0BTnKVmqhlJJCPLA==} + '@napi-rs/wasm-tools-wasm32-wasi@1.1.0': + resolution: {integrity: sha512-s6YDtDR1UWrsqJPtaxf+JLYLceWVyn3l8OpQYElHkDhf3Qfz9R6Ba3S0OgznTBv38L5/TIHysQ9Q4yO73Z0csg==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@napi-rs/wasm-tools-win32-arm64-msvc@1.0.1': - resolution: {integrity: sha512-PFi7oJIBu5w7Qzh3dwFea3sHRO3pojMsaEnUIy22QvsW+UJfNQwJCryVrpoUt8m4QyZXI+saEq/0r4GwdoHYFQ==} - engines: {node: '>= 10'} + '@napi-rs/wasm-tools-win32-arm64-msvc@1.1.0': + resolution: {integrity: sha512-x+NuxbG84VxU68tU8w7Rf5lSyq0l584M6dVlke5DTweHYFZoMyeqkpbwEq+qsyAX6ivfipK8xRsmFwamb5uDnA==} + engines: {node: '>= 12.22.0'} cpu: [arm64] os: [win32] - '@napi-rs/wasm-tools-win32-ia32-msvc@1.0.1': - resolution: {integrity: sha512-gXkuYzxQsgkj05Zaq+KQTkHIN83dFAwMcTKa2aQcpYPRImFm2AQzEyLtpXmyCWzJ0F9ZYAOmbSyrNew8/us6bw==} - engines: {node: '>= 10'} + '@napi-rs/wasm-tools-win32-ia32-msvc@1.1.0': + resolution: {integrity: sha512-mdD96QDEp70SX67rXFTY6c725nVYeqEEjyDqzzbNh6u1APj7CI7IMNpMmvE75XbCRl4C2MHZVU4U6AWdAzvyQQ==} + engines: {node: '>= 12.22.0'} cpu: [ia32] os: [win32] - '@napi-rs/wasm-tools-win32-x64-msvc@1.0.1': - resolution: {integrity: sha512-rEAf05nol3e3eei2sRButmgXP+6ATgm0/38MKhz9Isne82T4rPIMYsCIFj0kOisaGeVwoi2fnm7O9oWp5YVnYQ==} - engines: {node: '>= 10'} + '@napi-rs/wasm-tools-win32-x64-msvc@1.1.0': + resolution: {integrity: sha512-bVVjuvhlyVX++3eJXfDR63cXdw1ay5QYac6iq0MKQw8wZARInTM+bXCtByDT4fzVFI3+7ZthYb/ERWRdBNIqgQ==} + engines: {node: '>= 12.22.0'} cpu: [x64] os: [win32] - '@napi-rs/wasm-tools@1.0.1': - resolution: {integrity: sha512-enkZYyuCdo+9jneCPE/0fjIta4wWnvVN9hBo2HuiMpRF0q3lzv1J6b/cl7i0mxZUKhBrV3aCKDBQnCOhwKbPmQ==} - engines: {node: '>= 10'} + '@napi-rs/wasm-tools@1.1.0': + resolution: {integrity: sha512-VjHyKEqXAwYZK+HY7iJctYvRm3TFEbaQxeZwvAG1QRkoo1a39phMY8J6x9tUEqJI03W6MysB8F2jacI6wvcx+w==} + engines: {node: '>= 12.22.0'} '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -1010,8 +1019,8 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - '@types/node@24.13.3': - resolution: {integrity: sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==} + '@types/node@26.1.1': + resolution: {integrity: sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -1022,63 +1031,63 @@ packages: '@types/vscode@1.61.0': resolution: {integrity: sha512-9k5Nwq45hkRwdfCFY+eKXeQQSbPoA114mF7U/4uJXRBJeGIO7MuJdhF1PnaDN+lllL9iKGQtd6FFXShBXMNaFg==} - '@typescript-eslint/eslint-plugin@8.64.0': - resolution: {integrity: sha512-CGvQPBxN3wZLu6Rz2kFUpZeoCm78xUic92ck39KPePkO1NPOwjCqdQnm5Q87tpWw9vcBvW8XLrDXjH9PWYtJ3Q==} + '@typescript-eslint/eslint-plugin@8.65.0': + resolution: {integrity: sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.64.0 + '@typescript-eslint/parser': ^8.65.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.64.0': - resolution: {integrity: sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw==} + '@typescript-eslint/parser@8.65.0': + resolution: {integrity: sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.64.0': - resolution: {integrity: sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg==} + '@typescript-eslint/project-service@8.65.0': + resolution: {integrity: sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.64.0': - resolution: {integrity: sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==} + '@typescript-eslint/scope-manager@8.65.0': + resolution: {integrity: sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.64.0': - resolution: {integrity: sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==} + '@typescript-eslint/tsconfig-utils@8.65.0': + resolution: {integrity: sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.64.0': - resolution: {integrity: sha512-XWG4Fmmv/6SvyS9nH8jWrKs6terwJvE8cyRt1CzYYqzp9OrPhCT4cMc/f7C6RZCwG+qMmiffJS1/qJP8G1URtg==} + '@typescript-eslint/type-utils@8.65.0': + resolution: {integrity: sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.64.0': - resolution: {integrity: sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==} + '@typescript-eslint/types@8.65.0': + resolution: {integrity: sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.64.0': - resolution: {integrity: sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA==} + '@typescript-eslint/typescript-estree@8.65.0': + resolution: {integrity: sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.64.0': - resolution: {integrity: sha512-aJUGVB3+U0htrrCjoA8qukw8cm8fNCGAxK/tVoS70k8aeb7DETKeFozRiVFIwEeN9WJLsjaP3ph8I60tY2XZoQ==} + '@typescript-eslint/utils@8.65.0': + resolution: {integrity: sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.64.0': - resolution: {integrity: sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==} + '@typescript-eslint/visitor-keys@8.65.0': + resolution: {integrity: sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@typescript/typescript-aix-ppc64@7.0.2': @@ -1376,9 +1385,9 @@ packages: brace-expansion@1.1.16: resolution: {integrity: sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==} - brace-expansion@5.0.7: - resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==} - engines: {node: 18 || 20 || >=22} + brace-expansion@5.0.8: + resolution: {integrity: sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==} + engines: {node: 20 || >=22} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} @@ -1644,8 +1653,8 @@ packages: resolution: {integrity: sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==} engines: {node: '>= 0.4'} - es-toolkit@1.49.0: - resolution: {integrity: sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g==} + es-toolkit@1.50.0: + resolution: {integrity: sha512-OyZKhUVvEep9ITEiwHn8GKnMRQIVqoSIX7WnRbkWgJkllCujilqP2rD0u979tkl8wqyc8ICwlc1UBVv/Sl1G6w==} esbuild@0.28.1: resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} @@ -1745,8 +1754,8 @@ packages: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@10.7.0: - resolution: {integrity: sha512-GVTD7s1vdIl6UYvAfriOPeY1Df8LIZjfofLvHwde+erDHGGuHyuM6xoxRxmHiebhYuD2p1vN4wWh0XzPARSGDQ==} + eslint@10.8.0: + resolution: {integrity: sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: @@ -1835,8 +1844,8 @@ packages: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} - flatted@3.4.2: - resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + flatted@3.4.3: + resolution: {integrity: sha512-/zipXxyO6rGvuNGDiULY9MvEGSkb2gaG4GGH4ygMi0ZZzyMHdUZBmntJmx5x1G2VuPytCwGN4xsJP6cw+sK+vQ==} for-each@0.3.5: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} @@ -1849,8 +1858,8 @@ packages: fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - fs-extra@11.3.6: - resolution: {integrity: sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==} + fs-extra@11.4.0: + resolution: {integrity: sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==} engines: {node: '>=14.14'} function-bind@1.1.2: @@ -2246,8 +2255,8 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} - mdurl@2.0.0: - resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + mdurl@2.1.0: + resolution: {integrity: sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg==} memorystream@0.3.1: resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} @@ -2389,8 +2398,8 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} - own-keys@1.0.1: - resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + own-keys@1.0.2: + resolution: {integrity: sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==} engines: {node: '>= 0.4'} p-limit@3.1.0: @@ -2401,8 +2410,8 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} - p-map@7.0.5: - resolution: {integrity: sha512-e8vJF4XdVkzqqSHguEMz41mQO1wKwxKm5ENrUJQUu9kLDCtn83cxbyHZcszr4QC5zEA7WffRRC4gsTecC7J9oA==} + p-map@7.0.6: + resolution: {integrity: sha512-I4Prw6ivkd6p8PiYR1tXASOAOBzIJwu0TB7fqaX0c/8c3QAehNYmX57EijyGGGBt3c/BIowGwV03RVBtXvHEVg==} engines: {node: '>=18'} parse-json@8.3.0: @@ -2484,8 +2493,8 @@ packages: resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==} engines: {node: '>=6.0.0'} - prettier@3.9.5: - resolution: {integrity: sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==} + prettier@3.9.6: + resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==} engines: {node: '>=14'} hasBin: true @@ -2585,8 +2594,8 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sax@1.6.0: - resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} + sax@1.6.1: + resolution: {integrity: sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==} engines: {node: '>=11.0.0'} secretlint@10.2.2: @@ -2818,8 +2827,8 @@ packages: typed-rest-client@1.8.11: resolution: {integrity: sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==} - typescript-eslint@8.64.0: - resolution: {integrity: sha512-0qg+pDNMnqYzqH9AnNK+39tejHvsShUOUUoRUgtnTGE7QuMZhiFDnozq8nHJVq+Wae6NMLKNWLg5WmkcC/ndyQ==} + typescript-eslint@8.65.0: + resolution: {integrity: sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -2845,11 +2854,11 @@ packages: underscore@1.13.8: resolution: {integrity: sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==} - undici-types@7.18.2: - resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + undici-types@8.3.0: + resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} - undici@7.28.0: - resolution: {integrity: sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==} + undici@7.29.0: + resolution: {integrity: sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==} engines: {node: '>=20.18.1'} unicorn-magic@0.1.0: @@ -3017,7 +3026,7 @@ snapshots: '@azure/core-util': 1.14.0(supports-color@7.2.0) '@azure/logger': 1.4.0(supports-color@7.2.0) '@azure/msal-browser': 5.17.1 - '@azure/msal-node': 5.4.1 + '@azure/msal-node': 5.4.2 open: 10.2.0 tslib: 2.8.1 transitivePeerDependencies: @@ -3036,7 +3045,7 @@ snapshots: '@azure/msal-common@16.11.2': {} - '@azure/msal-node@5.4.1': + '@azure/msal-node@5.4.2': dependencies: '@azure/msal-common': 16.11.2 jsonwebtoken: 9.0.3 @@ -3054,10 +3063,26 @@ snapshots: '@emnapi/wasi-threads': 1.2.2 tslib: 2.8.1 + '@emnapi/core@1.9.2': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.11.2': dependencies: tslib: 2.8.1 + '@emnapi/runtime@1.9.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/wasi-threads@1.2.2': dependencies: tslib: 2.8.1 @@ -3140,9 +3165,9 @@ snapshots: '@esbuild/win32-x64@0.28.1': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@10.7.0(supports-color@7.2.0))': + '@eslint-community/eslint-utils@4.10.1(eslint@10.8.0(supports-color@7.2.0))': dependencies: - eslint: 10.7.0(supports-color@7.2.0) + eslint: 10.8.0(supports-color@7.2.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -3155,7 +3180,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/config-helpers@0.6.0': + '@eslint/config-helpers@0.7.0': dependencies: '@eslint/core': 1.2.1 @@ -3163,9 +3188,9 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/js@10.0.1(eslint@10.7.0(supports-color@7.2.0))': + '@eslint/js@10.0.1(eslint@10.8.0(supports-color@7.2.0))': optionalDependencies: - eslint: 10.7.0(supports-color@7.2.0) + eslint: 10.8.0(supports-color@7.2.0) '@eslint/object-schema@3.0.5': {} @@ -3192,133 +3217,133 @@ snapshots: '@inquirer/ansi@2.0.7': {} - '@inquirer/checkbox@5.2.1(@types/node@24.13.3)': + '@inquirer/checkbox@5.2.1(@types/node@26.1.1)': dependencies: '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@24.13.3) + '@inquirer/core': 11.2.1(@types/node@26.1.1) '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@24.13.3) + '@inquirer/type': 4.0.7(@types/node@26.1.1) optionalDependencies: - '@types/node': 24.13.3 + '@types/node': 26.1.1 - '@inquirer/confirm@6.1.1(@types/node@24.13.3)': + '@inquirer/confirm@6.1.1(@types/node@26.1.1)': dependencies: - '@inquirer/core': 11.2.1(@types/node@24.13.3) - '@inquirer/type': 4.0.7(@types/node@24.13.3) + '@inquirer/core': 11.2.1(@types/node@26.1.1) + '@inquirer/type': 4.0.7(@types/node@26.1.1) optionalDependencies: - '@types/node': 24.13.3 + '@types/node': 26.1.1 - '@inquirer/core@11.2.1(@types/node@24.13.3)': + '@inquirer/core@11.2.1(@types/node@26.1.1)': dependencies: '@inquirer/ansi': 2.0.7 '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@24.13.3) + '@inquirer/type': 4.0.7(@types/node@26.1.1) cli-width: 4.1.0 fast-wrap-ansi: 0.2.2 mute-stream: 3.0.0 signal-exit: 4.1.0 optionalDependencies: - '@types/node': 24.13.3 + '@types/node': 26.1.1 - '@inquirer/editor@5.2.2(@types/node@24.13.3)': + '@inquirer/editor@5.2.2(@types/node@26.1.1)': dependencies: - '@inquirer/core': 11.2.1(@types/node@24.13.3) - '@inquirer/external-editor': 3.0.3(@types/node@24.13.3) - '@inquirer/type': 4.0.7(@types/node@24.13.3) + '@inquirer/core': 11.2.1(@types/node@26.1.1) + '@inquirer/external-editor': 3.0.3(@types/node@26.1.1) + '@inquirer/type': 4.0.7(@types/node@26.1.1) optionalDependencies: - '@types/node': 24.13.3 + '@types/node': 26.1.1 - '@inquirer/expand@5.1.1(@types/node@24.13.3)': + '@inquirer/expand@5.1.1(@types/node@26.1.1)': dependencies: - '@inquirer/core': 11.2.1(@types/node@24.13.3) - '@inquirer/type': 4.0.7(@types/node@24.13.3) + '@inquirer/core': 11.2.1(@types/node@26.1.1) + '@inquirer/type': 4.0.7(@types/node@26.1.1) optionalDependencies: - '@types/node': 24.13.3 + '@types/node': 26.1.1 - '@inquirer/external-editor@3.0.3(@types/node@24.13.3)': + '@inquirer/external-editor@3.0.3(@types/node@26.1.1)': dependencies: chardet: 2.2.0 iconv-lite: 0.7.3 optionalDependencies: - '@types/node': 24.13.3 + '@types/node': 26.1.1 '@inquirer/figures@2.0.7': {} - '@inquirer/input@5.1.2(@types/node@24.13.3)': + '@inquirer/input@5.1.2(@types/node@26.1.1)': dependencies: - '@inquirer/core': 11.2.1(@types/node@24.13.3) - '@inquirer/type': 4.0.7(@types/node@24.13.3) + '@inquirer/core': 11.2.1(@types/node@26.1.1) + '@inquirer/type': 4.0.7(@types/node@26.1.1) optionalDependencies: - '@types/node': 24.13.3 + '@types/node': 26.1.1 - '@inquirer/number@4.1.1(@types/node@24.13.3)': + '@inquirer/number@4.1.1(@types/node@26.1.1)': dependencies: - '@inquirer/core': 11.2.1(@types/node@24.13.3) - '@inquirer/type': 4.0.7(@types/node@24.13.3) + '@inquirer/core': 11.2.1(@types/node@26.1.1) + '@inquirer/type': 4.0.7(@types/node@26.1.1) optionalDependencies: - '@types/node': 24.13.3 + '@types/node': 26.1.1 - '@inquirer/password@5.1.1(@types/node@24.13.3)': + '@inquirer/password@5.1.1(@types/node@26.1.1)': dependencies: '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@24.13.3) - '@inquirer/type': 4.0.7(@types/node@24.13.3) + '@inquirer/core': 11.2.1(@types/node@26.1.1) + '@inquirer/type': 4.0.7(@types/node@26.1.1) optionalDependencies: - '@types/node': 24.13.3 - - '@inquirer/prompts@8.5.2(@types/node@24.13.3)': - dependencies: - '@inquirer/checkbox': 5.2.1(@types/node@24.13.3) - '@inquirer/confirm': 6.1.1(@types/node@24.13.3) - '@inquirer/editor': 5.2.2(@types/node@24.13.3) - '@inquirer/expand': 5.1.1(@types/node@24.13.3) - '@inquirer/input': 5.1.2(@types/node@24.13.3) - '@inquirer/number': 4.1.1(@types/node@24.13.3) - '@inquirer/password': 5.1.1(@types/node@24.13.3) - '@inquirer/rawlist': 5.3.1(@types/node@24.13.3) - '@inquirer/search': 4.2.1(@types/node@24.13.3) - '@inquirer/select': 5.2.1(@types/node@24.13.3) + '@types/node': 26.1.1 + + '@inquirer/prompts@8.5.2(@types/node@26.1.1)': + dependencies: + '@inquirer/checkbox': 5.2.1(@types/node@26.1.1) + '@inquirer/confirm': 6.1.1(@types/node@26.1.1) + '@inquirer/editor': 5.2.2(@types/node@26.1.1) + '@inquirer/expand': 5.1.1(@types/node@26.1.1) + '@inquirer/input': 5.1.2(@types/node@26.1.1) + '@inquirer/number': 4.1.1(@types/node@26.1.1) + '@inquirer/password': 5.1.1(@types/node@26.1.1) + '@inquirer/rawlist': 5.3.1(@types/node@26.1.1) + '@inquirer/search': 4.2.1(@types/node@26.1.1) + '@inquirer/select': 5.2.1(@types/node@26.1.1) optionalDependencies: - '@types/node': 24.13.3 + '@types/node': 26.1.1 - '@inquirer/rawlist@5.3.1(@types/node@24.13.3)': + '@inquirer/rawlist@5.3.1(@types/node@26.1.1)': dependencies: - '@inquirer/core': 11.2.1(@types/node@24.13.3) - '@inquirer/type': 4.0.7(@types/node@24.13.3) + '@inquirer/core': 11.2.1(@types/node@26.1.1) + '@inquirer/type': 4.0.7(@types/node@26.1.1) optionalDependencies: - '@types/node': 24.13.3 + '@types/node': 26.1.1 - '@inquirer/search@4.2.1(@types/node@24.13.3)': + '@inquirer/search@4.2.1(@types/node@26.1.1)': dependencies: - '@inquirer/core': 11.2.1(@types/node@24.13.3) + '@inquirer/core': 11.2.1(@types/node@26.1.1) '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@24.13.3) + '@inquirer/type': 4.0.7(@types/node@26.1.1) optionalDependencies: - '@types/node': 24.13.3 + '@types/node': 26.1.1 - '@inquirer/select@5.2.1(@types/node@24.13.3)': + '@inquirer/select@5.2.1(@types/node@26.1.1)': dependencies: '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@24.13.3) + '@inquirer/core': 11.2.1(@types/node@26.1.1) '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@24.13.3) + '@inquirer/type': 4.0.7(@types/node@26.1.1) optionalDependencies: - '@types/node': 24.13.3 + '@types/node': 26.1.1 - '@inquirer/type@4.0.7(@types/node@24.13.3)': + '@inquirer/type@4.0.7(@types/node@26.1.1)': optionalDependencies: - '@types/node': 24.13.3 + '@types/node': 26.1.1 - '@napi-rs/cli@3.7.3(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.13.3)(supports-color@7.2.0)': + '@napi-rs/cli@3.7.4(@emnapi/runtime@1.11.2)(@types/node@26.1.1)(supports-color@7.2.0)': dependencies: - '@inquirer/prompts': 8.5.2(@types/node@24.13.3) + '@inquirer/prompts': 8.5.2(@types/node@26.1.1) '@napi-rs/cross-toolchain': 1.0.3(supports-color@7.2.0) - '@napi-rs/wasm-tools': 1.0.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) + '@napi-rs/wasm-tools': 1.1.0 '@octokit/rest': 22.0.1 clipanion: 4.0.0-rc.4(typanion@3.14.0) colorette: 2.0.20 emnapi: 1.11.2 - es-toolkit: 1.49.0 + es-toolkit: 1.50.0 js-yaml: 4.3.0 obug: 2.1.4 semver: 7.8.5 @@ -3326,7 +3351,6 @@ snapshots: optionalDependencies: '@emnapi/runtime': 1.11.2 transitivePeerDependencies: - - '@emnapi/core' - '@napi-rs/cross-toolchain-arm64-target-aarch64' - '@napi-rs/cross-toolchain-arm64-target-armv7' - '@napi-rs/cross-toolchain-arm64-target-ppc64le' @@ -3502,68 +3526,71 @@ snapshots: '@tybys/wasm-util': 0.10.3 optional: true - '@napi-rs/wasm-tools-android-arm-eabi@1.0.1': + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': + dependencies: + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@tybys/wasm-util': 0.10.3 + optional: true + + '@napi-rs/wasm-tools-android-arm-eabi@1.1.0': optional: true - '@napi-rs/wasm-tools-android-arm64@1.0.1': + '@napi-rs/wasm-tools-android-arm64@1.1.0': optional: true - '@napi-rs/wasm-tools-darwin-arm64@1.0.1': + '@napi-rs/wasm-tools-darwin-arm64@1.1.0': optional: true - '@napi-rs/wasm-tools-darwin-x64@1.0.1': + '@napi-rs/wasm-tools-darwin-x64@1.1.0': optional: true - '@napi-rs/wasm-tools-freebsd-x64@1.0.1': + '@napi-rs/wasm-tools-freebsd-x64@1.1.0': optional: true - '@napi-rs/wasm-tools-linux-arm64-gnu@1.0.1': + '@napi-rs/wasm-tools-linux-arm64-gnu@1.1.0': optional: true - '@napi-rs/wasm-tools-linux-arm64-musl@1.0.1': + '@napi-rs/wasm-tools-linux-arm64-musl@1.1.0': optional: true - '@napi-rs/wasm-tools-linux-x64-gnu@1.0.1': + '@napi-rs/wasm-tools-linux-x64-gnu@1.1.0': optional: true - '@napi-rs/wasm-tools-linux-x64-musl@1.0.1': + '@napi-rs/wasm-tools-linux-x64-musl@1.1.0': optional: true - '@napi-rs/wasm-tools-wasm32-wasi@1.0.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)': + '@napi-rs/wasm-tools-wasm32-wasi@1.1.0': dependencies: - '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) - transitivePeerDependencies: - - '@emnapi/core' - - '@emnapi/runtime' + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) optional: true - '@napi-rs/wasm-tools-win32-arm64-msvc@1.0.1': + '@napi-rs/wasm-tools-win32-arm64-msvc@1.1.0': optional: true - '@napi-rs/wasm-tools-win32-ia32-msvc@1.0.1': + '@napi-rs/wasm-tools-win32-ia32-msvc@1.1.0': optional: true - '@napi-rs/wasm-tools-win32-x64-msvc@1.0.1': + '@napi-rs/wasm-tools-win32-x64-msvc@1.1.0': optional: true - '@napi-rs/wasm-tools@1.0.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)': + '@napi-rs/wasm-tools@1.1.0': optionalDependencies: - '@napi-rs/wasm-tools-android-arm-eabi': 1.0.1 - '@napi-rs/wasm-tools-android-arm64': 1.0.1 - '@napi-rs/wasm-tools-darwin-arm64': 1.0.1 - '@napi-rs/wasm-tools-darwin-x64': 1.0.1 - '@napi-rs/wasm-tools-freebsd-x64': 1.0.1 - '@napi-rs/wasm-tools-linux-arm64-gnu': 1.0.1 - '@napi-rs/wasm-tools-linux-arm64-musl': 1.0.1 - '@napi-rs/wasm-tools-linux-x64-gnu': 1.0.1 - '@napi-rs/wasm-tools-linux-x64-musl': 1.0.1 - '@napi-rs/wasm-tools-wasm32-wasi': 1.0.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) - '@napi-rs/wasm-tools-win32-arm64-msvc': 1.0.1 - '@napi-rs/wasm-tools-win32-ia32-msvc': 1.0.1 - '@napi-rs/wasm-tools-win32-x64-msvc': 1.0.1 - transitivePeerDependencies: - - '@emnapi/core' - - '@emnapi/runtime' + '@napi-rs/wasm-tools-android-arm-eabi': 1.1.0 + '@napi-rs/wasm-tools-android-arm64': 1.1.0 + '@napi-rs/wasm-tools-darwin-arm64': 1.1.0 + '@napi-rs/wasm-tools-darwin-x64': 1.1.0 + '@napi-rs/wasm-tools-freebsd-x64': 1.1.0 + '@napi-rs/wasm-tools-linux-arm64-gnu': 1.1.0 + '@napi-rs/wasm-tools-linux-arm64-musl': 1.1.0 + '@napi-rs/wasm-tools-linux-x64-gnu': 1.1.0 + '@napi-rs/wasm-tools-linux-x64-musl': 1.1.0 + '@napi-rs/wasm-tools-wasm32-wasi': 1.1.0 + '@napi-rs/wasm-tools-win32-arm64-msvc': 1.1.0 + '@napi-rs/wasm-tools-win32-ia32-msvc': 1.1.0 + '@napi-rs/wasm-tools-win32-x64-msvc': 1.1.0 '@nodelib/fs.scandir@2.1.5': dependencies: @@ -3693,7 +3720,7 @@ snapshots: '@secretlint/source-creator': 10.2.2 '@secretlint/types': 10.2.2 debug: 4.4.3(supports-color@7.2.0) - p-map: 7.0.5 + p-map: 7.0.6 transitivePeerDependencies: - supports-color @@ -3763,9 +3790,9 @@ snapshots: '@types/json5@0.0.29': {} - '@types/node@24.13.3': + '@types/node@26.1.1': dependencies: - undici-types: 7.18.2 + undici-types: 8.3.0 '@types/normalize-package-data@2.4.4': {} @@ -3773,15 +3800,15 @@ snapshots: '@types/vscode@1.61.0': {} - '@typescript-eslint/eslint-plugin@8.64.0(@typescript-eslint/parser@8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@7.2.0))(supports-color@7.2.0))(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@7.2.0))(supports-color@7.2.0)': + '@typescript-eslint/eslint-plugin@8.65.0(@typescript-eslint/parser@8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@7.2.0))(supports-color@7.2.0))(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@7.2.0))(supports-color@7.2.0)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@7.2.0))(supports-color@7.2.0) - '@typescript-eslint/scope-manager': 8.64.0 - '@typescript-eslint/type-utils': 8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@7.2.0))(supports-color@7.2.0) - '@typescript-eslint/utils': 8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@7.2.0))(supports-color@7.2.0) - '@typescript-eslint/visitor-keys': 8.64.0 - eslint: 10.7.0(supports-color@7.2.0) + '@typescript-eslint/parser': 8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@7.2.0))(supports-color@7.2.0) + '@typescript-eslint/scope-manager': 8.65.0 + '@typescript-eslint/type-utils': 8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@7.2.0))(supports-color@7.2.0) + '@typescript-eslint/utils': 8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@7.2.0))(supports-color@7.2.0) + '@typescript-eslint/visitor-keys': 8.65.0 + eslint: 10.8.0(supports-color@7.2.0) ignore: 7.0.6 natural-compare: 1.4.0 ts-api-utils: 2.5.0(@typescript/typescript6@6.0.2) @@ -3789,56 +3816,56 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@7.2.0))(supports-color@7.2.0)': + '@typescript-eslint/parser@8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@7.2.0))(supports-color@7.2.0)': dependencies: - '@typescript-eslint/scope-manager': 8.64.0 - '@typescript-eslint/types': 8.64.0 - '@typescript-eslint/typescript-estree': 8.64.0(@typescript/typescript6@6.0.2)(supports-color@7.2.0) - '@typescript-eslint/visitor-keys': 8.64.0 + '@typescript-eslint/scope-manager': 8.65.0 + '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/typescript-estree': 8.65.0(@typescript/typescript6@6.0.2)(supports-color@7.2.0) + '@typescript-eslint/visitor-keys': 8.65.0 debug: 4.4.3(supports-color@7.2.0) - eslint: 10.7.0(supports-color@7.2.0) + eslint: 10.8.0(supports-color@7.2.0) typescript: '@typescript/typescript6@6.0.2' transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.64.0(@typescript/typescript6@6.0.2)(supports-color@7.2.0)': + '@typescript-eslint/project-service@8.65.0(@typescript/typescript6@6.0.2)(supports-color@7.2.0)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.64.0(@typescript/typescript6@6.0.2) - '@typescript-eslint/types': 8.64.0 + '@typescript-eslint/tsconfig-utils': 8.65.0(@typescript/typescript6@6.0.2) + '@typescript-eslint/types': 8.65.0 debug: 4.4.3(supports-color@7.2.0) typescript: '@typescript/typescript6@6.0.2' transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.64.0': + '@typescript-eslint/scope-manager@8.65.0': dependencies: - '@typescript-eslint/types': 8.64.0 - '@typescript-eslint/visitor-keys': 8.64.0 + '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/visitor-keys': 8.65.0 - '@typescript-eslint/tsconfig-utils@8.64.0(@typescript/typescript6@6.0.2)': + '@typescript-eslint/tsconfig-utils@8.65.0(@typescript/typescript6@6.0.2)': dependencies: typescript: '@typescript/typescript6@6.0.2' - '@typescript-eslint/type-utils@8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@7.2.0))(supports-color@7.2.0)': + '@typescript-eslint/type-utils@8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@7.2.0))(supports-color@7.2.0)': dependencies: - '@typescript-eslint/types': 8.64.0 - '@typescript-eslint/typescript-estree': 8.64.0(@typescript/typescript6@6.0.2)(supports-color@7.2.0) - '@typescript-eslint/utils': 8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@7.2.0))(supports-color@7.2.0) + '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/typescript-estree': 8.65.0(@typescript/typescript6@6.0.2)(supports-color@7.2.0) + '@typescript-eslint/utils': 8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@7.2.0))(supports-color@7.2.0) debug: 4.4.3(supports-color@7.2.0) - eslint: 10.7.0(supports-color@7.2.0) + eslint: 10.8.0(supports-color@7.2.0) ts-api-utils: 2.5.0(@typescript/typescript6@6.0.2) typescript: '@typescript/typescript6@6.0.2' transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.64.0': {} + '@typescript-eslint/types@8.65.0': {} - '@typescript-eslint/typescript-estree@8.64.0(@typescript/typescript6@6.0.2)(supports-color@7.2.0)': + '@typescript-eslint/typescript-estree@8.65.0(@typescript/typescript6@6.0.2)(supports-color@7.2.0)': dependencies: - '@typescript-eslint/project-service': 8.64.0(@typescript/typescript6@6.0.2)(supports-color@7.2.0) - '@typescript-eslint/tsconfig-utils': 8.64.0(@typescript/typescript6@6.0.2) - '@typescript-eslint/types': 8.64.0 - '@typescript-eslint/visitor-keys': 8.64.0 + '@typescript-eslint/project-service': 8.65.0(@typescript/typescript6@6.0.2)(supports-color@7.2.0) + '@typescript-eslint/tsconfig-utils': 8.65.0(@typescript/typescript6@6.0.2) + '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/visitor-keys': 8.65.0 debug: 4.4.3(supports-color@7.2.0) minimatch: 10.2.5 semver: 7.8.5 @@ -3848,20 +3875,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@7.2.0))(supports-color@7.2.0)': + '@typescript-eslint/utils@8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@7.2.0))(supports-color@7.2.0)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.7.0(supports-color@7.2.0)) - '@typescript-eslint/scope-manager': 8.64.0 - '@typescript-eslint/types': 8.64.0 - '@typescript-eslint/typescript-estree': 8.64.0(@typescript/typescript6@6.0.2)(supports-color@7.2.0) - eslint: 10.7.0(supports-color@7.2.0) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.0(supports-color@7.2.0)) + '@typescript-eslint/scope-manager': 8.65.0 + '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/typescript-estree': 8.65.0(@typescript/typescript6@6.0.2)(supports-color@7.2.0) + eslint: 10.8.0(supports-color@7.2.0) typescript: '@typescript/typescript6@6.0.2' transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.64.0': + '@typescript-eslint/visitor-keys@8.65.0': dependencies: - '@typescript-eslint/types': 8.64.0 + '@typescript-eslint/types': 8.65.0 eslint-visitor-keys: 5.0.1 '@typescript/typescript-aix-ppc64@7.0.2': @@ -4143,7 +4170,7 @@ snapshots: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@5.0.7: + brace-expansion@5.0.8: dependencies: balanced-match: 4.0.4 @@ -4216,7 +4243,7 @@ snapshots: parse5: 7.3.0 parse5-htmlparser2-tree-adapter: 7.1.0 parse5-parser-stream: 7.1.2 - undici: 7.28.0 + undici: 7.29.0 whatwg-mimetype: 4.0.0 chownr@1.1.4: @@ -4435,7 +4462,7 @@ snapshots: object-inspect: 1.13.4 object-keys: 1.1.1 object.assign: 4.1.7 - own-keys: 1.0.1 + own-keys: 1.0.2 regexp.prototype.flags: 1.5.4 safe-array-concat: 1.1.4 safe-push-apply: 1.0.0 @@ -4480,7 +4507,7 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 - es-toolkit@1.49.0: {} + es-toolkit@1.50.0: {} esbuild@0.28.1: optionalDependencies: @@ -4515,9 +4542,9 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-prettier@10.1.8(eslint@10.7.0(supports-color@7.2.0)): + eslint-config-prettier@10.1.8(eslint@10.8.0(supports-color@7.2.0)): dependencies: - eslint: 10.7.0(supports-color@7.2.0) + eslint: 10.8.0(supports-color@7.2.0) eslint-import-resolver-node@0.3.10(supports-color@7.2.0): dependencies: @@ -4527,23 +4554,23 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.14.0(@typescript-eslint/parser@8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@7.2.0))(supports-color@7.2.0))(eslint-import-resolver-node@0.3.10(supports-color@7.2.0))(eslint@10.7.0(supports-color@7.2.0))(supports-color@7.2.0): + eslint-module-utils@2.14.0(@typescript-eslint/parser@8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@7.2.0))(supports-color@7.2.0))(eslint-import-resolver-node@0.3.10(supports-color@7.2.0))(eslint@10.8.0(supports-color@7.2.0))(supports-color@7.2.0): dependencies: debug: 3.2.7(supports-color@7.2.0) optionalDependencies: - '@typescript-eslint/parser': 8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@7.2.0))(supports-color@7.2.0) - eslint: 10.7.0(supports-color@7.2.0) + '@typescript-eslint/parser': 8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@7.2.0))(supports-color@7.2.0) + eslint: 10.8.0(supports-color@7.2.0) eslint-import-resolver-node: 0.3.10(supports-color@7.2.0) transitivePeerDependencies: - supports-color - eslint-plugin-es@3.0.1(eslint@10.7.0(supports-color@7.2.0)): + eslint-plugin-es@3.0.1(eslint@10.8.0(supports-color@7.2.0)): dependencies: - eslint: 10.7.0(supports-color@7.2.0) + eslint: 10.8.0(supports-color@7.2.0) eslint-utils: 2.1.0 regexpp: 3.2.0 - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@7.2.0))(supports-color@7.2.0))(eslint@10.7.0(supports-color@7.2.0))(supports-color@7.2.0): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@7.2.0))(supports-color@7.2.0))(eslint@10.8.0(supports-color@7.2.0))(supports-color@7.2.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -4552,9 +4579,9 @@ snapshots: array.prototype.flatmap: 1.3.3 debug: 3.2.7(supports-color@7.2.0) doctrine: 2.1.0 - eslint: 10.7.0(supports-color@7.2.0) + eslint: 10.8.0(supports-color@7.2.0) eslint-import-resolver-node: 0.3.10(supports-color@7.2.0) - eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@7.2.0))(supports-color@7.2.0))(eslint-import-resolver-node@0.3.10(supports-color@7.2.0))(eslint@10.7.0(supports-color@7.2.0))(supports-color@7.2.0) + eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@7.2.0))(supports-color@7.2.0))(eslint-import-resolver-node@0.3.10(supports-color@7.2.0))(eslint@10.8.0(supports-color@7.2.0))(supports-color@7.2.0) hasown: 2.0.4 is-core-module: 2.16.2 is-glob: 4.0.3 @@ -4566,30 +4593,30 @@ snapshots: string.prototype.trimend: 1.0.10 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@7.2.0))(supports-color@7.2.0) + '@typescript-eslint/parser': 8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@7.2.0))(supports-color@7.2.0) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-node@11.1.0(eslint@10.7.0(supports-color@7.2.0)): + eslint-plugin-node@11.1.0(eslint@10.8.0(supports-color@7.2.0)): dependencies: - eslint: 10.7.0(supports-color@7.2.0) - eslint-plugin-es: 3.0.1(eslint@10.7.0(supports-color@7.2.0)) + eslint: 10.8.0(supports-color@7.2.0) + eslint-plugin-es: 3.0.1(eslint@10.8.0(supports-color@7.2.0)) eslint-utils: 2.1.0 ignore: 5.3.2 minimatch: 3.1.5 resolve: 1.22.12 semver: 6.3.1 - eslint-plugin-prettier@5.5.6(eslint-config-prettier@10.1.8(eslint@10.7.0(supports-color@7.2.0)))(eslint@10.7.0(supports-color@7.2.0))(prettier@3.9.5): + eslint-plugin-prettier@5.5.6(eslint-config-prettier@10.1.8(eslint@10.8.0(supports-color@7.2.0)))(eslint@10.8.0(supports-color@7.2.0))(prettier@3.9.6): dependencies: - eslint: 10.7.0(supports-color@7.2.0) - prettier: 3.9.5 + eslint: 10.8.0(supports-color@7.2.0) + prettier: 3.9.6 prettier-linter-helpers: 1.0.1 synckit: 0.11.13 optionalDependencies: - eslint-config-prettier: 10.1.8(eslint@10.7.0(supports-color@7.2.0)) + eslint-config-prettier: 10.1.8(eslint@10.8.0(supports-color@7.2.0)) eslint-scope@9.1.2: dependencies: @@ -4608,12 +4635,12 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.7.0(supports-color@7.2.0): + eslint@10.8.0(supports-color@7.2.0): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.7.0(supports-color@7.2.0)) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.0(supports-color@7.2.0)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.23.5(supports-color@7.2.0) - '@eslint/config-helpers': 0.6.0 + '@eslint/config-helpers': 0.7.0 '@eslint/core': 1.2.1 '@eslint/plugin-kit': 0.7.2 '@humanfs/node': 0.16.8 @@ -4715,10 +4742,10 @@ snapshots: flat-cache@4.0.1: dependencies: - flatted: 3.4.2 + flatted: 3.4.3 keyv: 4.5.4 - flatted@3.4.2: {} + flatted@3.4.3: {} for-each@0.3.5: dependencies: @@ -4735,7 +4762,7 @@ snapshots: fs-constants@1.0.0: optional: true - fs-extra@11.3.6: + fs-extra@11.4.0: dependencies: graceful-fs: 4.2.11 jsonfile: 6.2.1 @@ -5148,13 +5175,13 @@ snapshots: argparse: 2.0.1 entities: 4.5.0 linkify-it: 5.0.2 - mdurl: 2.0.0 + mdurl: 2.1.0 punycode.js: 2.3.1 uc.micro: 2.1.0 math-intrinsics@1.1.0: {} - mdurl@2.0.0: {} + mdurl@2.1.0: {} memorystream@0.3.1: {} @@ -5178,7 +5205,7 @@ snapshots: minimatch@10.2.5: dependencies: - brace-expansion: 5.0.7 + brace-expansion: 5.0.8 minimatch@3.1.5: dependencies: @@ -5223,7 +5250,7 @@ snapshots: node-sarif-builder@3.4.0: dependencies: '@types/sarif': 2.1.7 - fs-extra: 11.3.6 + fs-extra: 11.4.0 normalize-package-data@6.0.2: dependencies: @@ -5311,8 +5338,9 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 - own-keys@1.0.1: + own-keys@1.0.2: dependencies: + call-bound: 1.0.4 get-intrinsic: 1.3.0 object-keys: 1.1.1 safe-push-apply: 1.0.0 @@ -5325,7 +5353,7 @@ snapshots: dependencies: p-limit: 3.1.0 - p-map@7.0.5: {} + p-map@7.0.6: {} parse-json@8.3.0: dependencies: @@ -5401,7 +5429,7 @@ snapshots: dependencies: fast-diff: 1.3.0 - prettier@3.9.5: {} + prettier@3.9.6: {} pump@3.0.4: dependencies: @@ -5532,7 +5560,7 @@ snapshots: safer-buffer@2.1.2: {} - sax@1.6.0: {} + sax@1.6.1: {} secretlint@10.2.2(supports-color@7.2.0): dependencies: @@ -5830,13 +5858,13 @@ snapshots: tunnel: 0.0.6 underscore: 1.13.8 - typescript-eslint@8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@7.2.0))(supports-color@7.2.0): + typescript-eslint@8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@7.2.0))(supports-color@7.2.0): dependencies: - '@typescript-eslint/eslint-plugin': 8.64.0(@typescript-eslint/parser@8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@7.2.0))(supports-color@7.2.0))(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@7.2.0))(supports-color@7.2.0) - '@typescript-eslint/parser': 8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@7.2.0))(supports-color@7.2.0) - '@typescript-eslint/typescript-estree': 8.64.0(@typescript/typescript6@6.0.2)(supports-color@7.2.0) - '@typescript-eslint/utils': 8.64.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(supports-color@7.2.0))(supports-color@7.2.0) - eslint: 10.7.0(supports-color@7.2.0) + '@typescript-eslint/eslint-plugin': 8.65.0(@typescript-eslint/parser@8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@7.2.0))(supports-color@7.2.0))(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@7.2.0))(supports-color@7.2.0) + '@typescript-eslint/parser': 8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@7.2.0))(supports-color@7.2.0) + '@typescript-eslint/typescript-estree': 8.65.0(@typescript/typescript6@6.0.2)(supports-color@7.2.0) + '@typescript-eslint/utils': 8.65.0(@typescript/typescript6@6.0.2)(eslint@10.8.0(supports-color@7.2.0))(supports-color@7.2.0) + eslint: 10.8.0(supports-color@7.2.0) typescript: '@typescript/typescript6@6.0.2' transitivePeerDependencies: - supports-color @@ -5877,9 +5905,9 @@ snapshots: underscore@1.13.8: {} - undici-types@7.18.2: {} + undici-types@8.3.0: {} - undici@7.28.0: {} + undici@7.29.0: {} unicorn-magic@0.1.0: {} @@ -5971,7 +5999,7 @@ snapshots: xml2js@0.5.0: dependencies: - sax: 1.6.0 + sax: 1.6.1 xmlbuilder: 11.0.1 xmlbuilder@11.0.1: {} diff --git a/extensions/VSCode/src/extension.ts b/extensions/VSCode/src/extension.ts index 02fea4f2..38058609 100644 --- a/extensions/VSCode/src/extension.ts +++ b/extensions/VSCode/src/extension.ts @@ -40,7 +40,7 @@ import { CodeChatEditorServer, initServer } from "./index.js"; // ### Local packages import { - auto_update_timeout_ms, + autoUpdateTimeoutMs, CaptureEventWire, CaptureStatus, EditorMessage, @@ -53,7 +53,7 @@ import { import { DEBUG_ENABLED, MAX_MESSAGE_LENGTH, - console_log, + consoleLog, } from "../../../client/src/debug_enabled.mjs"; import { ResultErrTypes } from "../../../client/src/rust-types/ResultErrTypes.js"; import { @@ -2109,7 +2109,7 @@ export const activate = (context: vscode.ExtensionContext) => { vscode.commands.registerCommand( "extension.codeChatEditorActivate", async () => { - console_log("CodeChat Editor extension: starting."); + consoleLog("CodeChat Editor extension: starting."); if (!subscribed) { subscribed = true; @@ -2127,7 +2127,7 @@ export const activate = (context: vscode.ExtensionContext) => { ignore_text_document_change = false; return; } - console_log( + consoleLog( `CodeChat Editor extension: text changed - ${ event.reason }, ${format_struct(event.contentChanges)}.`, @@ -2197,7 +2197,7 @@ export const activate = (context: vscode.ExtensionContext) => { return; } - console_log( + consoleLog( "CodeChat Editor extension: sending updated cursor/scroll position.", ); @@ -2348,7 +2348,7 @@ export const activate = (context: vscode.ExtensionContext) => { webview_panel.onDidDispose(async () => { // Shut down the render client when the webview // panel closes. - console_log( + consoleLog( "CodeChat Editor extension: shut down webview.", ); // Closing the webview abruptly closes the Client, @@ -2374,7 +2374,7 @@ export const activate = (context: vscode.ExtensionContext) => { } // Start the server. - console_log("CodeChat Editor extension: starting server."); + consoleLog("CodeChat Editor extension: starting server."); codeChatEditorServer = new CodeChatEditorServer( vscode.Uri.joinPath( context.globalStorageUri, @@ -2390,7 +2390,7 @@ export const activate = (context: vscode.ExtensionContext) => { const hosted_in_ide = codechat_client_location === CodeChatEditorClientLocation.html; - console_log( + consoleLog( `CodeChat Editor extension: sending message Opened(${hosted_in_ide}).`, ); await codeChatEditorServer.sendMessageOpened(hosted_in_ide); @@ -2413,7 +2413,7 @@ export const activate = (context: vscode.ExtensionContext) => { while (codeChatEditorServer) { const message_raw = await codeChatEditorServer.getMessage(); if (message_raw === null) { - console_log("CodeChat Editor extension: queue closed."); + consoleLog("CodeChat Editor extension: queue closed."); break; } @@ -2421,7 +2421,7 @@ export const activate = (context: vscode.ExtensionContext) => { const { id, message } = JSON.parse( message_raw, ) as EditorMessage; - console_log( + consoleLog( `CodeChat Editor extension: Received data id = ${id}, message = ${format_struct( message, )}.`, @@ -2492,7 +2492,7 @@ export const activate = (context: vscode.ExtensionContext) => { }); // Send an `Update` with the full text to // re-sync the Client. - console_log( + consoleLog( "CodeChat Editor extension: sending update because Client is out of sync.", ); send_update(true); @@ -2598,7 +2598,7 @@ export const activate = (context: vscode.ExtensionContext) => { // VS Code can only apply line-based cursor // locations. DOM locations should be converted // by the server before reaching the extension. - console_log( + consoleLog( "CodeChat Editor extension: ignoring DOM cursor location in VS Code update.", ); } @@ -2707,7 +2707,7 @@ export const activate = (context: vscode.ExtensionContext) => { doc === undefined ? null : [doc.getText(), version]; - console_log( + consoleLog( `CodeChat Editor extension: Result(LoadFile(id = ${id}, ${format_struct(load_file_result)}))`, ); await codeChatEditorServer.sendResultLoadfile( @@ -2753,11 +2753,11 @@ export const activate = (context: vscode.ExtensionContext) => { // On deactivation, close everything down. export const deactivate = async () => { - console_log("CodeChat Editor extension: deactivating."); + consoleLog("CodeChat Editor extension: deactivating."); await stop_client("extension_deactivate"); webview_panel?.dispose(); - console_log("CodeChat Editor extension: deactivated."); + consoleLog("CodeChat Editor extension: deactivated."); }; // Supporting functions @@ -2776,7 +2776,7 @@ const format_struct = (complex_data_structure: any): string => // Send a result (a response to a message from the server) back to the server. const sendResult = async (id: number, result?: ResultErrTypes) => { assert(codeChatEditorServer); - console_log( + consoleLog( `CodeChat Editor extension: sending Result(id = ${id}, ${format_struct( result, )}).`, @@ -2811,7 +2811,7 @@ const send_update = (this_is_dirty: boolean) => { // needing to reload the Client with each cycle. current_editor = ate; const current_file = ate.document.fileName; - console_log( + consoleLog( `CodeChat Editor extension: sending CurrentFile(${current_file}}).`, ); try { @@ -2847,7 +2847,7 @@ const send_update = (this_is_dirty: boolean) => { : null; is_dirty = false; - console_log( + consoleLog( `CodeChat Editor extension: sending Update(${file_path}, ${cursor_position}, ${scroll_position}, ${format_struct( option_contents, )})`, @@ -2859,18 +2859,18 @@ const send_update = (this_is_dirty: boolean) => { scroll_position, ); } - }, auto_update_timeout_ms); + }, autoUpdateTimeoutMs); } }; // Gracefully shut down the render client if possible. Shut down the client as // well. const stop_client = async (closedBy: string = "client_stopped") => { - console_log("CodeChat Editor extension: stopping client."); + consoleLog("CodeChat Editor extension: stopping client."); const active = vscode.window.activeTextEditor; await endExtensionCaptureSession(active?.document.fileName, closedBy); if (codeChatEditorServer !== undefined) { - console_log("CodeChat Editor extension: stopping server."); + consoleLog("CodeChat Editor extension: stopping server."); await codeChatEditorServer.stopServer(); codeChatEditorServer = undefined; } diff --git a/extensions/VSCode/src/lib.rs b/extensions/VSCode/src/lib.rs index a828aef2..86c8e2b9 100644 --- a/extensions/VSCode/src/lib.rs +++ b/extensions/VSCode/src/lib.rs @@ -35,8 +35,20 @@ use code_chat_editor::{ide, webserver}; // ---- #[napi] pub fn init_server(extension_base_path: String) -> Result<(), Error> { + // `extension_base_path` is this extension's install directory. In a dev + // build, that's `extensions/VSCode/`, two directories below the + // repository root that `webserver::set_root_path` needs; in a packaged + // build, `client/static`, `log4rs.yml`, and `hashLocations.json` are + // copied alongside the extension (see `builder`'s `run_postrelease`), so + // the extension's own directory is already the root. + let base_path = PathBuf::from(extension_base_path); + let base_path = if cfg!(debug_assertions) { + base_path.join("../..") + } else { + base_path + }; webserver::init_server( - Some(&PathBuf::from(extension_base_path)), + &base_path, if cfg!(debug_assertions) { LevelFilter::Debug } else { @@ -56,7 +68,7 @@ impl CodeChatEditorServer { #[napi(constructor)] pub fn new(capture_spool_path: String) -> Result { Ok(CodeChatEditorServer( - ide::CodeChatEditorServer::new_with_capture_spool(PathBuf::from(capture_spool_path))?, + ide::CodeChatEditorServer::new_with_capture_spool(&PathBuf::from(capture_spool_path))?, )) } @@ -96,13 +108,15 @@ impl CodeChatEditorServer { } #[napi] + // We must pass `base_url` as a `String` per NAPI requirements. + #[allow(clippy::needless_pass_by_value)] pub fn configure_capture_service( &self, base_url: String, token: Option, ) -> Result<(), Error> { self.0 - .configure_capture_service(base_url, token) + .configure_capture_service(&base_url, token) .map_err(|err| Error::new(Status::GenericFailure, err)) } @@ -174,6 +188,6 @@ impl CodeChatEditorServer { // This returns after the server shuts down. #[napi] pub async fn stop_server(&self) { - self.0.stop_server().await + self.0.stop_server().await; } } diff --git a/extensions/standalone/Cargo.lock b/extensions/standalone/Cargo.lock new file mode 100644 index 00000000..a447dc85 --- /dev/null +++ b/extensions/standalone/Cargo.lock @@ -0,0 +1,4566 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "actix-codec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-files" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8c4f30e3272d7c345f88ae0aac3848507ef5ba871f9cc2a41c8085a0f0523b" +dependencies = [ + "actix-http", + "actix-service", + "actix-utils", + "actix-web", + "bitflags", + "bytes", + "derive_more", + "futures-core", + "http-range", + "log", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "v_htmlescape", +] + +[[package]] +name = "actix-http" +version = "3.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e2faa3e7418ed780cca54829d32782a4008a077230f67457caa063415e99c2" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "base64", + "bitflags", + "brotli", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "foldhash 0.2.0", + "futures-core", + "h2", + "http", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand 0.10.2", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "actix-router" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f8c75c51892f18d9c46150c5ac7beb81c95f78c8b83a634d49f4ca32551fe7" +dependencies = [ + "bytestring", + "cfg-if", + "http", + "regex", + "regex-lite", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63" +dependencies = [ + "actix-macros", + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "socket2 0.5.10", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df09e2d9239703dd64056359c920c7f3fba6535ec61a0059e0f44e095ffe02b4" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "actix-web-codegen", + "bytes", + "bytestring", + "cfg-if", + "cookie", + "derive_more", + "encoding_rs", + "foldhash 0.2.0", + "futures-core", + "futures-util", + "impl-more", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "regex-lite", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2 0.6.5", + "time", + "tracing", + "url", +] + +[[package]] +name = "actix-web-codegen" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "actix-web-httpauth" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456348ed9dcd72a13a1f4a660449fafdecee9ac8205552e286809eb5b0b29bd3" +dependencies = [ + "actix-utils", + "actix-web", + "base64", + "futures-core", + "futures-util", + "log", + "pin-project-lite", +] + +[[package]] +name = "actix-ws" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "decf53c3cdd63dd6f289980b430238f9a2f6d19f8bce8e418272e08d3da43f0f" +dependencies = [ + "actix-codec", + "actix-http", + "actix-web", + "bytestring", + "futures-core", + "futures-sink", + "tokio", + "tokio-util", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.17", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "ammonia" +version = "4.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6d763210e2eb7670d1a5183a08bebefa3f97db2a738a684f2ce00bd49f681d" +dependencies = [ + "cssparser", + "html5ever", + "maplit", + "url", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "arc-swap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" +dependencies = [ + "rustversion", +] + +[[package]] +name = "assert_cmd" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aa3a22042e45de04255c7bf3626e239f450200fd0493c1e382263544b20aea6" +dependencies = [ + "anstyle", + "bstr", + "libc", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + +[[package]] +name = "assert_fs" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ecf5c70ca07b7f80220bce936f0556a960ca6fb00fc2bd4125b5e581b218137" +dependencies = [ + "anstyle", + "globwalk", + "predicates", + "predicates-core", + "predicates-tree", + "tempfile", +] + +[[package]] +name = "assertables" +version = "10.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3c16d80246a076246d8b525d9f404543e90fe0818636cd89c249c2a4bee3bb1" +dependencies = [ + "regex", + "walkdir", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64-simd" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "781dd20c3aff0bd194fe7d2a977dd92f21c173891f3a03b677359e5fa457e5d5" +dependencies = [ + "simd-abstraction", +] + +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref 0.5.2", + "vsimd", +] + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "bitvec" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "brotli" +version = "8.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bstr" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f7dc094d718f2e1c1559ad110e27eeaae14a5465d3d56dd6dbd793079fbd530" +dependencies = [ + "memchr", + "regex-automata", + "serde_core", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" +dependencies = [ + "allocator-api2", +] + +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" +dependencies = [ + "serde", +] + +[[package]] +name = "bytestring" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86566c496f2f47d9b8147a4c8b02ffdb69c919fe0c2b2e7195d22cbba0e635c9" +dependencies = [ + "bytes", +] + +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cc" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cpufeatures", + "rand_core 0.10.1", +] + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "clap" +version = "4.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91e0c145792ef73a6ad36d27c75ac09f1832222a3c209689d90f534685ee5b7" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror", +] + +[[package]] +name = "codechat-editor-server" +version = "0.2.0" +dependencies = [ + "actix-files", + "actix-rt", + "actix-server", + "actix-web", + "actix-web-httpauth", + "actix-ws", + "ammonia", + "bytes", + "chrono", + "dprint-plugin-markdown", + "dunce", + "futures-util", + "htmd", + "html5ever", + "htmlize", + "imara-diff", + "indoc", + "log", + "log4rs", + "markup5ever_rcdom", + "mime", + "mime_guess", + "minify-html", + "minreq", + "normalize-line-endings", + "path-slash", + "pest", + "pest_derive", + "phf 0.14.0", + "pulldown-cmark 0.13.4", + "rand 0.10.2", + "regex", + "serde", + "serde_json", + "sha2", + "test_utils", + "thiserror", + "tokio", + "tracing", + "tracing-log", + "tracing-subscriber", + "ts-rs", + "url", + "urlencoding", + "webbrowser", +] + +[[package]] +name = "codechat-editor-standalone" +version = "0.2.0" +dependencies = [ + "actix-http", + "actix-rt", + "actix-web", + "assert_cmd", + "clap", + "codechat-editor-server", + "dunce", + "indoc", + "log", + "minreq", + "notify-debouncer-full", + "path-slash", + "predicates", + "pretty_assertions", + "rand 0.10.2", + "regex", + "test_utils", + "tokio", + "url", + "urlencoding", + "webbrowser", + "windows", +] + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "compact_str" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dfdd1c2274d9aa354115b09dc9a901d6c5576818cdf70d14cae2bdb47df00ab" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "const-str" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18f12cc9948ed9604230cdddc7c86e270f9401ccbe3c2e98a4378c5e7632212f" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cow-utils" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "417bef24afe1460300965a25ff4a24b8b45ad011948302ec221e8a0a81eb2c79" + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "cssparser" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9cdaae01d5ed7882b04d795e7f752f46ff52d2fa3b50a20d28c464510bba98" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "phf 0.13.1", + "smallvec", +] + +[[package]] +name = "cssparser-color" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa233e1dcd9c13a5d3e3a8a2c0f5a727bac380398345dbcb31db4597edc86b" +dependencies = [ + "cssparser", +] + +[[package]] +name = "cssparser-macros" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a2a99df6e410a8ff4245aa2006499ea662245f967cc7c0a38c83ef8eb44dbf" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "data-encoding" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" + +[[package]] +name = "data-url" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a30bfce702bcfa94e906ef82421f2c0e61c076ad76030c16ee5d2e9a32fe193" +dependencies = [ + "matches", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case 0.10.0", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.119", + "unicode-xid", +] + +[[package]] +name = "destructure_traitobject" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c877555693c14d2f84191cfd3ad8582790fc52b5e2274b40b59cf5f5cea25c7" + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", +] + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "dprint-core" +version = "0.68.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb1dc2cac6929b352e64ee15d45cecb990f081eebbec99be0444edb01e642a8" +dependencies = [ + "bumpalo", + "hashbrown 0.15.5", + "indexmap", + "rustc-hash", + "serde", + "unicode-width", +] + +[[package]] +name = "dprint-core-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1675ad2b358481f3cc46202040d64ac7a36c4ade414a696df32e0e45421a6e9f" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "dprint-plugin-markdown" +version = "0.22.1" +source = "git+https://github.com/bjones1/dprint-plugin-markdown.git?branch=all-fixes#3e767b74c195fdeb88033db99b9a35cf16d4a248" +dependencies = [ + "dprint-core", + "dprint-core-macros", + "pulldown-cmark 0.11.3", + "regex", + "serde", + "thiserror", + "unicode-width", +] + +[[package]] +name = "dragonbox_ecma" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd8e701084c37e7ef62d3f9e453b618130cbc0ef3573847785952a3ac3f746bf" + +[[package]] +name = "dtoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "either" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "file-id" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fc6a637b6dc58414714eddd9170ff187ecb0933d4c7024d1abbd23a3cc26e9" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide 0.8.9", +] + +[[package]] +name = "float-cmp" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures-core" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" + +[[package]] +name = "futures-macro" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "futures-sink" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" + +[[package]] +name = "futures-task" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" + +[[package]] +name = "futures-util" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", +] + +[[package]] +name = "globset" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e47d37d2ae4464254884b60ab7071be2b876a9c35b696bd018ddcc76847309cd" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "globwalk" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" +dependencies = [ + "bitflags", + "ignore", + "walkdir", +] + +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "allocator-api2", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "htmd" +version = "0.5.4" +source = "git+https://github.com/bjones1/htmd.git?branch=dom-interface#deafeb4b579530a94314828218cfbda79c615ef8" +dependencies = [ + "html5ever", + "markup5ever_rcdom", + "phf 0.13.1", +] + +[[package]] +name = "html5ever" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a1761807faccc9a19e86944bbf40610014066306f96edcdedc2fb714bcb7b8" +dependencies = [ + "log", + "markup5ever", +] + +[[package]] +name = "htmlize" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e815d50d9e411ba2690d730e6ec139c08260dddb756df315dbd16d01a587226" +dependencies = [ + "memchr", + "pastey", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-range" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15cdd26707701c53297e2fa6afb323d55fbc1d0810c3aec078ae3ef0424c3c15" + +[[package]] +name = "hybrid-array" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" +dependencies = [ + "typenum", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "ignore" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8a7b8211e695a1d0cd91cace480d4d0bd57667ab10277cc412c5f7f4884f83" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + +[[package]] +name = "imara-diff" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f01d462f766df78ab820dd06f5eb700233c51f0f4c2e846520eaf4ba6aa5c5c" +dependencies = [ + "hashbrown 0.15.5", + "memchr", +] + +[[package]] +name = "impl-more" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "277ff51754a3f68f12f58446c5d006aa8baa4914ea273cce24a599cfaff33d4f" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + +[[package]] +name = "inotify" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "153be1941a183ec9ccd095ddbe17a8b8d435ef6c76e9e02451b933c3999af2c8" +dependencies = [ + "bitflags", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c033f80b2c113cdf91ab7a33faa9cbc014726dcad99880c8609af2a370edf37d" +dependencies = [ + "libc", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "json-escape-simd" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c22a2041e3874a055a4eb03ea2395aaccdefa84ce75b31d542d72a741c3c6ad3" + +[[package]] +name = "kqueue" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "273c0752728918e0ac4976f2b275b6fefb9ecd400585dec929419f3844cd87b5" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" +dependencies = [ + "bitflags", + "libc", +] + +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "lightningcss" +version = "1.0.0-alpha.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d31b760f96e8fdfe1d0c295e4bf76c503d6f15d2d470d53bd8cd1f7aa8c7d934" +dependencies = [ + "ahash 0.8.12", + "bitflags", + "const-str", + "cssparser", + "cssparser-color", + "dashmap", + "data-encoding", + "getrandom 0.3.4", + "indexmap", + "itertools 0.10.5", + "lazy_static", + "lightningcss-derive", + "parcel_selectors", + "parcel_sourcemap", + "pastey", + "pathdiff", + "rayon", + "serde", + "serde-content", + "smallvec", +] + +[[package]] +name = "lightningcss-derive" +version = "1.0.0-alpha.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12744d1279367caed41739ef094c325d53fb0ffcd4f9b84a368796f870252" +dependencies = [ + "convert_case 0.6.0", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "local-channel" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +dependencies = [ + "futures-core", + "futures-sink", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +dependencies = [ + "serde_core", +] + +[[package]] +name = "log-mdc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a94d21414c1f4a51209ad204c1776a3d0765002c76c6abcb602a6f09f1e881c7" + +[[package]] +name = "log4rs" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e947bb896e702c711fccc2bf02ab2abb6072910693818d1d6b07ee2b9dfd86c" +dependencies = [ + "anyhow", + "arc-swap", + "chrono", + "derive_more", + "fnv", + "humantime", + "libc", + "log", + "log-mdc", + "mock_instant", + "parking_lot", + "rand 0.9.5", + "serde", + "serde-value", + "serde_json", + "serde_yaml", + "thiserror", + "thread-id", + "typemap-ors", + "unicode-segmentation", + "winapi", +] + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "markup5ever" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7122d987ec5f704ee56f6e5b41a7d93722e9aae27ae07cafa4036c4d3f9757de" +dependencies = [ + "log", + "tendril", + "web_atoms", +] + +[[package]] +name = "markup5ever_rcdom" +version = "0.39.0+unofficial" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ac010f19d6c4af81eeb4018a39d7a115de9d285af45c126a4ac02e6fc5716b7" +dependencies = [ + "html5ever", + "markup5ever", + "tendril", + "xml5ever", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minify-html" +version = "0.18.1" +source = "git+https://github.com/bjones1/minify-html.git?branch=dev#b5cce1953106d00b5e622214a99457d3312c5fa9" +dependencies = [ + "ahash 0.8.12", + "aho-corasick", + "lightningcss", + "memchr", + "minify-html-common", + "once_cell", + "oxc_allocator", + "oxc_codegen", + "oxc_minifier", + "oxc_parser", + "oxc_span", +] + +[[package]] +name = "minify-html-common" +version = "0.0.3" +source = "git+https://github.com/bjones1/minify-html.git?branch=dev#b5cce1953106d00b5e622214a99457d3312c5fa9" +dependencies = [ + "ahash 0.8.12", + "aho-corasick", + "itertools 0.14.0", + "memchr", + "once_cell", + "serde", + "serde_json", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "miniz_oxide" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +dependencies = [ + "adler2", +] + +[[package]] +name = "minreq" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "659579df697b372ef9e36f02fcbb41f6d6f157dcec7db9c9618fa0f23cf0fc20" + +[[package]] +name = "mio" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "mock_instant" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb517913cfcfb9eeda59f36020269075a152701a01606c612f547e4890be399" + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nonmax" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" + +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + +[[package]] +name = "notify" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" +dependencies = [ + "bitflags", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.60.2", +] + +[[package]] +name = "notify-debouncer-full" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02b49179cfebc9932238d04d6079912d26de0379328872846118a0fa0dbb302" +dependencies = [ + "file-id", + "log", + "notify", + "notify-types", + "walkdir", +] + +[[package]] +name = "notify-types" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags", + "objc2", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "outref" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f222829ae9293e33a9f5e9f440c6760a3d450a64affe1846486b140db81c1f4" + +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + +[[package]] +name = "owo-colors" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" + +[[package]] +name = "oxc-browserslist" +version = "3.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6967692b4c51de292573c3f5eed236b6df29ac47d122379ad3a9d209776a9cf3" +dependencies = [ + "miniz_oxide 0.9.1", + "postcard", + "rustc-hash", + "serde", + "thiserror", +] + +[[package]] +name = "oxc-miette" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4356a61f2ed4c9b3610245215fbf48970eb277126919f87db9d0efa93a74245c" +dependencies = [ + "cfg-if", + "owo-colors", + "oxc-miette-derive", + "textwrap", + "thiserror", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "oxc-miette-derive" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b237422b014f8f8fff75bb9379e697d13f8d57551a22c88bebb39f073c1bf696" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "oxc_allocator" +version = "0.127.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd3b8bfef454857d3d9ca08fb84c8955da8591b5a82a21bb34a7ebbf94da7b0f" +dependencies = [ + "allocator-api2", + "hashbrown 0.17.1", + "oxc_data_structures", + "rustc-hash", +] + +[[package]] +name = "oxc_ast" +version = "0.127.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "381ae8356082431bd7e217dd78c7179bfc379dbbe7a32494e28be4fc678812c7" +dependencies = [ + "bitflags", + "oxc_allocator", + "oxc_ast_macros", + "oxc_data_structures", + "oxc_diagnostics", + "oxc_estree", + "oxc_regular_expression", + "oxc_span", + "oxc_str", + "oxc_syntax", +] + +[[package]] +name = "oxc_ast_macros" +version = "0.127.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50246449a5fa669debd2debeb90be4c30f0a3a2e954f852ec40e5ef49701285" +dependencies = [ + "phf 0.13.1", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "oxc_ast_visit" +version = "0.127.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82466fd1885834078becf1385380c40624bf511723b695104b21f293c7dc5271" +dependencies = [ + "oxc_allocator", + "oxc_ast", + "oxc_span", + "oxc_syntax", +] + +[[package]] +name = "oxc_codegen" +version = "0.127.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b69f394fa01810f99943a9191dde9d5757bdccd5c06347af62663eea671a5153" +dependencies = [ + "bitflags", + "cow-utils", + "dragonbox_ecma", + "itoa", + "oxc_allocator", + "oxc_ast", + "oxc_data_structures", + "oxc_index", + "oxc_semantic", + "oxc_sourcemap", + "oxc_span", + "oxc_str", + "oxc_syntax", + "rustc-hash", +] + +[[package]] +name = "oxc_compat" +version = "0.127.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b656d726e4dafe2341759dc2f1fefa39fc736773f382885714f139d4dc69cc" +dependencies = [ + "cow-utils", + "oxc-browserslist", + "oxc_syntax", + "rustc-hash", + "serde", +] + +[[package]] +name = "oxc_data_structures" +version = "0.127.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1defc2fd17ee94f2c8511b0c4a4756d5868fbee891478953f2354ef444b1962f" + +[[package]] +name = "oxc_diagnostics" +version = "0.127.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7ccb0e8e7c9f1fb75e0700b2c75d9d854e534a7a356b13d2936893651f2b98" +dependencies = [ + "cow-utils", + "oxc-miette", + "percent-encoding", +] + +[[package]] +name = "oxc_ecmascript" +version = "0.127.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1904566c4e725c1511c88166ec203ae97bebb62887441b4a29b1e7757ec39859" +dependencies = [ + "cow-utils", + "num-bigint", + "num-traits", + "oxc_allocator", + "oxc_ast", + "oxc_regular_expression", + "oxc_span", + "oxc_syntax", +] + +[[package]] +name = "oxc_estree" +version = "0.127.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e87cd0e290bab4cb5d81377bbc1ebd414f01a7af72d7f8e5ccbb4a9a157d71df" + +[[package]] +name = "oxc_index" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3e6120999627ec9703025eab7c9f410ebb7e95557632a8902ca48210416c2b" +dependencies = [ + "nonmax", + "serde", +] + +[[package]] +name = "oxc_mangler" +version = "0.127.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc7dcb6b22f8e7aa9a8d2afa8f29e62a7892a02de780dab976aa42eb09e977a9" +dependencies = [ + "itertools 0.14.0", + "oxc_allocator", + "oxc_ast", + "oxc_data_structures", + "oxc_index", + "oxc_semantic", + "oxc_span", + "oxc_str", + "oxc_syntax", + "rustc-hash", +] + +[[package]] +name = "oxc_minifier" +version = "0.127.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59700b3cd4906c9e1878ac925bd86a284a1efd0a69353516f65ffc3ebb08a8ff" +dependencies = [ + "cow-utils", + "itoa", + "oxc_allocator", + "oxc_ast", + "oxc_ast_visit", + "oxc_compat", + "oxc_data_structures", + "oxc_ecmascript", + "oxc_index", + "oxc_mangler", + "oxc_parser", + "oxc_regular_expression", + "oxc_semantic", + "oxc_span", + "oxc_str", + "oxc_syntax", + "oxc_traverse", + "rustc-hash", +] + +[[package]] +name = "oxc_parser" +version = "0.127.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71acdb67749ff68bfbbd346da7dd2fe4947964be49ac9ec34d73d10a2396dcd" +dependencies = [ + "bitflags", + "cow-utils", + "memchr", + "num-bigint", + "num-traits", + "oxc_allocator", + "oxc_ast", + "oxc_data_structures", + "oxc_diagnostics", + "oxc_ecmascript", + "oxc_regular_expression", + "oxc_span", + "oxc_str", + "oxc_syntax", + "rustc-hash", + "seq-macro", +] + +[[package]] +name = "oxc_regular_expression" +version = "0.127.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08a1273168ec6d8083e161565d264847249b9aad51c430d92d344303ede058b2" +dependencies = [ + "bitflags", + "oxc_allocator", + "oxc_ast_macros", + "oxc_diagnostics", + "oxc_span", + "oxc_str", + "phf 0.13.1", + "rustc-hash", + "unicode-id-start", +] + +[[package]] +name = "oxc_semantic" +version = "0.127.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63d4f8a0d3eb4e8e03aa413f54300235cb0314dc26649d2ff19f609b7b478272" +dependencies = [ + "itertools 0.14.0", + "memchr", + "oxc_allocator", + "oxc_ast", + "oxc_ast_visit", + "oxc_diagnostics", + "oxc_ecmascript", + "oxc_index", + "oxc_span", + "oxc_str", + "oxc_syntax", + "rustc-hash", + "self_cell", +] + +[[package]] +name = "oxc_sourcemap" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d378eb8bad20e89d66276aebab51f6a5408571092cac94abdd3eabb773713d6" +dependencies = [ + "base64-simd 0.8.0", + "json-escape-simd", + "rustc-hash", + "serde", + "serde_json", +] + +[[package]] +name = "oxc_span" +version = "0.127.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9af84474452c3caa7aca1bcaca04b6e16552fe29472059b7921ae7a69790dccf" +dependencies = [ + "compact_str", + "oxc-miette", + "oxc_allocator", + "oxc_ast_macros", + "oxc_estree", + "oxc_str", +] + +[[package]] +name = "oxc_str" +version = "0.127.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136bcc6bed1182df0b9c529e478da55a490b38ba5f1189abf2e7a9b13f46f0b1" +dependencies = [ + "compact_str", + "hashbrown 0.17.1", + "oxc_allocator", + "oxc_estree", +] + +[[package]] +name = "oxc_syntax" +version = "0.127.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b448a086623714675f66b79271e25fa2b51708255fa6af7dad83be88cc6e8726" +dependencies = [ + "bitflags", + "cow-utils", + "dragonbox_ecma", + "nonmax", + "oxc_allocator", + "oxc_ast_macros", + "oxc_estree", + "oxc_index", + "oxc_span", + "oxc_str", + "phf 0.13.1", + "unicode-id-start", +] + +[[package]] +name = "oxc_traverse" +version = "0.127.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "648c4e7c8ee0a8d2ff28751cc9dc8d5502a7d3b2b96d4fa73de7fb31b46d54c6" +dependencies = [ + "itoa", + "oxc_allocator", + "oxc_ast", + "oxc_ast_visit", + "oxc_data_structures", + "oxc_ecmascript", + "oxc_semantic", + "oxc_span", + "oxc_str", + "oxc_syntax", + "rustc-hash", +] + +[[package]] +name = "parcel_selectors" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05f71e01edca03d245ab0a9f7ce13a974ceb79baaae8faf2ba0b11de6b90913" +dependencies = [ + "bitflags", + "cssparser", + "log", + "phf 0.11.3", + "phf_codegen 0.11.3", + "precomputed-hash", + "rustc-hash", + "smallvec", +] + +[[package]] +name = "parcel_sourcemap" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "485b74d7218068b2b7c0e3ff12fbc61ae11d57cb5d8224f525bd304c6be05bbb" +dependencies = [ + "base64-simd 0.7.0", + "data-url", + "rkyv", + "serde", + "serde_json", + "vlq", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + +[[package]] +name = "path-slash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42" + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pest" +version = "2.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df728be843c7070fab6ab7c328c4e9e9d78e23bf749c0669c86ee7ebfa050a2" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e2dd6fc3b26b3462ee188aac870f5a41d398f1cd5e2408d16531bd71c9591fd" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a7a9205cfb6f596a9e8b689c0a15f9ceb7a1aafae7aaf788150ac65b29975b6" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "pest_meta" +version = "2.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85abd351c0de1e8384fc791a0737111a350394937e92b956b743dac12429f57c" +dependencies = [ + "pest", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_shared 0.11.3", +] + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros 0.13.1", + "phf_shared 0.13.1", + "serde", +] + +[[package]] +name = "phf" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "010378780309880b08997fae13be7834dba947d36393bd372f2b1556deb2a2f6" +dependencies = [ + "phf_macros 0.14.0", + "phf_shared 0.14.0", + "serde", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_codegen" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand 0.8.7", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared 0.13.1", +] + +[[package]] +name = "phf_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeb62e0959d5a1bebc965f4d15d9e2b7cea002b6b0f5ba8cde6cc26738467100" +dependencies = [ + "fastrand", + "phf_shared 0.14.0", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "phf_macros" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fa8d0ca26d424d27630da600c6624696e7dec8bf7b3b492b383c5dc49e5e085" +dependencies = [ + "phf_generator 0.14.0", + "phf_shared 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + +[[package]] +name = "phf_shared" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6fd9027e2d9319be6349febd1db4e8d02aa544921200c9b777720ac34a3aa89" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "serde", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "predicates" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" +dependencies = [ + "anstyle", + "difflib", + "float-cmp", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144" + +[[package]] +name = "predicates-tree" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "pulldown-cmark" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "679341d22c78c6c649893cbd6c3278dcbe9fc4faa62fea3a9296ae2b50c14625" +dependencies = [ + "bitflags", + "memchr", + "unicase", +] + +[[package]] +name = "pulldown-cmark" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e" +dependencies = [ + "bitflags", + "memchr", + "pulldown-cmark-escape", + "unicase", +] + +[[package]] +name = "pulldown-cmark-escape" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae" + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_chacha", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "rkyv" +version = "0.7.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2297bf9c81a3f0dc96bc9521370b88f054168c29826a75e89c55ff196e7ed6a1" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d7b42d4b8d06048d3ac8db0eb31bcb942cbeb709f0b5f2b2ebde398d3038f5" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "self_cell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab42ca02749e120097e328d91d415325bdf43b1c72c4c8badf37375fe40a813" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "seq-macro" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-content" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3753ca04f350fa92d00b6146a3555e63c55388c9ef2e11e09bce2ff1c0b509c6" +dependencies = [ + "serde", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-abstraction" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cadb29c57caadc51ff8346233b5cec1d240b68ce55cf1afc764818791876987" +dependencies = [ + "outref 0.1.0", +] + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "smawk" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8e2fb0f499abb4d162f2bedad68f5ef91a1682b5a03596ddb67efd37768d100" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "string_cache" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.13.1", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "tendril" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fed54709c5b3a53d09bb1c113ea4f5ceafd1e772ddcb0030a82e1d56c087b08" +dependencies = [ + "new_debug_unreachable", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "termtree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + +[[package]] +name = "test_utils" +version = "0.1.0" +dependencies = [ + "assert_fs", + "assertables", + "log", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "thread-id" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2010d27add3f3240c1fef7959f46c814487b216baee662af53be645ba7831c07" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.5", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tokio-util" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "futures-util", + "libc", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "ts-rs" +version = "12.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756050066659291d47a554a9f558125db17428b073c5ffce1daf5dcb0f7231d8" +dependencies = [ + "thiserror", + "ts-rs-macros", +] + +[[package]] +name = "ts-rs-macros" +version = "12.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d90eea51bc7988ef9e674bf80a85ba6804739e535e9cab48e4bb34a8b652aa" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "termcolor", +] + +[[package]] +name = "typemap-ors" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68c24b707f02dd18f1e4ccceb9d49f2058c2fb86384ef9972592904d7a28867" +dependencies = [ + "unsafe-any-ors", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-id-start" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81b79ad29b5e19de4260020f8919b443b2ef0277d242ce532ec7b7a2cc8b6007" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "unsafe-any-ors" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a303d30665362d9680d7d91d78b23f5f899504d4f08b3c4cf08d055d87c0ad" +dependencies = [ + "destructure_traitobject", +] + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "v_htmlescape" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e8257fbc510f0a46eb602c10215901938b5c2a7d5e70fc11483b1d3c9b5b18c" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vlq" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65dd7eed29412da847b0f78bcec0ac98588165988a8cfe41d4ea1d429f8ccfff" + +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web_atoms" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "075474b12bcb3d2e3d4546580e9de478eeeead668a1761e2a8860c836b7ef297" +dependencies = [ + "phf 0.13.1", + "phf_codegen 0.13.1", + "string_cache", + "string_cache_codegen", +] + +[[package]] +name = "webbrowser" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc95580916af1e68ff6a7be07446fc5db73ebf71cf092de939bbf5f7e189f72" +dependencies = [ + "core-foundation", + "jni", + "log", + "ndk-context", + "objc2", + "objc2-foundation", + "url", + "web-sys", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "xml5ever" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab627f34ff61b80d756180d556f9c68801d836d271b3b8c094504ceca69d221" +dependencies = [ + "log", + "markup5ever", +] + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/extensions/standalone/Cargo.toml b/extensions/standalone/Cargo.toml new file mode 100644 index 00000000..48d324e0 --- /dev/null +++ b/extensions/standalone/Cargo.toml @@ -0,0 +1,96 @@ +# Copyright (C) 2025 Bryan A. Jones. +# +# This file is part of the CodeChat Editor. +# +# The CodeChat Editor is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# The CodeChat Editor is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# the CodeChat Editor. If not, see +# [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/). +# +# `Cargo.toml` -- Rust build/package management config for the standalone CLI +# ============================================================================= +# +# General package configurations +# ------------------------------ +[package] +authors = ["Bryan A. Jones", "Peter Loux"] +categories = ["development-tools", "text-editors"] +description = "A programmer's word processor -- standalone command-line server." +edition = "2024" +homepage = "https://codechat-editor.onrender.com/fw/fsb/opt/render/project/src/README.md" +keywords = ["literate programming"] +license = "GPL-3.0-only" +name = "codechat-editor-standalone" +readme = "../../README.md" +repository = "https://github.com/bjones1/CodeChat_Editor" +version = "0.2.0" + +# Dependencies +# ------------ +[dependencies] +actix-rt = "2.9.0" +actix-web = "4" +clap = { version = "4", features = ["derive"] } +codechat-editor-server = { path = "../../server" } +dunce = "1.0.5" +indoc = "2.0.5" +log = { version = "0.4", features = ["release_max_level_warn"] } +minreq = "3" +notify-debouncer-full = "0.7" +rand = "0.10" +regex = "1" +tokio = { version = "1", features = ["full"] } +urlencoding = "2" +webbrowser = "1.0.5" + +# [Windows-only dependencies](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#platform-specific-dependencies). +[target.'cfg(windows)'.dependencies] +windows = { version = "0.62", features = ["Win32_Storage_FileSystem"] } + +# ### Development-only dependencies +[dev-dependencies] +actix-http = "3.9.0" +assert_cmd = "2" +path-slash = "0.2.1" +predicates = "3.1.2" +pretty_assertions = "1.4.1" +test_utils = { path = "../../test_utils" } +url = "2.5.2" + +# Release +# ------- +[profile.release] +codegen-units = 1 +lto = true +opt-level = 3 +panic = "abort" +strip = "symbols" + +# The profile that `cargo dist` will build with. +[profile.dist] +inherits = "release" + +# Code coverage +# ------------- +# +[lints.rust] +# Avoid a lint about the `coverage` cfg set by `cargo llvm-cov` (see `bt +# cover`). +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage)'] } + +# Clippy config +# ------------- +[lints.clippy] +pedantic = { level = "warn", priority = 0 } +missing_errors_doc = { level = "allow", priority = 1 } +missing_panics_doc = { level = "allow", priority = 1 } +doc_markdown = { level = "allow", priority = 1 } diff --git a/server/dist.toml b/extensions/standalone/dist.toml similarity index 91% rename from server/dist.toml rename to extensions/standalone/dist.toml index 1ec46151..a5fc55d1 100644 --- a/server/dist.toml +++ b/extensions/standalone/dist.toml @@ -23,4 +23,4 @@ [dist] # Extra static files to include in each App (path relative to this Cargo.toml's # dir) -include = ["log4rs.yml", "hashLocations.json", "../client/static"] +include = ["../../server/log4rs.yml", "../../server/hashLocations.json", "../../client/static"] diff --git a/server/src/ide/filewatcher.rs b/extensions/standalone/src/filewatcher.rs similarity index 89% rename from server/src/ide/filewatcher.rs rename to extensions/standalone/src/filewatcher.rs index 3a524c8a..e6589d1a 100644 --- a/server/src/ide/filewatcher.rs +++ b/extensions/standalone/src/filewatcher.rs @@ -19,14 +19,18 @@ // ------- // // ### Standard library +#[cfg(windows)] +use std::sync::LazyLock; use std::{ + fmt::Write, path::{Path, PathBuf}, time::Duration, }; // ### Third-party use actix_web::{ - HttpRequest, HttpResponse, Responder, + App, HttpRequest, HttpResponse, Responder, + dev::{ServiceFactory, ServiceRequest}, error::{self, Error}, get, http::header::{self, ContentType}, @@ -34,13 +38,13 @@ use actix_web::{ }; use dunce::simplified; use indoc::formatdoc; -use lazy_static::lazy_static; use log::{error, info, warn}; use notify_debouncer_full::{ DebounceEventResult, new_debouncer, notify::{EventKind, RecursiveMode}, }; use rand::random; +#[cfg(windows)] use regex::Regex; use tokio::{ fs::DirEntry, @@ -49,38 +53,52 @@ use tokio::{ select, sync::mpsc, }; -use urlencoding; #[cfg(target_os = "windows")] use windows::Win32::Storage::FileSystem::GetLogicalDrives; // ### Local -use crate::{ - processing::CodeMirrorDiffable, +use code_chat_editor::{ + processing::{CodeChatForWeb, CodeMirror, CodeMirrorDiffable, SourceFileMetadata}, queue_send, - webserver::{ - INITIAL_IDE_MESSAGE_ID, MESSAGE_ID_INCREMENT, ResultErrTypes, ResultOkTypes, WebAppState, - filesystem_endpoint, get_test_mode, - }, -}; -use crate::{ - processing::{CodeChatForWeb, CodeMirror, SourceFileMetadata}, translation::{create_translation_queues, translation_task}, webserver::{ - EditorMessage, EditorMessageContents, RESERVED_MESSAGE_ID, UpdateMessageContents, - client_websocket, get_client_framework, html_wrapper, http_not_found, path_display, - send_response, + EditorMessage, EditorMessageContents, INITIAL_IDE_MESSAGE_ID, MESSAGE_ID_INCREMENT, + RESERVED_MESSAGE_ID, RegisterRoutes, ResultErrTypes, ResultOkTypes, UpdateMessageContents, + WebAppState, client_websocket, filesystem_endpoint, get_client_framework, get_test_mode, + html_wrapper, http_not_found, path_display, send_response, }, }; // Globals // ------- -lazy_static! { - /// Matches a bare drive letter. Only needed on Windows. - static ref DRIVE_LETTER_REGEX: Regex = Regex::new("^[a-zA-Z]:$").unwrap(); -} +/// Matches a bare drive letter. Only needed on Windows. +#[cfg(windows)] +static DRIVE_LETTER_REGEX: LazyLock = LazyLock::new(|| Regex::new("^[a-zA-Z]:$").unwrap()); pub const FILEWATCHER_PATH_PREFIX: &[&str] = &["fw", "fsc"]; +/// Registers the filewatcher IDE's routes with the server's `configure_app` +/// (see `code_chat_editor::webserver::RegisterRoutes`). This IDE is used only +/// by this standalone binary -- other embedders of the `code_chat_editor` +/// library (for example, the VSCode extension) don't need it. +#[derive(Clone)] +pub struct FilewatcherRoutes; + +impl RegisterRoutes for FilewatcherRoutes { + fn register(&self, app: App) -> App + where + T: ServiceFactory, + { + app.service(filewatcher_browser_endpoint) + .service(filewatcher_client_endpoint) + .service(filewatcher_websocket) + // Reroute to the filewatcher filesystem for typical user-requested + // URLs. + .route("/", web::get().to(filewatcher_root_fs_redirect)) + .route("/fw/fsb", web::get().to(filewatcher_root_fs_redirect)) + } +} + /// File browser endpoints /// ---------------------- /// @@ -89,7 +107,7 @@ pub const FILEWATCHER_PATH_PREFIX: &[&str] = &["fw", "fsc"]; /// replaced by something better. /// /// Redirect from the root of the filesystem to the actual root path on this OS. -#[cfg(not(tarpaulin_include))] +#[cfg_attr(coverage, coverage(off))] pub async fn filewatcher_root_fs_redirect() -> impl Responder { HttpResponse::TemporaryRedirect() .insert_header((header::LOCATION, "/fw/fsb/")) @@ -104,7 +122,7 @@ pub async fn filewatcher_root_fs_redirect() -> impl Responder { /// /// Omit code coverage -- this is a temporary interface, until IDE integration /// replaces this. -#[cfg(not(tarpaulin_include))] +#[cfg_attr(coverage, coverage(off))] #[get("/fw/fsb/{path:.*}")] async fn filewatcher_browser_endpoint( req: HttpRequest, @@ -146,7 +164,7 @@ async fn filewatcher_browser_endpoint( } else if canon_path.is_file() { // Get an ID for this connection. let connection_id_raw = get_connection_id_raw(&app_state); - return processing_task(&canon_path, req, app_state, connection_id_raw).await; + return processing_task(&canon_path, &req, app_state, connection_id_raw); } // It's not a directory or a file...we give up. For simplicity, don't handle @@ -164,7 +182,8 @@ async fn filewatcher_browser_endpoint( /// /// Omit code coverage -- this is a temporary interface, until IDE integration /// replaces this. -#[cfg(not(tarpaulin_include))] +#[cfg_attr(coverage, coverage(off))] +#[allow(clippy::too_many_lines)] async fn dir_listing(web_path: &str, dir_path: &Path) -> HttpResponse { // Special case on Windows: list drive letters. #[cfg(target_os = "windows")] @@ -176,9 +195,10 @@ async fn dir_listing(web_path: &str, dir_path: &Path) -> HttpResponse { Err(err) => return http_not_found(&format!("Unable to list drive letters: {err}.")), }; for drive_letter in logical_drives { - drive_html.push_str(&format!( - "
  • {drive_letter}:
  • \n" - )); + let _ = writeln!( + drive_html, + "
  • {drive_letter}:
  • " + ); } return HttpResponse::Ok() @@ -233,7 +253,7 @@ async fn dir_listing(web_path: &str, dir_path: &Path) -> HttpResponse { Err(err) => { return http_not_found(&format!("Unable to read file in directory: {err}.")); } - }; + } } // Sort them -- case-insensitive on Windows, normally on Linux/OS X. #[cfg(target_os = "windows")] @@ -258,13 +278,15 @@ async fn dir_listing(web_path: &str, dir_path: &Path) -> HttpResponse { Ok(v) => v, Err(err) => { return http_not_found(&format!( - "Unable to decode directory name '{err:?}' as UTF-8." + "Unable to decode directory name '{}' as UTF-8.", + Path::new(&err).display() )); } }; let encoded_dir = urlencoding::encode(&dir_name); - dir_html += &format!( - "
  • {dir_name}
  • \n", + let _ = writeln!( + dir_html, + "
  • {dir_name}
  • " ); } @@ -274,7 +296,10 @@ async fn dir_listing(web_path: &str, dir_path: &Path) -> HttpResponse { let file_name = match file.file_name().into_string() { Ok(v) => v, Err(err) => { - return http_not_found(&format!("Unable to decode file name {err:?} as UTF-8.",)); + return http_not_found(&format!( + "Unable to decode file name {} as UTF-8.", + Path::new(&err).display() + )); } }; let encoded_file = urlencoding::encode(&file_name); @@ -311,7 +336,7 @@ async fn dir_listing(web_path: &str, dir_path: &Path) -> HttpResponse { /// Copied almost verbatim from the /// [win\_partitions crate](https://docs.rs/crate/win_partitions/0.3.0/source/src/win_api.rs#144) /// when compilation errors broke the crate. -#[cfg(not(tarpaulin_include))] +#[cfg_attr(coverage, coverage(off))] #[cfg(target_os = "windows")] pub fn get_logical_drive() -> Result, std::io::Error> { let bitmask = unsafe { GetLogicalDrives() }; @@ -354,9 +379,10 @@ async fn filewatcher_client_endpoint( .await } -async fn processing_task( +#[allow(clippy::too_many_lines)] +fn processing_task( file_path: &Path, - req: HttpRequest, + req: &HttpRequest, app_state: WebAppState, connection_id_raw: u32, ) -> Result { @@ -373,7 +399,7 @@ async fn processing_task( // // The path to the currently open CodeChat Editor file. let Ok(current_filepath) = file_path.to_path_buf().canonicalize() else { - let msg = format!("Unable to canonicalize path {file_path:?}."); + let msg = format!("Unable to canonicalize path {}.", file_path.display()); error!("{msg}"); return Err(error::ErrorBadRequest(msg)); }; @@ -400,13 +426,12 @@ async fn processing_task( // Transfer the queues from the global state to this task. let (from_ide_tx, mut to_ide_rx) = - match app_state.ide_queues.lock().unwrap().remove(&connection_id) { - Some(queues) => (queues.from_websocket_tx.clone(), queues.to_websocket_rx), - None => { - let err = format!("No websocket queues for connection id {connection_id}."); - error!("{err}"); - return Err(error::ErrorBadRequest(err)); - } + if let Some(queues) = app_state.ide_queues.lock().unwrap().remove(&connection_id) { + (queues.from_websocket_tx.clone(), queues.to_websocket_rx) + } else { + let err = format!("No websocket queues for connection id {connection_id}."); + error!("{err}"); + return Err(error::ErrorBadRequest(err)); }; // #### The filewatcher task. @@ -451,7 +476,10 @@ async fn processing_task( // Provide it a file to open. if let Some(cfp) = ¤t_filepath { let Some(cfp_str) = cfp.to_str() else { - let err = format!("Unable to convert file path {cfp:?} to string."); + let err = format!( + "Unable to convert file path \"{}\" to string.", + cfp.display() + ); error!("{err}"); break 'task; }; @@ -463,7 +491,7 @@ async fn processing_task( // `queue_send` exits before this runs, the message didn't get // sent, so the ID wasn't used. id += MESSAGE_ID_INCREMENT; - }; + } shutdown_only = false; } @@ -522,11 +550,11 @@ async fn processing_task( if debounced_event.event.paths.len() != 1 || current_filepath.as_ref().is_none_or(|cfp| cfp != &debounced_event.event.paths[0]) { - warn!("Modification to different file {}.", debounced_event.event.paths[0].to_string_lossy()); + warn!("Modification to different file(s): {:?}.", debounced_event.event.paths); } else { let cfp = current_filepath.as_ref().unwrap(); let Some(current_filepath_str) = cfp.to_str() else { - error!("Unable to convert path {cfp:?} to string."); + error!("Unable to convert path \"{}\" to string.", cfp.display()); break 'task; }; @@ -562,9 +590,9 @@ async fn processing_task( contents: Some(CodeChatForWeb { metadata: SourceFileMetadata { // The IDE doesn't need to provide this. - mode: "".to_string(), + mode: String::new(), }, - source: crate::processing::CodeMirrorDiffable::Plain(CodeMirror { + source: CodeMirrorDiffable::Plain(CodeMirror { doc: file_contents, doc_blocks: vec![], }), @@ -574,6 +602,7 @@ async fn processing_task( // stays in sync with any diffs. Produce a // whole number to avoid encoding // difference with fractional values. + #[allow(clippy::cast_precision_loss)] version: random::() as f64, }), }), @@ -598,9 +627,8 @@ async fn processing_task( break 'process Err(ResultErrTypes::WrongFileUpdate(update_message_contents.file_path, current_filepath.clone())); } // Without code, there's nothing to save. - let codechat_for_web = match update_message_contents.contents { - None => break 'process Ok(ResultOkTypes::Void), - Some(cfw) => cfw, + let Some(codechat_for_web) = update_message_contents.contents else { + break 'process Ok(ResultOkTypes::Void) }; // Translate from the CodeChatForWeb format to @@ -614,14 +642,18 @@ async fn processing_task( // it, in order to avoid a watch notification // from this write. if let Err(err) = debounced_watcher.unwatch(cfp) { - break 'process Err(ResultErrTypes::FileUnwatchError(cfp.to_path_buf(), err.to_string())); + break 'process Err(ResultErrTypes::FileUnwatchError(cfp.clone(), err.to_string())); } // Save this string to a file. - if let Err(err) = fs::write(cfp.as_path(), plain.doc).await { - break 'process Err(ResultErrTypes::SaveFileError(cfp.to_path_buf(), err.to_string())); - } + let write_result = fs::write(cfp.as_path(), plain.doc).await; + // Re-watch the file regardless of whether the + // write succeeded, so a failed write doesn't + // silently leave the file unwatched. if let Err(err) = debounced_watcher.watch(cfp, RecursiveMode::NonRecursive) { - break 'process Err(ResultErrTypes::FileWatchError(cfp.to_path_buf(), err.to_string())); + break 'process Err(ResultErrTypes::FileWatchError(cfp.clone(), err.to_string())); + } + if let Err(err) = write_result { + break 'process Err(ResultErrTypes::SaveFileError(cfp.clone(), err.to_string())); } Ok(ResultOkTypes::Void) }; @@ -635,15 +667,15 @@ async fn processing_task( if let Some(cfp) = ¤t_filepath && let Err(err) = debounced_watcher.unwatch(cfp) { - break 'err_exit Err(ResultErrTypes::FileUnwatchError(cfp.to_path_buf(), err.to_string())); + break 'err_exit Err(ResultErrTypes::FileUnwatchError(cfp.clone(), err.to_string())); } // Update to the new path, which is already // canonicalized. - current_filepath = Some(file_path.to_path_buf()); + current_filepath = Some(file_path.clone()); // Watch the new file. 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())); + break 'err_exit Err(ResultErrTypes::FileWatchError(file_path.clone(), err.to_string())); } // Indicate there was no error in the `Result` // message. @@ -701,7 +733,7 @@ async fn processing_task( info!("Watcher closed."); }); - match get_client_framework(get_test_mode(&req), "fw/ws", &connection_id_raw.to_string()) { + match get_client_framework(get_test_mode(req), "fw/ws", &connection_id_raw.clone()) { Ok(s) => Ok(HttpResponse::Ok().content_type(ContentType::html()).body(s)), Err(err) => Err(error::ErrorBadRequest(err)), } @@ -717,13 +749,14 @@ pub async fn filewatcher_websocket( ) -> Result { client_websocket( format!("{FW}{connection_id_raw}"), - req, + &req, body, app_state.client_queues.clone(), ) } /// Return a unique ID for an IDE websocket connection. +#[must_use] pub fn get_connection_id_raw(app_state: &WebAppState) -> u32 { let mut connection_id_raw = app_state.filewatcher_next_connection_id.lock().unwrap(); *connection_id_raw += 1; @@ -749,14 +782,7 @@ mod tests { dev::{Service, ServiceResponse}, test, }; - use dunce::simplified; - use path_slash::PathExt; - use pretty_assertions::assert_eq; - use tokio::{select, sync::mpsc::Receiver, time::sleep}; - use url::Url; - - use super::FW; - use crate::{ + use code_chat_editor::{ processing::{ CodeChatForWeb, CodeMirror, CodeMirrorDiffable, SourceFileMetadata, TranslationResults, source_to_codechat_for_web, @@ -768,11 +794,20 @@ mod tests { configure_app, drop_leading_slash, make_app_data, send_response, set_root_path, }, }; + use dunce::simplified; + use path_slash::PathExt; + use pretty_assertions::assert_eq; + use tokio::{select, sync::mpsc::Receiver, time::sleep}; + use url::Url; + + use super::FW; use test_utils::{ cast, prep_test_dir, test_utils::{check_logger_errors, configure_testing_logger}, }; + use super::FilewatcherRoutes; + async fn get_websocket_queues( // A path to the temporary directory where the source file is located. test_dir: &Path, @@ -780,9 +815,14 @@ mod tests { WebsocketQueues, impl Service, Error = actix_web::Error> + use<>, ) { - set_root_path(None).unwrap(); + // Use this crate's own `root_path`, which correctly locates the repo + // root regardless of whether the test binary lives under + // `extensions/standalone/target/...` or (as a plain `cargo build`) + // deeper still under `.../target/debug/deps`. + set_root_path(&crate::root_path()).unwrap(); let app_data = make_app_data(None); - let app = test::init_service(configure_app(App::new(), &app_data)).await; + let app = + test::init_service(configure_app(App::new(), &app_data, &FilewatcherRoutes)).await; // Load in a test source file to create a websocket. let uri = format!("/fw/fsb/{}/test.py", test_dir.to_string_lossy()); @@ -815,7 +855,7 @@ mod tests { println!("{} - {:?}", m.id, m.message); m } - _ = sleep(Duration::from_secs(3)) => { + () = sleep(Duration::from_secs(3)) => { // The backtrace shows what message the code was waiting for; // otherwise, it's an unhelpful error message. panic!("Timeout waiting for message:\n{}", Backtrace::force_capture()); @@ -985,7 +1025,7 @@ mod tests { ), ( INITIAL_CLIENT_MESSAGE_ID + 2.0 * MESSAGE_ID_INCREMENT, - EditorMessageContents::ClientHtml("".to_string()), + EditorMessageContents::ClientHtml(String::new()), ), ( INITIAL_CLIENT_MESSAGE_ID + 3.0 * MESSAGE_ID_INCREMENT, @@ -1011,7 +1051,7 @@ mod tests { .send(EditorMessage { id: INITIAL_CLIENT_MESSAGE_ID + 4.0 * MESSAGE_ID_INCREMENT, message: EditorMessageContents::Update(UpdateMessageContents { - file_path: "".to_string(), + file_path: String::new(), cursor_position: None, scroll_position: None, is_re_translation: false, @@ -1020,7 +1060,7 @@ mod tests { mode: "cpp".to_string(), }, source: CodeMirrorDiffable::Plain(CodeMirror { - doc: "".to_string(), + doc: String::new(), doc_blocks: vec![], }), version: 0.0, diff --git a/server/src/main.rs b/extensions/standalone/src/main.rs similarity index 86% rename from server/src/main.rs rename to extensions/standalone/src/main.rs index 99c06299..3b11aa5e 100644 --- a/server/src/main.rs +++ b/extensions/standalone/src/main.rs @@ -13,6 +13,17 @@ // You should have received a copy of the GNU General Public License along with // the CodeChat Editor. If not, see // [http://www.gnu.org/licenses](http://www.gnu.org/licenses). +// +// Relax a few pedantic Clippy lints for test code only; see the `[lints.clippy]` +// section in `Cargo.toml` for the full-crate (production) lint config. +#![cfg_attr( + test, + allow( + clippy::too_many_lines, + clippy::cast_possible_truncation, + clippy::float_cmp + ) +)] //! `main.rs` -- Entrypoint for the CodeChat Editor Server //! ====================================================== //! @@ -47,7 +58,12 @@ use clap::{Parser, Subcommand}; use log::{LevelFilter, error, info}; // ### Local -use code_chat_editor::webserver::{self, Credentials, GetServerUrlError, path_to_url}; +use code_chat_editor::webserver::{ + self, Credentials, GetServerUrlError, LifecycleRoutes, path_to_url, +}; +use filewatcher::FilewatcherRoutes; + +mod filewatcher; // Data structures // --------------- @@ -91,7 +107,7 @@ enum Commands { log: Option, /// Define the username:password used to limit access to the server. By - /// default, access is unlimited. The username may not contain a colon.- + /// default, access is unlimited. The username may not contain a colon. #[arg(short, long, value_parser = parse_credentials)] auth: Option, }, @@ -110,6 +126,7 @@ enum Commands { // The following code implements the command-line interface for the CodeChat // Editor. impl Cli { + #[allow(clippy::too_many_lines)] fn run(self, addr: &SocketAddr) -> Result<(), Box> { match &self.command { Commands::Serve { @@ -123,10 +140,11 @@ impl Cli { return Ok(()); } webserver::main( - None, + &root_path(), addr, credentials.clone(), log.unwrap_or(LevelFilter::Info), + (FilewatcherRoutes, LifecycleRoutes), )?; } Commands::Start { open } => { @@ -158,11 +176,10 @@ impl Cli { } return Ok(()); - } else { - eprintln!( - "Unexpected response from server: {body}, status code = {status_code}" - ); } + eprintln!( + "Unexpected response from server: {body}, status code = {status_code}" + ); } Err(err) => { // Use this to skip the print from a nested if @@ -202,7 +219,7 @@ impl Cli { match self.test_mode { None => cmd = Command::new(¤t_exe), Some(TestMode::NotFound) => { - cmd = Command::new("nonexistent-command") + cmd = Command::new("nonexistent-command"); } Some(TestMode::Sleep) => { cmd = Command::new(¤t_exe); @@ -242,10 +259,12 @@ impl Cli { Ok(Some(status)) => { let mut stdout_buf = String::new(); let mut stderr_buf = String::new(); - let stdout = child.stdout.as_mut().unwrap(); - let stderr = child.stderr.as_mut().unwrap(); - stdout.read_to_string(&mut stdout_buf).unwrap(); - stderr.read_to_string(&mut stderr_buf).unwrap(); + if let Some(stdout) = child.stdout.as_mut() { + let _ = stdout.read_to_string(&mut stdout_buf); + } + if let Some(stderr) = child.stderr.as_mut() { + let _ = stderr.read_to_string(&mut stderr_buf); + } if status.success() { return Err(format!("Server unexpectedly shut down.\n{stdout_buf}\n{stderr_buf}").into()); } @@ -320,6 +339,8 @@ fn port_in_range(s: &str) -> Result { .parse() .map_err(|_| format!("`{s}` isn't a port number"))?; if PORT_RANGE.contains(&port) { + // Code above guarantees this is safe. + #[allow(clippy::cast_possible_truncation)] Ok(port as u16) } else { Err(format!( @@ -347,7 +368,7 @@ fn parse_credentials(s: &str) -> Result { /// This is used by `ping` to transform the "access connections from any /// address" address into localhost, a valid destination address for a ping. fn fix_addr(addr: &SocketAddr) -> SocketAddr { - if addr.ip() == IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)) { + if addr.ip() == IpAddr::V4(Ipv4Addr::UNSPECIFIED) { let mut addr = *addr; addr.set_ip(IpAddr::V4(Ipv4Addr::LOCALHOST)); addr @@ -360,7 +381,33 @@ fn fix_addr(addr: &SocketAddr) -> SocketAddr { } } -#[cfg(not(tarpaulin_include))] +/// Compute the root path to pass to `webserver::main`; see its docs (and +/// `webserver::set_root_path`'s) for what this must contain. In a `cargo +/// dist`-packaged build, this binary sits alongside `client/static`, +/// `log4rs.yml`, and `hashLocations.json` (see `dist.toml`'s `include`), so +/// this program's own directory is already the root. In a dev build, this +/// binary instead lives under `extensions/standalone/target/...`, so walk +/// back up to the repository root. +fn root_path() -> PathBuf { + let exe_dir = env::current_exe() + .expect("Unable to determine path to current executable.") + .parent() + .expect("Unable to find directory name containing the current executable.") + .to_path_buf(); + if cfg!(not(debug_assertions)) { + return exe_dir; + } + // A test binary (from an inline `#[cfg(test)] mod test`) lives in an + // extra `deps` directory (e.g. `target/debug/deps/`) compared to a plain + // `cargo build`'s `target/debug/`. + if cfg!(test) { + exe_dir.join("../../../../..") + } else { + exe_dir.join("../../../..") + } +} + +#[cfg_attr(coverage, coverage(off))] fn main() -> Result<(), Box> { let cli = Cli::parse(); let addr = SocketAddr::new(cli.host, cli.port); @@ -432,7 +479,7 @@ mod test { // ### `fix_addr` #[test] fn test_fix_addr_ipv4_unspecified() { - let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 8080); + let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 8080); let fixed = fix_addr(&addr); assert_eq!(fixed.ip(), IpAddr::V4(Ipv4Addr::LOCALHOST)); assert_eq!(fixed.port(), 8080); @@ -449,7 +496,7 @@ mod test { #[test] fn test_fix_addr_specific_unchanged() { // A specific (non-unspecified) address is returned unchanged. - let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); + let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8080); assert_eq!(fix_addr(&addr), addr); let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 5)), 1234); assert_eq!(fix_addr(&addr), addr); diff --git a/server/tests/cli.rs b/extensions/standalone/tests/cli.rs similarity index 82% rename from server/tests/cli.rs rename to extensions/standalone/tests/cli.rs index 733f2679..87435caa 100644 --- a/server/tests/cli.rs +++ b/extensions/standalone/tests/cli.rs @@ -19,15 +19,17 @@ // ------- // // ### Standard library -// -// None. -// +#[cfg(not(target_os = "macos"))] +use std::{thread::sleep, time::Duration}; + // ### Third-party use actix_web::{App, HttpResponse, HttpServer, web}; use assert_cmd::Command; use predicates::{prelude::predicate, str::contains}; // ### Local +#[cfg(not(target_os = "macos"))] +use test_utils::prep_test_dir; use tokio::task::spawn_blocking; // Support functions @@ -171,3 +173,33 @@ fn test_port_not_a_number() { let assert = get_server().args(["--port", "abc", "serve"]).assert(); assert.failure().stderr(contains("isn't a port number")); } + +// 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. 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(); + + // Wait for the server to exit, since it locks the temp\_dir. + sleep(Duration::from_secs(3)); + + // Report any errors produced when removing the temporary directory. + temp_dir.close().unwrap(); +} diff --git a/server/tests/fixtures/code_chat_editor/ide/filewatcher/tests/test_websocket_opened_1/test.py b/extensions/standalone/tests/fixtures/codechat_editor_standalone/filewatcher/tests/test_websocket_opened_1/test.py similarity index 100% rename from server/tests/fixtures/code_chat_editor/ide/filewatcher/tests/test_websocket_opened_1/test.py rename to extensions/standalone/tests/fixtures/codechat_editor_standalone/filewatcher/tests/test_websocket_opened_1/test.py diff --git a/server/tests/fixtures/code_chat_editor/ide/filewatcher/tests/test_websocket_opened_2/test.py b/extensions/standalone/tests/fixtures/codechat_editor_standalone/filewatcher/tests/test_websocket_update_1/test.py similarity index 100% rename from server/tests/fixtures/code_chat_editor/ide/filewatcher/tests/test_websocket_opened_2/test.py rename to extensions/standalone/tests/fixtures/codechat_editor_standalone/filewatcher/tests/test_websocket_update_1/test.py diff --git a/server/tests/fixtures/code_chat_editor/ide/filewatcher/tests/test_websocket_update_1/test1.py b/extensions/standalone/tests/fixtures/codechat_editor_standalone/filewatcher/tests/test_websocket_update_1/test1.py similarity index 100% rename from server/tests/fixtures/code_chat_editor/ide/filewatcher/tests/test_websocket_update_1/test1.py rename to extensions/standalone/tests/fixtures/codechat_editor_standalone/filewatcher/tests/test_websocket_update_1/test1.py diff --git a/server/Cargo.lock b/server/Cargo.lock index f3b70eeb..6deb7b10 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -302,9 +302,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "ammonia" -version = "4.1.3" +version = "4.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b9d3370580a12f4b7a10fdcc18b28942c083ba570e3d954fe59d10951b85a2" +checksum = "dc6d763210e2eb7670d1a5183a08bebefa3f97db2a738a684f2ce00bd49f681d" dependencies = [ "cssparser", "html5ever", @@ -321,56 +321,12 @@ dependencies = [ "libc", ] -[[package]] -name = "anstream" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - [[package]] name = "anstyle" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" -[[package]] -name = "anstyle-parse" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys 0.61.2", -] - [[package]] name = "anyhow" version = "1.0.104" @@ -386,21 +342,6 @@ dependencies = [ "rustversion", ] -[[package]] -name = "assert_cmd" -version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aa3a22042e45de04255c7bf3626e239f450200fd0493c1e382263544b20aea6" -dependencies = [ - "anstyle", - "bstr", - "libc", - "predicates", - "predicates-core", - "predicates-tree", - "wait-timeout", -] - [[package]] name = "assert_fs" version = "1.1.4" @@ -433,7 +374,7 @@ checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" dependencies = [ "proc-macro2", "quote", - "syn 3.0.2", + "syn 3.0.3", ] [[package]] @@ -551,7 +492,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f7dc094d718f2e1c1559ad110e27eeaae14a5465d3d56dd6dbd793079fbd530" dependencies = [ "memchr", - "regex-automata", "serde_core", ] @@ -615,9 +555,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" dependencies = [ "find-msvc-tools", "jobserver", @@ -662,46 +602,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "clap" -version = "4.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd059f9da4f5c36b3787f65d38ccaab1cc315f07b01f89abc8359ee6a8205011" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "clap_lex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" - [[package]] name = "cmake" version = "0.1.58" @@ -725,21 +625,16 @@ name = "codechat-editor-server" version = "0.2.0" dependencies = [ "actix-files", - "actix-http", "actix-rt", "actix-server", - "actix-service", "actix-web", "actix-web-httpauth", "actix-ws", "ammonia", - "anyhow", - "assert_cmd", "assert_fs", "assertables", "bytes", "chrono", - "clap", "dprint-plugin-markdown", "dunce", "futures", @@ -749,7 +644,6 @@ dependencies = [ "htmlize", "imara-diff", "indoc", - "lazy_static", "log", "log4rs", "markup5ever_rcdom", @@ -758,7 +652,6 @@ dependencies = [ "minify-html", "minreq", "normalize-line-endings", - "notify-debouncer-full", "path-slash", "pest", "pest_derive", @@ -783,15 +676,8 @@ dependencies = [ "url", "urlencoding", "webbrowser", - "windows", ] -[[package]] -name = "colorchoice" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" - [[package]] name = "combine" version = "4.6.7" @@ -1169,9 +1055,9 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "either" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" [[package]] name = "embedded-io" @@ -1216,15 +1102,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" -[[package]] -name = "file-id" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1fc6a637b6dc58414714eddd9170ff187ecb0933d4c7024d1abbd23a3cc26e9" -dependencies = [ - "windows-sys 0.60.2", -] - [[package]] name = "filetime" version = "0.2.29" @@ -1305,15 +1182,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" -[[package]] -name = "fsevent-sys" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" -dependencies = [ - "libc", -] - [[package]] name = "funty" version = "2.0.0" @@ -1525,12 +1393,6 @@ dependencies = [ "allocator-api2", ] -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - [[package]] name = "htmd" version = "0.5.4" @@ -1640,9 +1502,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" dependencies = [ "atomic-waker", "bytes", @@ -1825,9 +1687,9 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.30" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b009b6744c1445efd7244084e25e498636412effb6760b55067553baa925cc7" +checksum = "7f8a7b8211e695a1d0cd91cace480d4d0bd57667ab10277cc412c5f7f4884f83" dependencies = [ "crossbeam-deque", "globset", @@ -1851,9 +1713,9 @@ dependencies = [ [[package]] name = "impl-more" -version = "0.3.1" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35a84fd5aa25fae5c0f4a33d9cac2ca017fc622cbd089be2229993514990f870" +checksum = "277ff51754a3f68f12f58446c5d006aa8baa4914ea273cce24a599cfaff33d4f" [[package]] name = "indexmap" @@ -1876,38 +1738,12 @@ dependencies = [ "rustversion", ] -[[package]] -name = "inotify" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "153be1941a183ec9ccd095ddbe17a8b8d435ef6c76e9e02451b933c3999af2c8" -dependencies = [ - "bitflags", - "inotify-sys", - "libc", -] - -[[package]] -name = "inotify-sys" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c033f80b2c113cdf91ab7a33faa9cbc014726dcad99880c8609af2a370edf37d" -dependencies = [ - "libc", -] - [[package]] name = "ipnet" version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - [[package]] name = "itertools" version = "0.10.5" @@ -2023,26 +1859,6 @@ version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" -[[package]] -name = "kqueue" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "273c0752728918e0ac4976f2b275b6fefb9ecd400585dec929419f3844cd87b5" -dependencies = [ - "kqueue-sys", - "libc", -] - -[[package]] -name = "kqueue-sys" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" -dependencies = [ - "bitflags", - "libc", -] - [[package]] name = "language-tags" version = "0.3.2" @@ -2057,9 +1873,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.186" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libredox" @@ -2363,46 +2179,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" -[[package]] -name = "notify" -version = "8.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" -dependencies = [ - "bitflags", - "fsevent-sys", - "inotify", - "kqueue", - "libc", - "log", - "mio", - "notify-types", - "walkdir", - "windows-sys 0.60.2", -] - -[[package]] -name = "notify-debouncer-full" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02b49179cfebc9932238d04d6079912d26de0379328872846118a0fa0dbb302" -dependencies = [ - "file-id", - "log", - "notify", - "notify-types", - "walkdir", -] - -[[package]] -name = "notify-types" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" -dependencies = [ - "bitflags", -] - [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -2477,12 +2253,6 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - [[package]] name = "openssl-probe" version = "0.2.1" @@ -2524,9 +2294,9 @@ checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" [[package]] name = "oxc-browserslist" -version = "3.0.11" +version = "3.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50a403a3c6be65be7bc7730d07d959ec6c143e4ff8117da485df78cd5582260a" +checksum = "6967692b4c51de292573c3f5eed236b6df29ac47d122379ad3a9d209776a9cf3" dependencies = [ "miniz_oxide 0.9.1", "postcard", @@ -2968,9 +2738,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.7" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47627dd7305c6a2d6c8c6bcd24c5a4c17dbbf425f4f9c5313e724b38fc9782e9" +checksum = "7df728be843c7070fab6ab7c328c4e9e9d78e23bf749c0669c86ee7ebfa050a2" dependencies = [ "memchr", "ucd-trie", @@ -2978,9 +2748,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.7" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b4254325ecad416ab689e27ba51da03ba01a9632bc6e108f5fe7c3c4ad29d58" +checksum = "9e2dd6fc3b26b3462ee188aac870f5a41d398f1cd5e2408d16531bd71c9591fd" dependencies = [ "pest", "pest_generator", @@ -2988,9 +2758,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.7" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c4c0e91ead7a8f7acecbca6f003fc2e8282b1dbe2dd9c9d2f16aba42995e0a7" +checksum = "6a7a9205cfb6f596a9e8b689c0a15f9ceb7a1aafae7aaf788150ac65b29975b6" dependencies = [ "pest", "pest_meta", @@ -3001,9 +2771,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.8.7" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9744bc48116fee06334924bb5f2bad41eed5e89bd26e29b0b799f9a3f82c210" +checksum = "85abd351c0de1e8384fc791a0737111a350394937e92b956b743dac12429f57c" dependencies = [ "pest", ] @@ -3671,9 +3441,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" dependencies = [ "web-time", "zeroize", @@ -3847,7 +3617,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.2", + "syn 3.0.3", ] [[package]] @@ -3872,7 +3642,7 @@ checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" dependencies = [ "proc-macro2", "quote", - "syn 3.0.2", + "syn 3.0.3", ] [[package]] @@ -4068,12 +3838,6 @@ dependencies = [ "regex", ] -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - [[package]] name = "subtle" version = "2.6.1" @@ -4104,9 +3868,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a207d6d6a2b7fc470b80443726053f18a2481b7e1eee970597051596567987a3" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" dependencies = [ "proc-macro2", "quote", @@ -4208,9 +3972,9 @@ dependencies = [ [[package]] name = "thirtyfour" -version = "0.37.2" +version = "0.37.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799b1de3a58f1ed82060fbee54cc27bc87523e35ade57d9c80bc88ffc1420044" +checksum = "7cde234b9eff7d8f7e05e8af9de9c315f4500512a0a52535d7c6140bd2c54b7c" dependencies = [ "arc-swap", "async-trait", @@ -4242,9 +4006,9 @@ dependencies = [ [[package]] name = "thirtyfour-macros" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cf0ffc3ba4368e99597bd6afd83f4ff6febad66d9ae541ab46e697d32285fc0" +checksum = "d712d0fd1988fb67a3fd89d10ead116544448bad01e43d326bd64dd8e09b5ea4" dependencies = [ "proc-macro2", "quote", @@ -4268,7 +4032,7 @@ checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" dependencies = [ "proc-macro2", "quote", - "syn 3.0.2", + "syn 3.0.3", ] [[package]] @@ -4347,9 +4111,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.53.0" +version = "1.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d988bcd52dbe076d3d46903332f58c912b87a2c49b1428419a5845154762ffee" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" dependencies = [ "bytes", "libc", @@ -4385,9 +4149,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" dependencies = [ "futures-core", "pin-project-lite", @@ -4409,14 +4173,15 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" dependencies = [ "bytes", "futures-core", "futures-sink", "futures-util", + "libc", "pin-project-lite", "tokio", ] @@ -4686,12 +4451,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - [[package]] name = "uuid" version = "1.24.0" @@ -4732,15 +4491,6 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" -[[package]] -name = "wait-timeout" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" -dependencies = [ - "libc", -] - [[package]] name = "walkdir" version = "2.5.0" @@ -4931,27 +4681,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" -dependencies = [ - "windows-collections", - "windows-core", - "windows-future", - "windows-numerics", -] - -[[package]] -name = "windows-collections" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" -dependencies = [ - "windows-core", -] - [[package]] name = "windows-core" version = "0.62.2" @@ -4965,17 +4694,6 @@ dependencies = [ "windows-strings", ] -[[package]] -name = "windows-future" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" -dependencies = [ - "windows-core", - "windows-link", - "windows-threading", -] - [[package]] name = "windows-implement" version = "0.60.2" @@ -5004,16 +4722,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-numerics" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" -dependencies = [ - "windows-core", - "windows-link", -] - [[package]] name = "windows-result" version = "0.4.1" @@ -5038,16 +4746,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", + "windows-targets", ] [[package]] @@ -5065,40 +4764,14 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - -[[package]] -name = "windows-threading" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" -dependencies = [ - "windows-link", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] @@ -5107,96 +4780,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "wit-bindgen" version = "0.57.1" @@ -5269,18 +4894,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.54" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" +checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.54" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" +checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" dependencies = [ "proc-macro2", "quote", diff --git a/server/Cargo.toml b/server/Cargo.toml index 23f29691..ee7321f7 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -42,18 +42,14 @@ name = "code_chat_editor" # ------------ [dependencies] actix-files = "0.6" -actix-http = "3.9.0" actix-rt = "2.9.0" actix-server = "2.6.0" -actix-service = "2.0.3" actix-web = "4" actix-web-httpauth = "0.8.2" actix-ws = "0.4" ammonia = "4.1.2" -anyhow = "1.0.100" bytes = { version = "1", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] } -clap = { version = "4", features = ["derive"] } dprint-plugin-markdown = { git = "https://github.com/bjones1/dprint-plugin-markdown.git", branch = "all-fixes" } dunce = "1.0.5" futures-util = "0.3.29" @@ -63,10 +59,7 @@ html5ever = "0.39" htmlize = "1.0.6" imara-diff = { version = "0.2", features = [] } indoc = "2.0.5" -lazy_static = "1" -# Ideally, we'd set this in a non-library place, but this also builds a CLI. -# TODO: separate the CLI into a different crate. -log = { version = "0.4", features = ["release_max_level_warn"] } +log = "0.4" log4rs = "1.3" markup5ever_rcdom = "0.39" mime = "0.3.17" @@ -74,7 +67,6 @@ mime_guess = "2.0.5" minify-html = { git = "https://github.com/bjones1/minify-html.git", branch = "dev", version = "0.18.1" } minreq = "3" normalize-line-endings = "0.3.0" -notify-debouncer-full = "0.7" path-slash = "0.2.1" pest = "2.7.14" pest_derive = "2.7.14" @@ -100,14 +92,8 @@ url = "2.5.2" urlencoding = "2" webbrowser = "1.0.5" -# [Windows-only dependencies](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#platform-specific-dependencies). -[target.'cfg(windows)'.dependencies] -windows = { version = "0.62", features = ["Win32_Storage_FileSystem"] } - # ### Development-only dependencies [dev-dependencies] -actix-http = "3.9.0" -assert_cmd = "2" assert_fs = "1" assertables = "10" futures = "0.3.31" @@ -144,6 +130,38 @@ strip = "symbols" [profile.dist] inherits = "release" +# Code coverage +# ------------- +# [lints.rust] -# Avoid a lint about tarpaulin. -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tarpaulin_include)'] } +# Avoid a lint about the `coverage` cfg set by `cargo llvm-cov` (see `bt +# cover`). +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage)'] } + +# Clippy config +# ------------- +# +# A good article: https://emschwartz.me/your-clippy-config-should-be-stricter. +# +# Docs on clippy: see `cargo clippy --help`, `cargo check --help` (the target +# selection and feature selection flags seems to apply to clippy). +[lints.clippy] +# https://github.com/rust-lang/rust-clippy shows which categories are enabled by +# default; https://doc.rust-lang.org/stable/clippy/lints.html for a description +# of each of these categories. +# +# Individual lints below need an explicit `priority` higher than this group's +# implicit 0, or Cargo errors with "lint group `pedantic` has the same +# priority (0) as a lint" (see +# https://doc.rust-lang.org/cargo/reference/manifest.html#the-lints-section). +pedantic = { level = "warn", priority = 0 } +# Disable specific pedantic lints that don't apply. Use LP docs instead of +# rustdoc for errors and panics. +missing_errors_doc = { level = "allow", priority = 1 } +missing_panics_doc = { level = "allow", priority = 1 } +# This warns at the phrase "CodeChat Editor", so it's disabled. +doc_markdown = { level = "allow", priority = 1 } +# Some lints relaxed for test code only via a single +# `#![cfg_attr(test, allow(...))]` line in each crate root (`src/lib.rs`, +# `src/main.rs`) and a plain `#![allow(...)]` in each `tests/*.rs` integration +# test binary (which is entirely test code). diff --git a/server/src/capture.rs b/server/src/capture.rs index 21e1bb26..c80b38e0 100644 --- a/server/src/capture.rs +++ b/server/src/capture.rs @@ -105,6 +105,7 @@ pub enum CaptureEventType { } impl CaptureEventType { + #[must_use] pub const fn as_str(self) -> &'static str { match self { Self::WriteDoc => "write_doc", @@ -138,6 +139,7 @@ impl std::fmt::Display for CaptureEventType { /// Hash a local file path before it enters capture storage. The hash is stable /// enough to group edits to the same file while avoiding raw path collection. +#[must_use] pub fn hash_capture_path(path: &str) -> String { hash_capture_text(path) } @@ -147,10 +149,13 @@ fn hash_capture_token(token: &str) -> String { } fn hash_capture_text(value: &str) -> String { + use std::fmt::Write; Sha256::digest(value.as_bytes()) .iter() - .map(|byte| format!("{byte:02x}")) - .collect() + .fold(String::new(), |mut acc, byte| { + let _ = write!(acc, "{byte:02x}"); + acc + }) } /// JSON payload received from local clients for capture events. @@ -274,13 +279,14 @@ impl CaptureContext { data.entry("source".to_string()) .or_insert_with(|| serde_json::json!("server_translation")); + let user_id = self.user_id.clone()?; self.server_sequence_number += 1; Some(CaptureEventWire { event_id: None, sequence_number: Some(self.server_sequence_number), schema_version: self.schema_version, - user_id: self.user_id.clone()?, + user_id, session_id: self.session_id.clone(), event_source: Some("server_translation".to_string()), language_id: None, @@ -317,8 +323,8 @@ pub struct CaptureServiceConfig { } impl CaptureServiceConfig { - fn configured(base_url: String, token: Option) -> Result { - let base_url = normalize_service_base_url(&base_url)?; + fn configured(base_url: &str, token: Option) -> Result { + let base_url = normalize_service_base_url(base_url)?; Ok(Self { base_url: Some(base_url), token: token.filter(|token| !token.trim().is_empty()), @@ -462,6 +468,7 @@ pub struct CaptureStatus { } impl CaptureStatus { + #[must_use] pub fn disabled() -> Self { Self { enabled: false, @@ -517,6 +524,7 @@ pub struct CaptureEvent { } impl CaptureEvent { + #[must_use] pub fn new( user_id: String, file_hash: Option, @@ -541,6 +549,7 @@ impl CaptureEvent { } #[allow(clippy::too_many_arguments)] + #[must_use] pub fn with_columns( event_id: Option, sequence_number: Option, @@ -571,6 +580,7 @@ impl CaptureEvent { } } + #[must_use] pub fn now( user_id: String, file_hash: Option, @@ -732,7 +742,7 @@ impl EventCapture { thread::Builder::new() .name("codechat-capture-upload".to_string()) - .spawn(move || upload_worker(rx, config_worker, status_worker, spool_worker)) + .spawn(move || upload_worker(&rx, &config_worker, &status_worker, &spool_worker)) .map_err(|err| { io::Error::other(format!("Capture: failed to start upload worker: {err}")) })?; @@ -745,7 +755,7 @@ impl EventCapture { }) } - pub fn configure_service(&self, base_url: String, token: Option) -> Result<(), String> { + pub fn configure_service(&self, base_url: &str, token: Option) -> Result<(), String> { let mut new_config = CaptureServiceConfig::configured(base_url, token)?; let token_status = if new_config.token().is_some() { CaptureTokenStatus::Unverified @@ -764,7 +774,7 @@ impl EventCapture { status.enabled = true; status.state = CaptureState::Spooling; status.token_status = token_status; - status.service_base_url = new_config.base_url.clone(); + status.service_base_url.clone_from(&new_config.base_url); status.participant_id = None; status.instance_id = None; status.capture_enabled = None; @@ -826,7 +836,7 @@ impl EventCapture { /// Durably append an event to the local FIFO spool, then ask the worker to /// upload as soon as service access permits. - pub fn log(&self, event: CaptureEvent) { + pub fn log(&self, event: &CaptureEvent) { debug!( "Capture: spooling event: type={}, user_id={}, file_hash={:?}", event.event_type, event.user_id, event.file_hash @@ -846,7 +856,7 @@ impl EventCapture { } }; - match append_spool_event(&self.spool_path, &event, spool_identity) { + match append_spool_event(&self.spool_path, event, spool_identity) { Ok(()) => { update_status(&self.status, |status| { if matches!( @@ -881,15 +891,16 @@ impl EventCapture { } } + #[must_use] pub fn status(&self) -> CaptureStatus { - self.status - .lock() - .map(|status| status.clone()) - .unwrap_or_else(|_| { + self.status.lock().map_or_else( + |_| { let mut status = CaptureStatus::disabled(); status.last_error = Some("Capture status lock is poisoned".to_string()); status - }) + }, + |status| status.clone(), + ) } } @@ -901,15 +912,18 @@ fn update_status(status: &Arc>, f: impl FnOnce(&mut Capture } fn upload_worker( - rx: mpsc::Receiver, - config: Arc>, - status: Arc>, - spool_path: PathBuf, + rx: &mpsc::Receiver, + config: &Arc>, + status: &Arc>, + spool_path: &Path, ) { - info!("Capture: upload worker started with spool at {spool_path:?}."); + info!( + "Capture: upload worker started with spool at {}.", + spool_path.display() + ); let mut retry_delay = Duration::from_millis(INITIAL_RETRY_DELAY_MS); loop { - match upload_next_batch(&spool_path, &config, &status) { + match upload_next_batch(spool_path, config, status) { UploadOutcome::UploadedBatch => { retry_delay = Duration::from_millis(INITIAL_RETRY_DELAY_MS); continue; @@ -927,7 +941,7 @@ fn upload_worker( } let wait_for = if matches!( - capture_status_state(&status), + capture_status_state(status), CaptureState::ServiceUnavailable ) { retry_delay @@ -936,8 +950,7 @@ fn upload_worker( }; match rx.recv_timeout(wait_for) { - Ok(WorkerMsg::Flush) => {} - Err(RecvTimeoutError::Timeout) => {} + Ok(WorkerMsg::Flush) | Err(RecvTimeoutError::Timeout) => {} Err(RecvTimeoutError::Disconnected) => { warn!("Capture: upload worker channel closed; worker exiting."); break; @@ -945,7 +958,7 @@ fn upload_worker( } if matches!( - capture_status_state(&status), + capture_status_state(status), CaptureState::ServiceUnavailable ) { retry_delay = retry_delay @@ -958,8 +971,7 @@ fn upload_worker( fn capture_status_state(status: &Arc>) -> CaptureState { status .lock() - .map(|status| status.state) - .unwrap_or(CaptureState::Disabled) + .map_or(CaptureState::Disabled, |status| status.state) } #[derive(Debug, PartialEq, Eq)] @@ -971,6 +983,7 @@ enum UploadOutcome { TransientFailure, } +#[allow(clippy::too_many_lines)] fn upload_next_batch( spool_path: &Path, config: &Arc>, @@ -1078,7 +1091,10 @@ fn upload_next_batch( if !run_if_capture_config_snapshot_is_current(config, &cfg, || { for file in &batch.files { if let Err(err) = fs::remove_file(file) { - warn!("Capture: unable to remove uploaded spool file {file:?}: {err}"); + warn!( + "Capture: unable to remove uploaded spool file {}: {err}", + file.display() + ); } } update_status(status, |status| { @@ -1099,11 +1115,7 @@ fn upload_next_batch( UploadOutcome::UploadedBatch } Err(err) => match err.status_code { - Some(401) => { - apply_http_error_if_current(config, status, &cfg, &err); - UploadOutcome::Paused - } - Some(403) => { + Some(401 | 403) => { apply_http_error_if_current(config, status, &cfg, &err); UploadOutcome::Paused } @@ -1197,6 +1209,7 @@ fn update_spool_count(spool_path: &Path, status: &Arc>) { } } +#[allow(clippy::too_many_lines)] fn build_next_batch( spool_path: &Path, cfg: &CaptureServiceConfig, @@ -1333,8 +1346,7 @@ fn spool_record_matches_current_identity(record: &SpoolRecord, cfg: &CaptureServ cfg.participant_id .as_deref() - .map(|participant_id| record.event.user_id == participant_id) - .unwrap_or(false) + .is_some_and(|participant_id| record.event.user_id == participant_id) } fn read_spool_record(path: &Path) -> Result { @@ -1593,10 +1605,10 @@ fn quarantine_files( } for file in files { - let file_name = file - .file_name() - .map(|name| name.to_owned()) - .unwrap_or_else(|| "capture-event.json".into()); + let file_name = file.file_name().map_or_else( + || "capture-event.json".into(), + std::borrow::ToOwned::to_owned, + ); let target = quarantine.join(file_name); if let Err(err) = fs::rename(file, &target).or_else(|_| { fs::copy(file, &target)?; @@ -1604,13 +1616,16 @@ fn quarantine_files( }) { update_status(status, |status| { status.failed_events += 1; - status.last_error = Some(format!("Unable to quarantine {file:?}: {err}")); + status.last_error = Some(format!("Unable to quarantine {}: {err}", file.display())); }); continue; } let reason_path = target.with_extension("reason.txt"); if let Err(err) = fs::write(&reason_path, reason) { - warn!("Capture: unable to write quarantine reason {reason_path:?}: {err}"); + warn!( + "Capture: unable to write quarantine reason \"{}\": {err}", + reason_path.display() + ); } update_status(status, |status| { status.quarantined_events += 1; @@ -1658,7 +1673,7 @@ mod tests { fn capture_service_config(token: &str, generation: u64) -> CaptureServiceConfig { let mut cfg = CaptureServiceConfig::configured( - "https://capture.example/dev".to_string(), + "https://capture.example/dev", Some(token.to_string()), ) .expect("capture service config should parse"); @@ -1672,9 +1687,8 @@ mod tests { generation: u64, participant_id: &str, ) -> CaptureServiceConfig { - let mut cfg = - CaptureServiceConfig::configured(base_url.to_string(), Some(token.to_string())) - .expect("capture service config should parse"); + let mut cfg = CaptureServiceConfig::configured(base_url, Some(token.to_string())) + .expect("capture service config should parse"); cfg.generation = generation; cfg.participant_id = Some(participant_id.to_string()); cfg.instance_id = Some(format!("{participant_id}-instance")); @@ -1827,7 +1841,7 @@ mod tests { let _ = fs::remove_dir_all(&spool_path); let capture = EventCapture::new(spool_path.clone()).expect("capture worker should start"); - capture.log(CaptureEvent::with_columns( + capture.log(&CaptureEvent::with_columns( Some("event-1".to_string()), Some(1), Some(2), @@ -2151,14 +2165,14 @@ mod tests { let _ = fs::remove_dir_all(&spool_path); let mut old_cfg = CaptureServiceConfig::configured( - "https://capture.example/dev".to_string(), + "https://capture.example/dev", Some("old-token".to_string()), ) .expect("old config should parse"); old_cfg.participant_id = Some("old-user".to_string()); old_cfg.instance_id = Some("old-instance".to_string()); let mut new_cfg = CaptureServiceConfig::configured( - "https://capture.example/dev".to_string(), + "https://capture.example/dev", Some("new-token".to_string()), ) .expect("new config should parse"); @@ -2208,12 +2222,12 @@ mod tests { let _ = fs::remove_dir_all(&spool_path); let old_cfg = CaptureServiceConfig::configured( - "https://capture.example/dev".to_string(), + "https://capture.example/dev", Some("old-token".to_string()), ) .expect("old config should parse"); let mut new_cfg = CaptureServiceConfig::configured( - "https://capture.example/dev".to_string(), + "https://capture.example/dev", Some("new-token".to_string()), ) .expect("new config should parse"); @@ -2246,7 +2260,7 @@ mod tests { let _ = fs::remove_dir_all(&spool_path); let cfg = CaptureServiceConfig::configured( - "https://capture.example/dev".to_string(), + "https://capture.example/dev", Some("token".to_string()), ) .expect("config should parse"); diff --git a/server/src/ide.rs b/server/src/ide.rs index e7d8cede..6c690d22 100644 --- a/server/src/ide.rs +++ b/server/src/ide.rs @@ -29,9 +29,9 @@ //! //! All internal fields are private so that IDE extensions are forced to use //! only this public interface, hiding the implementation details of the server. -//! Sub-modules (`filewatcher` — file watcher support, `vscode`) contain -//! IDE-specific logic. -pub mod filewatcher; +//! The `vscode` sub-module contains IDE-specific logic. (The file watcher +//! "IDE" lives in `extensions/standalone`, since it's used only by that +//! binary.) pub mod vscode; // Imports @@ -41,6 +41,7 @@ pub mod vscode; use std::{ collections::HashMap, net::{IpAddr, Ipv4Addr, SocketAddr}, + path::Path, sync::Arc, thread, time::Duration, @@ -64,7 +65,7 @@ use tokio::{ // ### Local use crate::{ capture::CaptureEventWire, - ide::vscode::{connection_id_raw_to_str, vscode_ide_core}, + ide::vscode::{VscodeRoutes, connection_id_raw_to_str, vscode_ide_core}, processing::{CodeChatForWeb, CodeMirror, CodeMirrorDiffable, SourceFileMetadata}, translation::{CreatedTranslationQueues, create_translation_queues}, webserver::{ @@ -109,18 +110,19 @@ impl CodeChatEditorServer { // Creating the server could fail, so this must return an `io::Result`. pub fn new() -> std::io::Result { let capture_spool_path = webserver::ROOT_PATH.lock().unwrap().join("capture-spool"); - Self::new_with_capture_spool(capture_spool_path) + Self::new_with_capture_spool(&capture_spool_path) } pub fn new_with_capture_spool( - capture_spool_path: std::path::PathBuf, + capture_spool_path: &Path, ) -> std::io::Result { // Start the server. let (server, app_state) = webserver::setup_server_with_capture_spool( // A port of 0 requests the OS to assign an open port. - &SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0), + &SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0), None, capture_spool_path, + VscodeRoutes, )?; let server_handle = server.handle(); @@ -165,9 +167,10 @@ impl CodeChatEditorServer { }) } - // This returns an error if the conversion to JSON fails, `None` if the - // queue is closed, or a JSON-encoded string containing the message - // otherwise. + // This returns `None` if both the incoming-message queue and the + // expired-message queue are closed, or the next available message + // (an incoming message, or a synthetic timeout `Result` for an + // unacknowledged message) otherwise. pub async fn get_message(&self) -> Option { // Get a message -- either an expired message result or an incoming // message. @@ -197,7 +200,7 @@ impl CodeChatEditorServer { // Like `get_message`, but with a timeout. pub async fn get_message_timeout(&self, timeout: Duration) -> Option { select! { - _ = sleep(timeout) => None, + () = sleep(timeout) => None, v = self.get_message() => v } } @@ -231,11 +234,8 @@ impl CodeChatEditorServer { sleep(REPLY_TIMEOUT_MS).await; // Since the websocket failed to send a `Result`, produce a timeout // `Result` for it. - match expired_messages_tx.send(id).await { - Ok(join_handle) => join_handle, - Err(err) => { - error!("Error -- unable to send expired message: {err}"); - } + if let Err(err) = expired_messages_tx.send(id).await { + error!("Error -- unable to send expired message: {err}"); } }); // Add this to the list of pending message. @@ -271,13 +271,14 @@ impl CodeChatEditorServer { .await } + #[must_use] pub fn capture_status(&self) -> crate::capture::CaptureStatus { webserver::capture_status(&self.app_state) } pub fn configure_capture_service( &self, - base_url: String, + base_url: &str, token: Option, ) -> Result<(), String> { self.app_state @@ -327,13 +328,13 @@ impl CodeChatEditorServer { self.send_message_timeout(EditorMessageContents::Update(UpdateMessageContents { file_path, cursor_position: cursor_position.map(CursorPosition::Line), - scroll_position: scroll_position.map(|x| x as f32), + scroll_position, is_re_translation: false, contents: option_contents.map(|contents| CodeChatForWeb { metadata: SourceFileMetadata { // The IDE doesn't need to provide the `mode`; this will // determined by the server. - mode: "".to_string(), + mode: String::new(), }, source: CodeMirrorDiffable::Plain(CodeMirror { doc: contents.0, diff --git a/server/src/ide/vscode.rs b/server/src/ide/vscode.rs index b6fa791e..104a9b14 100644 --- a/server/src/ide/vscode.rs +++ b/server/src/ide/vscode.rs @@ -30,11 +30,12 @@ pub mod tests; // // ### Third-party use actix_web::{ - HttpRequest, HttpResponse, + App, HttpRequest, HttpResponse, + dev::{ServiceFactory, ServiceRequest}, error::{Error, ErrorBadRequest}, get, web, }; -use htmlize::escape_text; +use htmlize::{escape_attribute, escape_text}; use indoc::formatdoc; use log::{debug, error}; @@ -46,9 +47,9 @@ use crate::{ translation_task, }, webserver::{ - EditorMessage, EditorMessageContents, IdeType, RESERVED_MESSAGE_ID, ResultErrTypes, - ResultOkTypes, WebAppState, client_websocket, filesystem_endpoint, get_client_framework, - get_server_url, html_wrapper, send_response, + EditorMessage, EditorMessageContents, IdeType, RESERVED_MESSAGE_ID, RegisterRoutes, + ResultErrTypes, ResultOkTypes, WebAppState, client_websocket, filesystem_endpoint, + get_client_framework, get_server_url, html_wrapper, send_response, }, }; @@ -59,6 +60,24 @@ const VSC: &str = "vsc-"; // Code // ---- +// +// Registers the VSCode IDE's routes with the server's `configure_app` (see +// `crate::webserver::RegisterRoutes`). +#[derive(Clone)] +pub struct VscodeRoutes; + +impl RegisterRoutes for VscodeRoutes { + fn register(&self, app: App) -> App + where + T: ServiceFactory, + { + app.service(serve_vscode_fs) + .service(vscode_ide_websocket) + .service(vscode_client_websocket) + .service(vscode_client_framework) + } +} + #[get("/vsc/ws-ide/{connection_id_raw}")] pub async fn vscode_ide_websocket( connection_id_raw: web::Path, @@ -78,7 +97,7 @@ pub async fn vscode_ide_websocket( CreateTranslationQueuesError::IdeInUse(connection_id_str) => { return client_websocket( connection_id_str, - req, + &req, body, app_state.ide_queues.clone(), ); @@ -89,11 +108,12 @@ pub async fn vscode_ide_websocket( // Move data between the IDE and the processing task via queues. The // websocket connection between the client and the IDE will run in the // endpoint for that connection. - client_websocket(connection_id, req, body, app_state.ide_queues.clone()) + client_websocket(connection_id, &req, body, app_state.ide_queues.clone()) } // Given a (random) connection ID produced by the IDE, return a string that // gives a VSCode-specific ID. +#[must_use] pub fn connection_id_raw_to_str(connection_id_raw: &str) -> String { format!("{VSC}{connection_id_raw}") } @@ -101,6 +121,7 @@ pub fn connection_id_raw_to_str(connection_id_raw: &str) -> String { // Create queues, perform the IDE startup sequence, then enter a loop // translating between the IDE and Client. The caller must provide a task to // move data to/from the IDE queues. +#[allow(clippy::too_many_lines)] pub fn vscode_ide_core( connection_id_raw: String, app_state: WebAppState, @@ -126,7 +147,7 @@ pub fn vscode_ide_core( // Make sure it's the `Opened` message. let EditorMessageContents::Opened(ide_type) = first_message.message else { - let err = ResultErrTypes::UnexpectedMessage(format!("{:#?}", first_message)); + let err = ResultErrTypes::UnexpectedMessage(format!("{first_message:#?}")); error!("{err}"); send_response(&to_ide_tx, first_message.id, Err(err)).await; @@ -137,113 +158,115 @@ pub fn vscode_ide_core( debug!("Received IDE Opened message."); // Ensure the IDE type (VSCode) is correct. - match ide_type { - IdeType::VSCode(is_self_hosted) => { - // Get the address for the server. - let port = *app_state_task.port.lock().unwrap(); - let address = match get_server_url(port).await { - Ok(address) => address, - Err(err) => { - error!("{err:?}"); - break 'task; - } - }; - if is_self_hosted { - // Send a response (successful) to the `Opened` message. - debug!( - "Sending response = OK to IDE Opened message, id {}.", - first_message.id - ); - send_response(&to_ide_tx, first_message.id, Ok(ResultOkTypes::Void)).await; - // Send the HTML for the internal browser. The ID of - // this message is `RESERVED_MESSAGE_ID`. - let client_html = formatdoc!( - r#" - - - - - - - - "# - ); - debug!("Sending ClientHtml message to IDE: {client_html}"); - queue_send!(to_ide_tx.send(EditorMessage { - id: RESERVED_MESSAGE_ID, - message: EditorMessageContents::ClientHtml(client_html) - }), 'task); + if let IdeType::VSCode(is_self_hosted) = ide_type { + // Get the address for the server. + let port = *app_state_task.port.lock().unwrap(); + let address = match get_server_url(port).await { + Ok(address) => address, + Err(err) => { + error!("{err:?}"); + break 'task; + } + }; + if is_self_hosted { + // Send a response (successful) to the `Opened` message. + debug!( + "Sending response = OK to IDE Opened message, id {}.", + first_message.id + ); + send_response(&to_ide_tx, first_message.id, Ok(ResultOkTypes::Void)).await; + // Send the HTML for the internal browser. The ID of + // this message is `RESERVED_MESSAGE_ID`. Escape the + // connection ID before embedding it in an HTML + // attribute, since it's supplied by whoever connects to + // the IDE websocket and could otherwise be used to break + // out of the `src` attribute and inject markup. + let escaped_connection_id_raw = escape_attribute(connection_id_raw.as_str()); + let client_html = formatdoc!( + r#" + + + + + + + + "# + ); + debug!("Sending ClientHtml message to IDE: {client_html}"); + queue_send!(to_ide_tx.send(EditorMessage { + id: RESERVED_MESSAGE_ID, + message: EditorMessageContents::ClientHtml(client_html) + }), 'task); - // Wait for the response. - let Some(message) = from_ide_rx.recv().await else { - error!("{}", "IDE websocket received no data."); - break 'task; - }; + // Wait for the response. + let Some(message) = from_ide_rx.recv().await else { + error!("{}", "IDE websocket received no data."); + break 'task; + }; - // Make sure it's the `Result` message with no errors. - let res = - // First, make sure the ID matches. - if message.id != RESERVED_MESSAGE_ID { - Err(format!("Unexpected message ID {}.", message.id)) - } else { - match message.message { - EditorMessageContents::Result(message_result) => match message_result { - Err(err) => Err(format!("Error in ClientHtml: {err}")), - Ok(result_ok) => - if let ResultOkTypes::Void = result_ok { - Ok(()) - } else { - Err(format!( - "Unexpected Result message with LoadFile contents {result_ok:?}." - )) - } - }, - _ => Err(format!("Unexpected message {message:?}")), - } - }; - if let Err(err) = res { - error!("{err}"); - // Send a `Closed` message using the next available - // ID (`RESERVED_MESSAGE_ID + 1`). - queue_send!(to_ide_tx.send(EditorMessage { - id: RESERVED_MESSAGE_ID + 1.0, - message: EditorMessageContents::Closed - }), 'task); - break 'task; + // Make sure it's the `Result` message with no errors. + let res = + // First, make sure the ID matches. + if message.id == RESERVED_MESSAGE_ID { + match message.message { + EditorMessageContents::Result(message_result) => match message_result { + Err(err) => Err(format!("Error in ClientHtml: {err}")), + Ok(result_ok) => + if let ResultOkTypes::Void = result_ok { + Ok(()) + } else { + Err(format!( + "Unexpected Result message with LoadFile contents {result_ok:?}." + )) + } + }, + _ => Err(format!("Unexpected message {message:?}")), + } + } else { + Err(format!("Unexpected message ID {}.", message.id)) }; - } else { - // Open the Client in an external browser. - if let Err(err) = - webbrowser::open(&format!("{address}/vsc/cf/{connection_id_raw}")) - { - let err = ResultErrTypes::WebBrowserOpenFailed(err.to_string()); - error!("{err:?}"); - send_response(&to_ide_tx, first_message.id, Err(err)).await; + if let Err(err) = res { + error!("{err}"); + // Send a `Closed` message using the next available + // ID (`RESERVED_MESSAGE_ID + 1`). + queue_send!(to_ide_tx.send(EditorMessage { + id: RESERVED_MESSAGE_ID + 1.0, + message: EditorMessageContents::Closed + }), 'task); + break 'task; + } + } else { + // Open the Client in an external browser. + if let Err(err) = + webbrowser::open(&format!("{address}/vsc/cf/{connection_id_raw}")) + { + let err = ResultErrTypes::WebBrowserOpenFailed(err.to_string()); + error!("{err:?}"); + send_response(&to_ide_tx, first_message.id, Err(err)).await; - // Send a `Closed` message; use an ID of - // `RESERVED_MESSAGE_ID`, since this is the first - // message from the IDE. - queue_send!(to_ide_tx.send(EditorMessage{ - id: RESERVED_MESSAGE_ID, - message: EditorMessageContents::Closed - }), 'task); - break 'task; - } - // Send a response (successful) to the `Opened` message. - send_response(&to_ide_tx, first_message.id, Ok(ResultOkTypes::Void)).await; + // Send a `Closed` message; use an ID of + // `RESERVED_MESSAGE_ID`, since this is the first + // message from the IDE. + queue_send!(to_ide_tx.send(EditorMessage{ + id: RESERVED_MESSAGE_ID, + message: EditorMessageContents::Closed + }), 'task); + break 'task; } + // Send a response (successful) to the `Opened` message. + send_response(&to_ide_tx, first_message.id, Ok(ResultOkTypes::Void)).await; } - _ => { - // This is the wrong IDE type. Report the error. - let err = ResultErrTypes::InvalidIdeType(ide_type); - error!("{err:?}"); - send_response(&to_ide_tx, first_message.id, Err(err)).await; + } else { + // This is the wrong IDE type. Report the error. + let err = ResultErrTypes::InvalidIdeType(ide_type); + error!("{err:?}"); + send_response(&to_ide_tx, first_message.id, Err(err)).await; - // Close the connection, again using the first available ID - // of `RESERVED_MESSAGE_ID`. - queue_send!(to_ide_tx.send(EditorMessage { id: RESERVED_MESSAGE_ID, message: EditorMessageContents::Closed}), 'task); - break 'task; - } + // Close the connection, again using the first available ID + // of `RESERVED_MESSAGE_ID`. + queue_send!(to_ide_tx.send(EditorMessage { id: RESERVED_MESSAGE_ID, message: EditorMessageContents::Closed}), 'task); + break 'task; } shutdown_only = false; } @@ -288,7 +311,7 @@ pub async fn vscode_client_websocket( ) -> Result { client_websocket( format!("{VSC}{connection_id}"), - req, + &req, body, app_state.client_queues.clone(), ) diff --git a/server/src/ide/vscode/tests.rs b/server/src/ide/vscode/tests.rs index 226ee88a..b6b42a14 100644 --- a/server/src/ide/vscode/tests.rs +++ b/server/src/ide/vscode/tests.rs @@ -23,6 +23,7 @@ use std::{ io::{Error, Read}, net::SocketAddr, path::{self, Path, PathBuf}, + sync::LazyLock, thread::{self, JoinHandle}, time::{Duration, SystemTime}, }; @@ -32,7 +33,6 @@ use assertables::{assert_contains, assert_ends_with, assert_starts_with}; use dunce::simplified; use futures_util::{SinkExt, StreamExt}; use indoc::indoc; -use lazy_static::lazy_static; use minreq; use path_slash::PathExt; use pretty_assertions::assert_eq; @@ -51,6 +51,11 @@ use crate::webserver::{ EditorMessage, EditorMessageContents, INITIAL_CLIENT_MESSAGE_ID, INITIAL_IDE_MESSAGE_ID, INITIAL_MESSAGE_ID, IdeType, MESSAGE_ID_INCREMENT, ResultErrTypes, }; +use crate::{ + ide::vscode::VscodeRoutes, + translation::{EolType, find_eol_type}, + webserver::{LifecycleRoutes, main, test_root_path}, +}; use crate::{ processing::{ CodeChatForWeb, CodeMirror, CodeMirrorDiff, CodeMirrorDiffable, CodeMirrorDocBlock, @@ -58,10 +63,6 @@ use crate::{ }, webserver::{ResultOkTypes, UpdateMessageContents, drop_leading_slash}, }; -use crate::{ - translation::{EolType, find_eol_type}, - webserver::main, -}; use test_utils::{ cast, test_utils::{check_logger_errors, configure_testing_logger, prep_test_dir_impl}, @@ -69,18 +70,18 @@ use test_utils::{ // Globals // ------- -lazy_static! { - // Run a single webserver for all tests. - static ref WEBSERVER_HANDLE: JoinHandle> = - thread::spawn(|| { - main( - None, - &SocketAddr::new("127.0.0.1".parse().unwrap(), IP_PORT), - None, - log::LevelFilter::Debug - ) - }); -} +// Run a single webserver for all tests. +static WEBSERVER_HANDLE: LazyLock>> = LazyLock::new(|| { + thread::spawn(|| { + main( + &test_root_path(), + &SocketAddr::new("127.0.0.1".parse().unwrap(), IP_PORT), + None, + log::LevelFilter::Debug, + (VscodeRoutes, LifecycleRoutes), + ) + }) +}); // Constants // --------- @@ -112,7 +113,7 @@ async fn read_message( let msg_txt = loop { let msg = select! { data = ws_stream.next() => data.unwrap().unwrap(), - _ = sleep(Duration::from_secs(6) - now.elapsed().unwrap()) => panic!("Timeout waiting for message") + () = sleep(Duration::from_secs(6).checked_sub(now.elapsed().unwrap()).unwrap()) => panic!("Timeout waiting for message") }; match msg { Message::Close(_) => panic!("Unexpected close message."), @@ -130,7 +131,7 @@ async fn read_message( type WebSocketStreamTcp = WebSocketStream>; async fn connect_async_server(prefix: &str, connection_id: &str) -> WebSocketStreamTcp { - connect_async(format!("ws://127.0.0.1:{IP_PORT}{prefix}/{connection_id}",)) + connect_async(format!("ws://127.0.0.1:{IP_PORT}{prefix}/{connection_id}")) .await .expect("Failed to connect") .0 @@ -194,7 +195,7 @@ async fn open_client(ws_ide: &mut WebSocketSt /// Perform all the setup for testing the Server via IDE and Client websockets. /// This should be invoked by the `prep_test!` macro; otherwise, test files /// won't be found. -async fn _prep_test( +async fn prep_test_core( connection_id: &str, test_full_name: &str, ) -> (TempDir, PathBuf, WebSocketStreamTcp, WebSocketStreamTcp) { @@ -205,7 +206,7 @@ async fn _prep_test( let now = SystemTime::now(); let mut started = false; while now.elapsed().unwrap() < WEBSERVER_START_TIMEOUT { - if minreq::get(format!("http://127.0.0.1:{IP_PORT}/ping",)) + if minreq::get(format!("http://127.0.0.1:{IP_PORT}/ping")) .send() .is_ok_and(|response| response.as_bytes() == b"pong") { @@ -224,13 +225,13 @@ async fn _prep_test( (temp_dir, test_dir, ws_ide, ws_client) } -/// This calls `_prep_test` with the current function name. It must be a macro, +/// This calls `prep_test_core` with the current function name. It must be a macro, /// so that it's called with the test function's name; calling it inside -/// `_prep_test` would give the wrong name. +/// `prep_test_core` would give the wrong name. macro_rules! prep_test { ($connection_id: ident) => {{ use test_utils::function_name; - _prep_test($connection_id, function_name!()) + prep_test_core($connection_id, function_name!()) }}; } @@ -263,7 +264,7 @@ async fn test_vscode_ide_websocket1() { &EditorMessage { id: INITIAL_IDE_MESSAGE_ID, message: EditorMessageContents::Update(UpdateMessageContents { - file_path: "".to_string(), + file_path: String::new(), cursor_position: None, scroll_position: None, is_re_translation: false, @@ -349,7 +350,7 @@ async fn test_vscode_ide_websocket3() { .unwrap() .status_code, 404 - ) + ); }); // The HTTP request produces a `LoadFile` message. @@ -393,7 +394,7 @@ async fn test_vscode_ide_websocket3a() { let file_path = test_dir.join("test.py"); // Force the path separator to be Window-style for this test, even on // non-Windows platforms. - let file_path_str = file_path.to_str().unwrap().to_string().replace("/", "\\"); + let file_path_str = file_path.to_str().unwrap().to_string().replace('/', "\\"); // Since we expect a 404 error, wait some to ensure the 404 results from not // finding the requested file, instead of the web server not being fully @@ -412,7 +413,7 @@ async fn test_vscode_ide_websocket3a() { .unwrap() .status_code, 404 - ) + ); }); // The HTTP request produces a `LoadFile` message. @@ -507,7 +508,7 @@ async fn test_vscode_ide_websocket8() { .unwrap() .status_code, 200 - ) + ); }); // This should produce a `LoadFile` message. @@ -557,7 +558,7 @@ async fn test_vscode_ide_websocket8() { doc_blocks: vec![CodeMirrorDocBlock { from: 0, to: 1, - indent: "".to_string(), + indent: String::new(), delimiter: "#".to_string(), contents: "

    testing".to_string() }], @@ -689,7 +690,7 @@ async fn test_vscode_ide_websocket7() { doc_blocks: vec![CodeMirrorDocBlock { from: 0, to: 1, - indent: "".to_string(), + indent: String::new(), delimiter: "#".to_string(), contents: "

    more".to_string() }] @@ -769,7 +770,7 @@ async fn test_vscode_ide_websocket7() { doc_blocks: vec![CodeMirrorDocBlockTransaction::Add(CodeMirrorDocBlock { from: 6, to: 7, - indent: "".to_string(), + indent: String::new(), delimiter: "#".to_string(), contents: "

    most".to_string(), },),], @@ -828,7 +829,7 @@ async fn test_vscode_ide_websocket6() { doc_blocks: vec![CodeMirrorDocBlock { from: 0, to: 1, - indent: "".to_string(), + indent: String::new(), delimiter: "#".to_string(), contents: "less\n".to_string(), }], @@ -996,7 +997,7 @@ async fn test_vscode_ide_websocket4() { doc_blocks: vec![CodeMirrorDocBlock { from: 0, to: 1, - indent: "".to_string(), + indent: String::new(), delimiter: "#".to_string(), contents: "

    test.py".to_string() }], diff --git a/server/src/lexer.rs b/server/src/lexer.rs index faa7aa1f..fc4c5c77 100644 --- a/server/src/lexer.rs +++ b/server/src/lexer.rs @@ -24,10 +24,13 @@ pub mod supported_languages; // ------- // // ### Standard library -use std::{cmp::min, collections::HashMap, sync::Arc}; +use std::{ + cmp::min, + collections::HashMap, + sync::{Arc, LazyLock}, +}; // ### Third-party -use lazy_static::lazy_static; use log::trace; use regex::Regex; @@ -263,12 +266,13 @@ pub enum CodeDocBlock { // // Create constant regexes needed by the lexer, following the // [Regex docs recommendation](https://docs.rs/regex/1.6.0/regex/index.html#example-avoid-compiling-the-same-regex-in-a-loop). -lazy_static! { - static ref WHITESPACE_ONLY_REGEX: Regex = Regex::new("^[[:space:]]*$").unwrap(); - /// TODO: This regex should also allow termination on an unescaped `${` - /// sequence, which then must count matching braces to find the end of the - /// expression. - static ref TEMPLATE_LITERAL_CLOSING_REGEX: Regex = Regex::new( +static WHITESPACE_ONLY_REGEX: LazyLock = + LazyLock::new(|| Regex::new("^[[:space:]]*$").unwrap()); +/// TODO: This regex should also allow termination on an unescaped `${` +/// sequence, which then must count matching braces to find the end of the +/// expression. +static TEMPLATE_LITERAL_CLOSING_REGEX: LazyLock = LazyLock::new(|| { + Regex::new( // Allow `.` to match *any* character, including a newline. See the // [regex docs](https://docs.rs/regex/1.6.0/regex/index.html#grouping-and-flags). &("(?s)".to_string() + @@ -285,11 +289,13 @@ lazy_static! { ")*" + // Now, find the end of the string: the string delimiter. "`"), - ).unwrap(); + ) + .unwrap() +}); - /// A vector of all supported languages. - pub static ref LEXERS: LanguageLexersCompiled = compile_lexers(get_language_lexer_vec()); -} +/// A vector of all supported languages. +pub static LEXERS: LazyLock = + LazyLock::new(|| compile_lexers(get_language_lexer_vec())); // Support C# verbatim string literals, which end with a `"`; a `""` inserts a // single " in the string. @@ -301,6 +307,7 @@ const C_SHARP_VERBATIM_STRING_CLOSING: &str = /// ### Language "compiler" /// /// "Compile" a language description into regexes used to lex the language. +#[allow(clippy::too_many_lines)] fn build_lexer_regex( // The language description to build regexes for. language_lexer: LanguageLexer, @@ -375,7 +382,7 @@ fn build_lexer_regex( // If this is a single-character string delimiter, then we're done. if delimiter.chars().count() < 2 { return String::new(); - }; + } // Otherwise, build a vector of substrings of the delimiter: for a // delimiter of `'''`, we want `["", "'"", "''"]`. @@ -487,7 +494,7 @@ fn build_lexer_regex( // Look for either the delimiter or a newline to terminate the // string. - (false, NewlineSupport::None) => Regex::new(&format!("{}|\n", escaped_delimiter)), + (false, NewlineSupport::None) => Regex::new(&format!("{escaped_delimiter}|\n")), } .unwrap(); regex_builder( @@ -560,7 +567,7 @@ fn build_lexer_regex( ), ); } - }; + } // This must be last, since it includes one group (so the index of all // future items will be off by 1). Build a regex for a heredoc start. @@ -594,6 +601,7 @@ fn build_lexer_regex( // Compile lexers // -------------- +#[must_use] pub fn compile_lexers(language_lexer_arr: Vec) -> LanguageLexersCompiled { let mut language_lexers_compiled = LanguageLexersCompiled { language_lexer_compiled_vec: Vec::new(), @@ -637,6 +645,8 @@ pub fn compile_lexers(language_lexer_arr: Vec) -> LanguageLexersC /// /// These linter warnings would IMHO make the code less readable. #[allow(clippy::bool_to_int_with_if)] +#[must_use] +#[allow(clippy::too_many_lines)] pub fn source_lexer( // The source code to lex. source_code: &str, @@ -725,12 +735,13 @@ pub fn source_lexer( } } CodeDocBlock::CodeBlock(ref mut _last_code_block) => { - if indent.is_empty() && delimiter.is_empty() { - // Code blocks should never need to be appended to a - // previous entry. - panic!("Attempted to append code block contents to a previous entry.") - //_last_code_block.push_str(contents); - } + // Code blocks should never need to be appended to a + // previous entry. + //_last_code_block.push_str(contents); + assert!( + !(indent.is_empty() && delimiter.is_empty()), + "Attempted to append code block contents to a previous entry." + ); } } } @@ -808,7 +819,7 @@ pub fn source_lexer( // match will be appended to the current code // block. |closing_regex: &Regex| { - trace!("Searching for the end of this token using the pattern '{:?}'.", closing_regex); + trace!("Searching for the end of this token using the pattern '{closing_regex:?}'."); // Add the opening delimiter to the code. source_code_unlexed_index += matching_group_str.len(); @@ -875,10 +886,9 @@ pub fn source_lexer( &source_code[full_comment_start_index..source_code_unlexed_index]; trace!( - "This is an inline comment. Source code before the line containing this comment is:\n'{}'\n\ - The text preceding this comment is: '{}'.\n\ - The comment is: '{}'\n", - code_lines_before_comment, comment_line_prefix, full_comment + "This is an inline comment. Source code before the line containing this comment is:\n'{code_lines_before_comment}'\n\ + The text preceding this comment is: '{comment_line_prefix}'.\n\ + The comment is: '{full_comment}'\n" ); // **Next**, determine if this comment is a doc block. @@ -933,13 +943,9 @@ pub fn source_lexer( trace!( "This is a doc block. Possibly added the preceding code block\n\ - '{}'.\n\ - Added a doc block with indent = '{}', delimiter = '{}', and contents =\n\ - '{}'.\n", - current_code_block, - comment_line_prefix, - matching_group_str, - contents + '{current_code_block}'.\n\ + Added a doc block with indent = '{comment_line_prefix}', delimiter = '{matching_group_str}', and contents =\n\ + '{contents}'.\n" ); // We've now stored the current code block (which @@ -964,8 +970,7 @@ pub fn source_lexer( source_code_unlexed_index + matching_group_str.len(); trace!( - "The opening delimiter is '{}', and the closing delimiter regex is '{}'.", - matching_group_str, comment_delim_regex + "The opening delimiter is '{matching_group_str}', and the closing delimiter regex is '{comment_delim_regex}'." ); // For nested comments, only treat the innermost comment @@ -1011,11 +1016,18 @@ pub fn source_lexer( while nesting_depth != 0 { // Get the index of the next block comment // delimiter. - trace!( - "Looking for a block comment delimiter in '{}'.", - &source_code[comment_start_index - ..min(comment_start_index + 30, source_code.len())] - ); + trace!("Looking for a block comment delimiter in '{}'.", { + // Clamp to a valid char boundary, since + // `comment_start_index + 30` is an + // arbitrary byte offset that may land in + // the middle of a multi-byte UTF-8 + // character. + let mut end = min(comment_start_index + 30, source_code.len()); + while !source_code.is_char_boundary(end) { + end -= 1; + } + &source_code[comment_start_index..end] + }); let delimiter_captures_wrapped = comment_delim_regex.captures(&source_code[comment_start_index..]); if delimiter_captures_wrapped.is_none() { @@ -1070,10 +1082,8 @@ pub fn source_lexer( comment_start_index = source_code_unlexed_index + opening_delimiter.len(); trace!( - "Found a nested opening block comment delimiter. Nesting depth: {}", - nesting_depth + "Found a nested opening block comment delimiter. Nesting depth: {nesting_depth}" ); - continue; } else { // This is a closing comment delimiter. assert!(nesting_depth > 0); @@ -1092,8 +1102,7 @@ pub fn source_lexer( + closing_delimiter_match.start() + closing_delimiter_match.len(); trace!( - "Found a non-innermost closing block comment delimiter. Nesting depth: {}", - nesting_depth + "Found a non-innermost closing block comment delimiter. Nesting depth: {nesting_depth}" ); continue; } @@ -1146,10 +1155,7 @@ pub fn source_lexer( [closing_delimiter_end_index ..newline_or_eof_after_closing_delimiter_index]; - trace!( - "The post-comment line is '{}'.", - post_closing_delimiter_line - ); + trace!("The post-comment line is '{post_closing_delimiter_line}'."); // Set the `current_code_block` to contain // preceding code (which might be multiple @@ -1178,12 +1184,9 @@ pub fn source_lexer( newline_or_eof_after_closing_delimiter_index; trace!( - "current_code_block is '{}'\n\ - comment_line_prefix is '{}'\n\ - code_lines_before_comment is '{}'", - current_code_block, - comment_line_prefix, - code_lines_before_comment + "current_code_block is '{current_code_block}'\n\ + comment_line_prefix is '{comment_line_prefix}'\n\ + code_lines_before_comment is '{code_lines_before_comment}'" ); // Next, determine if this is a doc block. @@ -1308,8 +1311,24 @@ pub fn source_lexer( // and delimiter have already been split // out for that line. for line in &split_contents[1..] { + // `indent_column` is a byte offset + // computed from the (ASCII) + // indent/delimiter widths; a line's + // contents may contain multi-byte + // UTF-8 characters, so `indent_column` + // might not land on a char boundary + // within `line`. Slicing at a + // non-boundary index would panic; + // guard against that. If it's not a + // valid boundary, this line can't + // consist solely of (single-byte) + // whitespace up to that column, so + // it's not indented. let this_line_indent = if line.len() < indent_column { line + } else if !line.is_char_boundary(indent_column) { + is_indented = false; + break; } else { &line[..indent_column] }; @@ -1355,8 +1374,7 @@ pub fn source_lexer( // print the doc block trace!( - "Appending a doc block with indent '{}', delimiter '{}', and contents '{}'.", - comment_line_prefix, matching_group_str, contents + "Appending a doc block with indent '{comment_line_prefix}', delimiter '{matching_group_str}', and contents '{contents}'." ); // advance `current_code_block_index` to @@ -1380,7 +1398,7 @@ pub fn source_lexer( // #### String-like syntax RegexDelimType::String(closing_regex) => { trace!("This is a string. "); - append_code(closing_regex) + append_code(closing_regex); } RegexDelimType::TemplateLiteral => { diff --git a/server/src/lexer/pest_parser.rs b/server/src/lexer/pest_parser.rs index 5924706f..b2887305 100644 --- a/server/src/lexer/pest_parser.rs +++ b/server/src/lexer/pest_parser.rs @@ -41,6 +41,7 @@ #[macro_export] macro_rules! make_parse_to_code_doc_blocks { ($parser: ty) => { + #[allow(clippy::too_many_lines)] pub fn parse_to_code_doc_blocks(input: &str) -> Vec<$crate::lexer::CodeDocBlock> { // While Pest has no problem working with all types of line endings, // CodeMirror converts all line endings to `\n` then indexes strings @@ -283,30 +284,30 @@ mod test { fn test_pest_c_1() { assert_eq!( c::parse_to_code_doc_blocks(indoc!( - r#" - //"# + r" + //" )), vec![CodeDocBlock::DocBlock(DocBlock { - indent: "".to_string(), + indent: String::new(), delimiter: "//".to_string(), - contents: "".to_string(), + contents: String::new(), lines: 0, })], ); assert_eq!( c::parse_to_code_doc_blocks(indoc!( - r#" + r" code; /* Testing 1, 2, 3 - */"# + */" )), vec![ CodeDocBlock::CodeBlock("code;\n".to_string()), CodeDocBlock::DocBlock(DocBlock { - indent: "".to_string(), + indent: String::new(), delimiter: "/*".to_string(), contents: "Testing\n1,\n\n2, 3\n".to_string(), lines: 4, @@ -315,18 +316,18 @@ mod test { ); assert_eq!( c::parse_to_code_doc_blocks(indoc!( - r#" + r" code; /* Testing * 1, * * 2, 3 - */"# + */" )), vec![ CodeDocBlock::CodeBlock("code;\n".to_string()), CodeDocBlock::DocBlock(DocBlock { - indent: "".to_string(), + indent: String::new(), delimiter: "/*".to_string(), contents: "Testing\n1,\n\n2, 3\n".to_string(), lines: 4, @@ -362,7 +363,7 @@ mod test { .to_string() ), CodeDocBlock::DocBlock(DocBlock { - indent: "".to_string(), + indent: String::new(), delimiter: "#".to_string(), contents: "A comment.\n".to_string(), lines: 1, @@ -398,7 +399,7 @@ mod test { .to_string() ), CodeDocBlock::DocBlock(DocBlock { - indent: "".to_string(), + indent: String::new(), delimiter: "#".to_string(), contents: "A comment.".to_string(), lines: 1, @@ -425,7 +426,7 @@ mod test { .to_string() ), CodeDocBlock::DocBlock(DocBlock { - indent: "".to_string(), + indent: String::new(), delimiter: "#".to_string(), contents: "A comment.".to_string(), lines: 1, @@ -450,7 +451,7 @@ mod test { .to_string() ), CodeDocBlock::DocBlock(DocBlock { - indent: "".to_string(), + indent: String::new(), delimiter: "#".to_string(), contents: "A comment.".to_string(), lines: 1, diff --git a/server/src/lexer/supported_languages.rs b/server/src/lexer/supported_languages.rs index 1097ee10..dc34c11c 100644 --- a/server/src/lexer/supported_languages.rs +++ b/server/src/lexer/supported_languages.rs @@ -79,7 +79,7 @@ fn make_language_lexer( ext_arr: ext_arr.iter().map(|x| Arc::new(x.to_string())).collect(), inline_comment_delim_arr: inline_comment_delim_arr .iter() - .map(|x| x.to_string()) + .map(std::string::ToString::to_string) .collect(), block_comment_delim_arr: block_comment_delim_arr.to_vec(), string_delim_spec_arr: string_delim_spec_arr.to_vec(), @@ -101,6 +101,7 @@ fn make_string_delimiter_spec( } } +#[allow(clippy::unnecessary_wraps)] fn make_heredoc_delim( start_prefix: &str, delim_ident_regex: &str, @@ -127,6 +128,7 @@ fn make_block_comment_delim(opening: &str, closing: &str, is_nestable: bool) -> // Define lexers for each supported language. // ------------------------------------------ +#[allow(clippy::too_many_lines)] pub fn get_language_lexer_vec() -> Vec { vec![ // ### Linux shell scripts @@ -171,7 +173,7 @@ pub fn get_language_lexer_vec() -> Vec { // raw string syntax in C++11 and newer is IMHO so rare we won't // encounter it in older code. See the C++ // [string literals docs for the reasoning behind the start body regex.](https://en.cppreference.com/w/cpp/language/string_literal) - make_heredoc_delim("R\"", "[^()\\\\[[:space:]]]*", "(", ")", "\""), + make_heredoc_delim("R\"", "[^()\\\\\\s]*", "(", ")", "\""), SpecialCase::None, Some(pest_parser::c::parse_to_code_doc_blocks), ), diff --git a/server/src/lexer/tests.rs b/server/src/lexer/tests.rs index 266f3e7d..040c7d95 100644 --- a/server/src/lexer/tests.rs +++ b/server/src/lexer/tests.rs @@ -33,12 +33,8 @@ fn build_doc_block(indent: &str, delimiter: &str, contents: &str) -> CodeDocBloc indent: indent.to_string(), delimiter: delimiter.to_string(), contents: contents.to_string(), - lines: contents.matches("\n").count() - + (if contents.chars().last().unwrap_or('\n') == '\n' { - 0 - } else { - 1 - }), + lines: contents.matches('\n').count() + + usize::from(contents.chars().last().unwrap_or('\n') != '\n'), }) } @@ -335,9 +331,9 @@ fn test_js() { // Indented block comments. assert_eq!( source_lexer( - r#"test_1(); + r"test_1(); /* Test - 2 */"#, + 2 */", js ), [ @@ -348,9 +344,9 @@ fn test_js() { assert_eq!( source_lexer( - r#"test_1(); + r"test_1(); /* Test - 2 */"#, + 2 */", js ), [ @@ -361,10 +357,10 @@ fn test_js() { assert_eq!( source_lexer( - r#"test_1(); + r"test_1(); /* Test 2 - */"#, + */", js ), [ @@ -375,10 +371,10 @@ fn test_js() { assert_eq!( source_lexer( - r#"test_1(); + r"test_1(); /* Test 2 - */"#, + */", js ), [ @@ -389,12 +385,12 @@ fn test_js() { assert_eq!( source_lexer( - r#"test_1(); + r"test_1(); /* Test 2 3 - */"#, + */", js ), [ @@ -406,9 +402,9 @@ fn test_js() { // Mis-indented block comments. assert_eq!( source_lexer( - r#"test_1(); + r"test_1(); /* Test - 2 */"#, + 2 */", js ), [ @@ -419,9 +415,9 @@ fn test_js() { assert_eq!( source_lexer( - r#"test_1(); + r"test_1(); /* Test - 2 */"#, + 2 */", js ), [ @@ -459,8 +455,7 @@ fn test_js() { ); } -// TODO: re-enable this test when heredocs are supported. -#[ignore] +#[ignore = "Re-enable this test where heredocs are supported."] #[test] fn test_cpp() { let llc = compile_lexers(get_language_lexer_vec()); @@ -590,29 +585,29 @@ fn test_rust() { assert_eq!( source_lexer( - r#" /* Depth 1 + r" /* Depth 1 /* Depth 2 comment */ /* Depth 2 /* Depth 3 */ */ /* Depth 2 /* Depth 3 comment */ */ -More depth 1 */"#, +More depth 1 */", rust ), [ build_code_block(" /* Depth 1\n"), build_doc_block(" ", "/*", "Depth 2 comment\n"), build_code_block( - r#" /* Depth 2 + r" /* Depth 2 /* Depth 3 */ */ /* Depth 2 -"# +" ), build_doc_block(" ", "/*", "Depth 3 comment\n"), build_code_block( - r#" */ -More depth 1 */"# + r" */ +More depth 1 */" ), ] ); @@ -751,3 +746,26 @@ fn test_compiler() { "verilog" ); } + +#[test] +fn test_utf8_indent_no_panic() { + // Regression test: a multi-byte UTF-8 character in a block comment's + // continuation line must not cause a byte-index char-boundary panic when + // computing/removing the comment's indentation. + let llc = compile_lexers(get_language_lexer_vec()); + // Use SQL, since (unlike C/C++) it's lexed by the regex-based lexer + // rather than the PEG-based parser. + let sql = llc.map_mode_to_lexer.get(&stringit("sql")).unwrap(); + let s = "/* Test\n \u{65E5}\u{672C}\u{8A9E} text\n*/\n"; + // This must not panic; also verify the contents are preserved intact + // (since the multi-byte character prevents this comment from being + // recognized as consistently indented, it should be left unchanged). + assert_eq!( + source_lexer(s, sql), + [build_doc_block( + "", + "/*", + "Test\n \u{65E5}\u{672C}\u{8A9E} text\n\n" + )] + ); +} diff --git a/server/src/lib.rs b/server/src/lib.rs index 16e71f18..7f09f8e2 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -1,19 +1,29 @@ -/// Copyright (C) 2025 Bryan A. Jones. -/// -/// This file is part of the CodeChat Editor. The CodeChat Editor is free -/// software: you can redistribute it and/or modify it under the terms of the -/// GNU General Public License as published by the Free Software Foundation, -/// either version 3 of the License, or (at your option) any later version. -/// -/// The CodeChat Editor is distributed in the hope that it will be useful, but -/// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -/// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -/// more details. -/// -/// You should have received a copy of the GNU General Public License along with -/// the CodeChat Editor. If not, see -/// [http://www.gnu.org/licenses](http://www.gnu.org/licenses). -/// +// Copyright (C) 2025 Bryan A. Jones. +// +// This file is part of the CodeChat Editor. The CodeChat Editor is free +// software: you can redistribute it and/or modify it under the terms of the +// GNU General Public License as published by the Free Software Foundation, +// either version 3 of the License, or (at your option) any later version. +// +// The CodeChat Editor is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// the CodeChat Editor. If not, see +// [http://www.gnu.org/licenses](http://www.gnu.org/licenses). +// +// Relax a few pedantic Clippy lints for test code only; see the `[lints.clippy]` +// section in `Cargo.toml` for the full-crate (production) lint config. +#![cfg_attr( + test, + allow( + clippy::too_many_lines, + clippy::cast_possible_truncation, + clippy::float_cmp + ) +)] /// `lib.rs` -- Define library modules for the CodeChat Editor Server /// ================================================================= /// diff --git a/server/src/processing.rs b/server/src/processing.rs index 60fa7cf2..baf13c14 100644 --- a/server/src/processing.rs +++ b/server/src/processing.rs @@ -57,7 +57,6 @@ use html5ever::{ tree_builder::TreeBuilderOpts, }; use imara_diff::{Algorithm, Diff, Hunk, InternedInput, TokenSource}; -use lazy_static::lazy_static; use markup5ever_rcdom::{Node, NodeData, RcDom, SerializableHandle}; use minify_html; use phf::phf_map; @@ -247,12 +246,13 @@ pub enum TranslationResultsString { // // Globals // ------- -lazy_static! { - /// Match the lexer directive in a source file. - static ref LEXER_DIRECTIVE: Regex = Regex::new(r"CodeChat Editor lexer: (\w+)").unwrap(); - /// If this matches, it means an unterminated fenced code block. This should - /// be replaced with the `` terminator. - static ref DOC_BLOCK_SEPARATOR_BROKEN_FENCE: Regex = Regex::new(concat!( +/// Match the lexer directive in a source file. +static LEXER_DIRECTIVE: LazyLock = + LazyLock::new(|| Regex::new(r"CodeChat Editor lexer: (\w+)").unwrap()); +/// If this matches, it means an unterminated fenced code block. This should +/// be replaced with the `` terminator. +static DOC_BLOCK_SEPARATOR_BROKEN_FENCE: LazyLock = LazyLock::new(|| { + Regex::new(concat!( // Allow the `.` wildcard to match newlines. "(?s)", // The first `` will be munged when a fenced code @@ -261,8 +261,10 @@ lazy_static! { // Non-greedy wildcard -- match the first separator, so we don't munch // multiple `DOC_BLOCK_SEPARATOR_STRING`s in one replacement. ".*?", - "\n")).unwrap(); -} + "\n" + )) + .unwrap() +}); // Use this as a way to end unterminated fenced code blocks and specific types // of HTML blocks. (The remaining types of HTML blocks are terminated by a blank @@ -304,14 +306,14 @@ const DOC_BLOCK_SEPARATOR_SPLIT_STRING: &str = ""; // Correctly terminated fenced code blocks produce this, which can be removed // from the HTML produced by Markdown conversion. -const DOC_BLOCK_SEPARATOR_REMOVE_FENCE: &str = r#" +const DOC_BLOCK_SEPARATOR_REMOVE_FENCE: &str = r" -->?>]]> ``````````````````````` ~~~~~~~~~~~~~~~~~~~~~~~ -"#; +"; // The replacement string for the `DOC_BLOCK_SEPARATOR_BROKEN_FENCE` regex. const DOC_BLOCK_SEPARATOR_MENDED_FENCE: &str = "\n\n"; @@ -376,6 +378,7 @@ impl<'de> Deserialize<'de> for CodeMirrorDocBlock { // Determine if the provided file is part of a project // --------------------------------------------------- +#[must_use] pub fn find_path_to_toc(file_path: &Path) -> Option { // To determine if this source code is part of a project, look for a project // file by searching the current directory, then all its parents, for a file @@ -424,13 +427,10 @@ pub fn codechat_for_web_to_source( ) -> Result { let lexer_name = &codechat_for_web.metadata.mode; // Given the mode, find the lexer. - let lexer = match LEXERS.map_mode_to_lexer.get(lexer_name) { - Some(v) => v, - None => { - return Err(CodechatForWebToSourceError::InvalidLexer( - lexer_name.clone(), - )); - } + let Some(lexer) = LEXERS.map_mode_to_lexer.get(lexer_name) else { + return Err(CodechatForWebToSourceError::InvalidLexer( + lexer_name.clone(), + )); }; // Extract the plain (not diffed) CodeMirror contents. @@ -446,14 +446,14 @@ pub fn codechat_for_web_to_source( } // Translate the HTML document to Markdown. let converter = HtmlToMarkdownWrapped::new(); - let tree = html_to_tree(&code_mirror.doc, &None)?; + let tree = html_to_tree(&code_mirror.doc, None)?; dehydrating_walk_node(&tree); return converter .convert(&tree) .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, &None) + let code_doc_block_vec = doc_block_html_to_markdown(code_doc_block_vec_html, None) .map_err(CodechatForWebToSourceError::HtmlToMarkdownFailed)?; code_doc_block_vec_to_source(&code_doc_block_vec, lexer) .map_err(CodechatForWebToSourceError::CannotTranslateCodeChat) @@ -461,6 +461,7 @@ pub fn codechat_for_web_to_source( /// Return the byte index of `s[utf_16_index]`, where the indexing operation is /// in UTF-16 code units. +#[must_use] pub fn byte_index_of(s: &str, utf_16_index: usize) -> usize { let mut byte_index = 0; let mut current_index = 0; @@ -488,30 +489,40 @@ fn code_mirror_to_code_doc_blocks(code_mirror: &CodeMirror) -> Vec // Walk through each doc block, inserting the previous code block followed // by the doc block. for codemirror_doc_block in doc_blocks { - // Translate `from`. + // Translate `from`. Use a checked subtraction, since a malformed (for + // example, out-of-order or overlapping) `doc_blocks` array sent by the + // Client could otherwise cause this to underflow and wrap around to a + // huge value, producing a confusing out-of-bounds slice panic below + // instead of a clear error at the point of the actual problem. let byte_index_prev = byte_index; byte_index += byte_index_of( &code_mirror.doc[byte_index..], - codemirror_doc_block.from - utf16_index, + codemirror_doc_block + .from + .checked_sub(utf16_index) + .expect("doc_blocks must be sorted, with non-overlapping from/to ranges"), ); utf16_index = codemirror_doc_block.from; // Append the code block, unless it's empty. let code_contents = &code_mirror.doc[byte_index_prev..byte_index]; if !code_contents.is_empty() { - code_doc_block_arr.push(CodeDocBlock::CodeBlock(code_contents.to_string())) + code_doc_block_arr.push(CodeDocBlock::CodeBlock(code_contents.to_string())); } // Append the doc block. code_doc_block_arr.push(CodeDocBlock::DocBlock(DocBlock { - indent: codemirror_doc_block.indent.to_string(), - delimiter: codemirror_doc_block.delimiter.to_string(), - contents: codemirror_doc_block.contents.to_string(), + indent: codemirror_doc_block.indent.clone(), + delimiter: codemirror_doc_block.delimiter.clone(), + contents: codemirror_doc_block.contents.clone(), lines: 0, })); let byte_index_prev = byte_index; // Translate `to`. byte_index += byte_index_of( &code_mirror.doc[byte_index..], - codemirror_doc_block.to - utf16_index, + codemirror_doc_block + .to + .checked_sub(utf16_index) + .expect("doc_blocks must be sorted, with non-overlapping from/to ranges"), ); utf16_index = codemirror_doc_block.to; // Verify that everything between `from` and `to` is newlines. @@ -541,6 +552,8 @@ pub enum HtmlToMarkdownWrappedError { HtmlToMarkdownFailed(#[from] std::io::Error), #[error("unable to word wrap Markdown")] WordWrapFailed(#[from] FormatError), + #[error("line width exceeds u32::MAX")] + LineWidthOverflow(#[from] std::num::TryFromIntError), } impl HtmlToMarkdownWrapped { @@ -567,8 +580,9 @@ impl HtmlToMarkdownWrapped { .build(), } } - fn set_line_width(&mut self, line_width: usize) { - self.word_wrap_config.line_width = line_width as u32; + fn set_line_width(&mut self, line_width: usize) -> Result<(), HtmlToMarkdownWrappedError> { + self.word_wrap_config.line_width = u32::try_from(line_width)?; + Ok(()) } /// Convert one item in a stream of HTML to markdown. The HTML must be start @@ -610,7 +624,7 @@ pub fn doc_block_html_to_markdown( // // For this reason, when provided, this function must called with a vec // containing only one doc block. - dom_location: &Option<(Vec, usize)>, + dom_location: Option<&(Vec, usize)>, ) -> Result, HtmlToMarkdownWrappedError> { let mut converter = HtmlToMarkdownWrapped::new(); let mut last_doc_block_index = None; @@ -643,7 +657,7 @@ pub fn doc_block_html_to_markdown( total_delimiter_width + doc_block.indent.chars().count(), WORD_WRAP_COLUMN, ), - )); + ))?; doc_block.contents = converter.next(&tree)?; } } @@ -809,7 +823,7 @@ fn code_doc_block_vec_to_source( // This is code. Simply append it (by definition, indent and // delimiter are empty). { - file_contents += contents + file_contents += contents; } } } @@ -890,7 +904,7 @@ pub fn source_to_codechat_for_web( // Create an initially-empty struct; the source code will be // translated to this. let mut code_mirror = CodeMirror { - doc: "".to_string(), + doc: String::new(), doc_blocks: Vec::new(), }; @@ -945,7 +959,7 @@ pub fn source_to_codechat_for_web( match code_or_doc_block { CodeDocBlock::CodeBlock(code_string) => { source.push_str(&code_string); - len += len_utf16(&code_string) + len += len_utf16(&code_string); } CodeDocBlock::DocBlock(doc_block) => { // Create the doc block. @@ -954,8 +968,8 @@ pub fn source_to_codechat_for_web( // To. Note that the last doc block could be zero // length, so handle this case. to: len + max(doc_block.lines, 1), - indent: doc_block.indent.to_string(), - delimiter: doc_block.delimiter.to_string(), + indent: doc_block.indent.clone(), + delimiter: doc_block.delimiter.clone(), // Used the markdown-translated replacement for this // doc block, rather than the original string. contents: minify(doc_block_contents_iter.next().unwrap())?, @@ -1034,7 +1048,7 @@ pub fn minify(html: &str) -> Result { // Compute the length of the provided string in UTF16 characters. fn len_utf16(s: &str) -> usize { - s.chars().map(|c| c.len_utf16()).sum() + s.chars().map(char::len_utf16).sum() } // Like `source_to_codechat_for_web`, translate a source file to the CodeChat @@ -1123,7 +1137,7 @@ pub const UNICODE_CURSOR_MARKER: char = '\u{E83B}'; fn html_to_tree( html: &str, // See the same parameter from `doc_block_html_to_markdown`. - dom_location: &Option<(Vec, usize)>, + dom_location: Option<&(Vec, usize)>, ) -> io::Result> { let dom = parse_document( RcDom::default(), @@ -1176,7 +1190,7 @@ fn html_to_tree( // A framework to transform HTML by parsing it to a DOM tree, walking the tree, // then serializing the tree back to an HTML string. pub fn transform_html)>(html: &str, transform: T) -> io::Result { - let tree = html_to_tree(html, &None)?; + let tree = html_to_tree(html, None)?; transform(&tree); // Serialize the transformed DOM back to a string. @@ -1422,14 +1436,13 @@ pub fn remove_tinymce_data( // since this will re-borrow it. remove_tinymce_data(parent, index) }; - } else { - // If we didn't remove this element, then filter out unwanted - // attributes. - attrs.borrow_mut().retain(|attr| { - !(attr.name.local.starts_with("data-mce-") - || (attr.name.local == *"class" && attr.value.starts_with("mce-"))) - }); } + // If we didn't remove this element, then filter out unwanted + // attributes. + attrs.borrow_mut().retain(|attr| { + !(attr.name.local.starts_with("data-mce-") + || (attr.name.local == *"class" && attr.value.starts_with("mce-"))) + }); } Some(node.clone()) } @@ -1437,6 +1450,7 @@ pub fn remove_tinymce_data( /// Walk a node, dehydrating it by removing TineMCE temporary attributes, /// changing math to pulldown-cmark's output, and changing graphviz/Mermaid to /// fenced code blocks. +#[allow(clippy::too_many_lines)] fn dehydrating_walk_node(node: &Rc) { let mut index = 0; // Avoid a `while` loop, since accessing `node.children` requires a borrow @@ -1675,6 +1689,7 @@ static CUSTOM_ELEMENT_TO_CODE_BLOCK_LANGUAGE: phf::Map<&'static str, &'static st // // #### String diff /// Given two strings, return a list of changes between them. +#[must_use] pub fn diff_str(before: &str, after: &str) -> Vec { let mut change_spec: Vec = Vec::new(); // The previous value of `before.start` and the character index @@ -1719,11 +1734,11 @@ pub fn diff_str(before: &str, after: &str) -> Vec { None }, insert: if hunk_after.is_empty() { - "".to_string() + String::new() } else { hunk_after.into_iter().collect() }, - }) + }); } change_spec @@ -1748,12 +1763,12 @@ impl<'a> TokenSource for CodeMirrorDocBlocksStruct<'a> { } fn estimate_tokens(&self) -> u32 { - self.0.len() as u32 + u32::try_from(self.0.len()).unwrap_or(u32::MAX) } } -fn none_if_eq(before: T, after: T) -> Option { - if before == after { None } else { Some(after) } +fn none_if_eq(before: &T, after: T) -> Option { + if before == &after { None } else { Some(after) } } fn none_if_eq_ref(before: &T, after: &T) -> Option { @@ -1765,6 +1780,8 @@ fn none_if_eq_ref(before: &T, after: &T) -> Option { } /// Given two `CodeMirrorDocBlocks`, return a list of changes between them. +#[must_use] +#[allow(clippy::too_many_lines)] pub fn diff_code_mirror_doc_blocks( before: &CodeMirrorDocBlockVec, after: &CodeMirrorDocBlockVec, @@ -1801,11 +1818,11 @@ pub fn diff_code_mirror_doc_blocks( CodeMirrorDocBlockUpdate { from: prev_before_range_start_val.from, from_new: none_if_eq( - prev_before_range_start_val.from, + &prev_before_range_start_val.from, prev_after_range_start_val.from, ), to: none_if_eq( - prev_before_range_start_val.to, + &prev_before_range_start_val.to, prev_after_range_start_val.to, ), indent: none_if_eq_ref( @@ -1852,8 +1869,8 @@ pub fn diff_code_mirror_doc_blocks( change_specs.push(CodeMirrorDocBlockTransaction::Update( CodeMirrorDocBlockUpdate { from: before_val.from, - from_new: none_if_eq(before_val.from, after_val.from), - to: none_if_eq(before_val.to, after_val.to), + from_new: none_if_eq(&before_val.from, after_val.from), + to: none_if_eq(&before_val.to, after_val.to), indent: none_if_eq_ref(&before_val.indent, &after_val.indent), delimiter: none_if_eq_ref(&before_val.delimiter, &after_val.delimiter), contents: diff_str(&before_val.contents, &after_val.contents), @@ -1886,8 +1903,8 @@ pub fn diff_code_mirror_doc_blocks( // doesn't matter, since it's not used. diff_all( &Hunk { - before: (before.len() as u32..0), - after: after.len() as u32..0, + before: (u32::try_from(before.len()).unwrap_or(u32::MAX)..0), + after: u32::try_from(after.len()).unwrap_or(u32::MAX)..0, }, &mut change_specs, ); diff --git a/server/src/processing/tests.rs b/server/src/processing/tests.rs index 7086cad3..8c24b233 100644 --- a/server/src/processing/tests.rs +++ b/server/src/processing/tests.rs @@ -118,7 +118,7 @@ fn test_codechat_for_web_to_source() { let codechat_for_web = build_codechat_for_web("python", "", vec![]); assert_eq!( cast!(codechat_for_web_to_source(&codechat_for_web), Ok), - "".to_string() + String::new() ); let codechat_for_web = build_codechat_for_web("undefined", "", vec![]); @@ -228,7 +228,7 @@ fn test_codemirror_to_code_doc_blocks_py() { } #[test] -#[should_panic] +#[should_panic(expected = "assertion `left == right` failed")] fn test_codemirror_to_code_doc_blocks_error() { run_test( "python", @@ -388,11 +388,11 @@ fn test_code_doc_blocks_to_source_css() { css_lexer ) .unwrap(), - r#"Test_0 + r"Test_0 /* Test 1 Test 2 */ -"# +" ); // Repeat the above tests with an indent. @@ -409,11 +409,11 @@ fn test_code_doc_blocks_to_source_css() { css_lexer ) .unwrap(), - r#"Test_0 + r"Test_0 /* Test 1 Test 2 */ -"# +" ); // Basic code. @@ -806,7 +806,7 @@ fn test_source_to_codechat_for_web_1() { 1, "", "//", - r#"

    1"# + r"

    1" ),] ))) ); @@ -847,7 +847,7 @@ fn apply_str_diff(before: &str, diffs: &[StringDiff]) -> String { before.replace_range(from_index..to_index, &diff.insert); } else { before.insert_str(from_index, &diff.insert); - }; + } } before } @@ -902,7 +902,7 @@ fn test_diff_1() { &[StringDiff { from: 0, to: Some(2), - insert: "".to_string(), + insert: String::new(), }], ); @@ -931,7 +931,7 @@ fn test_diff_1() { &[StringDiff { from: 2, to: Some(6), - insert: "".to_string(), + insert: String::new(), }], ); @@ -960,7 +960,7 @@ fn test_diff_1() { &[StringDiff { from: 6, to: Some(8), - insert: "".to_string(), + insert: String::new(), }], ); @@ -1085,7 +1085,7 @@ fn test_diff_2() { vec![CodeMirrorDocBlockTransaction::Add(CodeMirrorDocBlock { from: 10, to: 11, - indent: "".to_string(), + indent: String::new(), delimiter: "#".to_string(), contents: "test1".to_string() })] @@ -1114,7 +1114,7 @@ fn test_diff_2() { CodeMirrorDocBlockTransaction::Add(CodeMirrorDocBlock { from: 11, to: 12, - indent: "".to_string(), + indent: String::new(), delimiter: "#".to_string(), contents: "test".to_string() }), @@ -1137,7 +1137,7 @@ fn test_diff_2() { vec![CodeMirrorDocBlockTransaction::Add(CodeMirrorDocBlock { from: 11, to: 12, - indent: "".to_string(), + indent: String::new(), delimiter: "#".to_string(), contents: "test2".to_string() })] @@ -1154,7 +1154,7 @@ fn test_diff_2() { vec![CodeMirrorDocBlockTransaction::Add(CodeMirrorDocBlock { from: 11, to: 12, - indent: "".to_string(), + indent: String::new(), delimiter: "#".to_string(), contents: "test2".to_string() })] @@ -1272,7 +1272,7 @@ fn test_diff_2() { CodeMirrorDocBlockTransaction::Add(CodeMirrorDocBlock { from: 14, to: 15, - indent: "".to_string(), + indent: String::new(), delimiter: "#".to_string(), contents: "test4a".to_string() }), @@ -1297,7 +1297,7 @@ fn test_doc_block_html_to_markdown_1() { "", "

    Index 0

    Index 1.0Index 1.1012345

    " )], - &Some((vec![1, 2], 3)), + Some(&(vec![1, 2], 3)), ) .unwrap(), vec![build_doc_block( @@ -1397,7 +1397,7 @@ fn test_hydrate_html_1() { } fn dehydrate_html(html: &str) -> io::Result> { - let tree = html_to_tree(html, &None)?; + let tree = html_to_tree(html, None)?; dehydrating_walk_node(&tree); Ok(tree) } diff --git a/server/src/translation.rs b/server/src/translation.rs index 393a22ed..3775f097 100644 --- a/server/src/translation.rs +++ b/server/src/translation.rs @@ -212,11 +212,11 @@ use std::{ fmt::Debug, path::{Path, PathBuf}, rc::Rc, + sync::LazyLock, }; use htmd::Node; // ### Third-party -use lazy_static::lazy_static; use log::{debug, error, warn}; use rand::random; use regex::Regex; @@ -253,10 +253,8 @@ use crate::{ // The max length of a message to show in the console. const MAX_MESSAGE_LENGTH: usize = 500; -lazy_static! { - /// 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(); -} +/// A regex to determine the type of the first EOL. See 'PROCESSINGS\`. +pub static EOL_FINDER: LazyLock = LazyLock::new(|| Regex::new("[^\r\n]*(\r?\n)").unwrap()); // Data structures // --------------- @@ -268,6 +266,7 @@ pub enum EolType { // Code // ---- +#[must_use] pub fn find_eol_type(s: &str) -> EolType { match EOL_FINDER.captures(s) { // Assume a line type for strings with no newlines. @@ -452,6 +451,7 @@ struct TranslationTask { /// 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)] +#[allow(clippy::too_many_lines)] pub async fn translation_task( connection_id_prefix: String, connection_id_raw: String, @@ -474,7 +474,7 @@ pub async fn translation_task( .processing_task_queue_tx .lock() .unwrap() - .insert(connection_id.to_string(), from_http_tx); + .insert(connection_id.clone(), from_http_tx); let mut continue_loop = true; let mut tt = TranslationTask { @@ -524,7 +524,7 @@ pub async fn translation_task( EditorMessageContents::Closed | EditorMessageContents::RequestClose => { debug!("Forwarding it to the Client."); - queue_send!(tt.to_client_tx.send(ide_message)) + queue_send!(tt.to_client_tx.send(ide_message)); }, EditorMessageContents::Result(_) => continue_loop = tt.ide_result(ide_message).await, @@ -571,7 +571,7 @@ pub async fn translation_task( // 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); + debug!("Received HTTP request for {} and sending LoadFile to IDE, id = {}.", http_request.file_path.display(), tt.id); // Convert the request into a `LoadFile` message. queue_send!(tt.to_ide_tx.send(EditorMessage { id: tt.id, @@ -609,7 +609,7 @@ pub async fn translation_task( // Handle messages that are simply passed through. EditorMessageContents::Closed => { debug!("Forwarding it to the IDE."); - queue_send!(tt.to_ide_tx.send(client_message)) + queue_send!(tt.to_ide_tx.send(client_message)); }, EditorMessageContents::Result(ref result) => { @@ -619,7 +619,7 @@ pub async fn translation_task( if matches!(result, Err(ResultErrTypes::OutOfSync(..))) { tt.sent_full = false; } - queue_send!(tt.to_ide_tx.send(client_message)) + queue_send!(tt.to_ide_tx.send(client_message)); }, // Open a web browser when requested. @@ -655,7 +655,7 @@ pub async fn translation_task( 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())), + Err(err) => Err(ResultErrTypes::UrlToPathError(url_string.clone(), err.to_string())), Ok(file_path) => { match file_path.to_str() { None => Err(ResultErrTypes::NoPathToString(file_path)), @@ -856,6 +856,7 @@ impl TranslationTask { } // Pass a `Result` message to the Client, unless it's a `LoadFile` result. + #[allow(clippy::too_many_lines)] 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."); @@ -947,7 +948,7 @@ impl TranslationTask { ), // There's no file, so return empty contents, which will // be ignored. - "".to_string(), + String::new(), ), Ok(mut fc) => { let option_file_contents = try_read_as_text(&mut fc).await; @@ -962,7 +963,7 @@ impl TranslationTask { .await, // If the file is binary, return empty contents, // which will be ignored. - option_file_contents.unwrap_or("".to_string()), + option_file_contents.unwrap_or(String::new()), ) } } @@ -978,7 +979,7 @@ impl TranslationTask { 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.clone_from(&plain.doc); self.code_mirror_doc_blocks = Some(plain.doc_blocks.clone()); debug!("Sending Update from LoadFile to Client, id = {}.", self.id); @@ -997,6 +998,7 @@ impl TranslationTask { true } + #[allow(clippy::too_many_lines)] async fn ide_update(&mut self, ide_message: EditorMessage) -> bool { let EditorMessageContents::Update(update) = ide_message.message else { panic!("Expected update message."); @@ -1123,7 +1125,7 @@ impl TranslationTask { metadata: SourceFileMetadata { // Since this is raw data, `mode` doesn't // matter. - mode: "".to_string(), + mode: String::new(), }, source: CodeMirrorDiffable::Plain(CodeMirror { doc: code_mirror.doc.clone(), @@ -1134,7 +1136,7 @@ impl TranslationTask { }), })); self.source_code = code_mirror.doc; - self.code_mirror_doc = self.source_code.clone(); + self.code_mirror_doc.clone_from(&self.source_code); self.code_mirror_doc_blocks = Some(vec![]); Ok(ResultOkTypes::Void) } @@ -1197,6 +1199,7 @@ impl TranslationTask { } } + #[allow(clippy::too_many_lines)] async fn client_update(&mut self, client_message: EditorMessage) -> bool { let EditorMessageContents::Update(update_message_contents) = client_message.message else { panic!("Expected update message."); @@ -1231,7 +1234,7 @@ impl TranslationTask { "client", ); } - self.code_mirror_doc = code_mirror.doc.clone(); + self.code_mirror_doc.clone_from(&code_mirror.doc); self.code_mirror_doc_blocks = Some(code_mirror.doc_blocks.clone()); // We may need to change this version if we send a // diff back to the Client. @@ -1289,8 +1292,13 @@ impl TranslationTask { ))) { // Use a whole number to avoid encoding - // differences with fractional values. - cfw_version = random::() as f64; + // differences with fractional values. Precision loss from the + // u64 -> f64 cast is fine, since we just need a unique-ish version number. + cfw_version = { + #[allow(clippy::cast_precision_loss)] + let v = random::() as f64; + v + }; // The Client needs an update. let client_contents = self.diff_code_mirror( cfw.metadata.clone(), @@ -1324,10 +1332,10 @@ impl TranslationTask { self.code_mirror_doc_blocks = Some(code_mirror_translated.doc_blocks); } - }; + } // 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 { + let new_cfw = if self.sent_full && self.allow_source_diffs { Some(CodeChatForWeb { metadata: cfw.metadata, source: CodeMirrorDiffable::Diff(CodeMirrorDiff { @@ -1354,7 +1362,7 @@ impl TranslationTask { }; self.version = cfw_version; self.source_code = new_source_code_eol; - ccfw + new_cfw } Err(message) => { let err = ResultErrTypes::CannotTranslateCodeChat(message.to_string()); @@ -1389,7 +1397,7 @@ impl TranslationTask { let is_markdown = self .code_mirror_doc_blocks .as_ref() - .is_none_or(|v| v.is_empty()); + .is_none_or(std::vec::Vec::is_empty); // 1. Find the HTML (for a Markdown document) or the doc // block the cursor is in. Create a temporary @@ -1440,7 +1448,7 @@ impl TranslationTask { // the HTML to Markdown. let translated = doc_block_html_to_markdown( vec![CodeDocBlock::DocBlock(doc_block)], - &Some((dom_path, dom_offset)), + Some(&(dom_path, dom_offset)), ) .ok()?; let CodeDocBlock::DocBlock(db) = &translated[0] else { @@ -1460,7 +1468,7 @@ impl TranslationTask { // CodeMirror uses 1-based line numbers. Some(CursorPosition::Line( - (preceding_newlines + newlines_before_marker + 1) as u32, + u32::try_from(preceding_newlines + newlines_before_marker + 1).ok()?, )) })() } else { @@ -1485,13 +1493,13 @@ impl TranslationTask { } } -// If a string is encoded using CRLFs (Windows style), convert it to LFs only -// (Unix style). +// Given a string using LF-only (Unix style) line endings, convert it to CRLF +// (Windows style) line endings if `eol_type` requests it. fn eol_convert(s: String, eol_type: &EolType) -> String { if eol_type == &EolType::Crlf { // There shouldn't be any Windows-style CRLFs -- but if there are, // handle this nicely. - s.replace("\r\n", "\n").replace("\n", "\r\n") + s.replace("\r\n", "\n").replace('\n', "\r\n") } else { s } @@ -1568,7 +1576,7 @@ fn doc_blocks_compare(a: &CodeMirrorDocBlockVec, b: &CodeMirrorDocBlockVec) -> b // `MAX_MESSAGE_LENGTH` characters of the provided value. fn debug_shorten(val: T) -> String { if cfg!(debug_assertions) { - let msg = format!("{:?}", val); + let msg = format!("{val:?}"); let max_index = msg .char_indices() .nth(MAX_MESSAGE_LENGTH) @@ -1576,7 +1584,7 @@ fn debug_shorten(val: T) -> String { .0; msg[..max_index].to_string() } else { - "".to_string() + String::new() } } @@ -1692,14 +1700,14 @@ mod tests { let ide = vec![CodeMirrorDocBlock { from: 0, to: 20, - indent: "".to_string(), + indent: String::new(), delimiter: "//".to_string(), contents: "
    • Task list

    Line
    break

    Non-breaking\u{a0} space.

    ".to_string(), }]; let client = vec![CodeMirrorDocBlock { from: 0, to: 20, - indent: "".to_string(), + indent: String::new(), delimiter: "//".to_string(), contents: "
    • Task list

    Line
    break

    Non-breaking  space.

    ".to_string(), }]; @@ -1711,14 +1719,14 @@ mod tests { let ide = vec![CodeMirrorDocBlock { from: 0, to: 20, - indent: "".to_string(), + indent: String::new(), delimiter: "//".to_string(), contents: "
      1. 1
    ".to_string(), }]; let client = vec![CodeMirrorDocBlock { from: 0, to: 20, - indent: "".to_string(), + indent: String::new(), delimiter: "//".to_string(), contents: "
      1. 1
    ".to_string(), }]; diff --git a/server/src/webserver.rs b/server/src/webserver.rs index 2831d061..ae0ae1ef 100644 --- a/server/src/webserver.rs +++ b/server/src/webserver.rs @@ -27,12 +27,14 @@ pub mod tests; use std::{ borrow::Cow, collections::{HashMap, HashSet}, - env, fs, io, + env, fs, + hash::BuildHasher, + io, net::SocketAddr, path::{self, MAIN_SEPARATOR_STR, Path, PathBuf}, str::FromStr, string::FromUtf8Error, - sync::{Arc, Mutex}, + sync::{Arc, LazyLock, Mutex}, time::Duration, }; @@ -56,9 +58,11 @@ use dunce::simplified; use futures_util::StreamExt; use htmlize::{escape_attribute, escape_text}; use indoc::formatdoc; -use lazy_static::lazy_static; use log::{LevelFilter, error, info, warn}; -use log4rs::{self, config::load_config_file}; +use log4rs::{ + self, + config::{Deserializers, load_config_file}, +}; use mime::Mime; use mime_guess; use path_slash::{PathBufExt, PathExt}; @@ -81,20 +85,9 @@ use url::Url; // ### Local //use crate::capture::EventCapture; -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, - }, +use crate::processing::{ + CodeChatForWeb, SourceToCodeChatForWebError, TranslationResultsString, find_path_to_toc, + source_to_codechat_for_web_string, }; use crate::capture::{ @@ -360,7 +353,7 @@ pub struct UpdateMessageContents { pub cursor_position: Option, /// The line at the top of the screen. #[serde(skip_serializing_if = "Option::is_none")] - pub scroll_position: Option, + pub scroll_position: Option, /// True if this is a re-translation, which can be ignored if the receiver's /// document is dirty. Therefore, this is written by the IDE and read by the /// Client and IDE; conversely, it's ignored by the Server. The IDE and @@ -376,7 +369,11 @@ pub struct UpdateMessageContents { #[derive(Debug, Serialize, Deserialize, PartialEq, TS)] #[ts(export)] pub enum CursorPosition { - /// The line the cursor is on. + /// The line the cursor is on. Use `u32`, not `u64`: JSON/JS `number` is an + /// f64, which loses precision above 2^53, and `u64` values aren't + /// type-checked against that limit, so a `u64` here could silently + /// corrupt on the JS side. `u32`'s max (~4.3 billion) is always exactly + /// representable, and no real source file has that many lines anyway. Line(u32), /// The exact location of the cursor in the HTML DOM. Only the Client and /// the Server may use this in messages to each other. The IDE will not @@ -464,7 +461,7 @@ macro_rules! queue_send_func { pub const REPLY_TIMEOUT_MS: Duration = if cfg!(test) { Duration::from_millis(2500) } else { - Duration::from_millis(15000) + Duration::from_secs(15) }; /// The time to wait for a pong from the websocket in response to a ping sent by @@ -486,79 +483,85 @@ pub const INITIAL_IDE_MESSAGE_ID: f64 = INITIAL_CLIENT_MESSAGE_ID + 1.0; /// assuming an average of 1 message/second.) pub const MESSAGE_ID_INCREMENT: f64 = 3.0; -lazy_static! { - pub static ref ROOT_PATH: Arc> = Arc::new(Mutex::new(PathBuf::new())); - - // Define the location of static files. - static ref CLIENT_STATIC_PATH: PathBuf = { - let mut client_static_path = ROOT_PATH.lock().unwrap().clone(); - #[cfg(debug_assertions)] - client_static_path.push("client"); +pub static ROOT_PATH: LazyLock>> = + LazyLock::new(|| Arc::new(Mutex::new(PathBuf::new()))); - client_static_path.push("static"); - client_static_path - }; +// Define the location of static files. +static CLIENT_STATIC_PATH: LazyLock = LazyLock::new(|| { + let mut client_static_path = ROOT_PATH.lock().unwrap().clone(); + #[cfg(debug_assertions)] + client_static_path.push("client"); - // Read in the hashed names of files bundled by esbuild. - static ref BUNDLED_FILES_MAP: HashMap = { - let mut hl = ROOT_PATH.lock().unwrap().clone(); - #[cfg(debug_assertions)] - hl.push("server"); - hl.push("hashLocations.json"); - let json = fs::read_to_string(hl.clone()).unwrap_or_else( - |err| panic!("Error: Unable to read {:#?}: {err}", hl.to_string_lossy()) - ); - let hmm: HashMap = - serde_json::from_str(&json).unwrap_or_else(|_| panic!("Unable to parse JSON in {:#?}", hl.to_string_lossy())); - hmm - }; + client_static_path.push("static"); + client_static_path +}); - static ref CODECHAT_EDITOR_FRAMEWORK_JS: String = BUNDLED_FILES_MAP.get("CodeChatEditorFramework.js").cloned().expect("Unable to find framework JS in bundled files map."); - static ref CODECHAT_EDITOR_PROJECT_CSS: String = BUNDLED_FILES_MAP.get("CodeChatEditorProject.css").cloned().expect("Unable to find project CSS in bundled files map."); -} +// Read in the hashed names of files bundled by esbuild. +static BUNDLED_FILES_MAP: LazyLock> = LazyLock::new(|| { + let mut hl = ROOT_PATH.lock().unwrap().clone(); + #[cfg(debug_assertions)] + hl.push("server"); + hl.push("hashLocations.json"); + let json = fs::read_to_string(hl.clone()) + .unwrap_or_else(|err| panic!("Error: Unable to read {:#?}: {err}", hl.to_string_lossy())); + let hmm: HashMap = serde_json::from_str(&json) + .unwrap_or_else(|_| panic!("Unable to parse JSON in {:#?}", hl.to_string_lossy())); + hmm +}); + +static CODECHAT_EDITOR_FRAMEWORK_JS: LazyLock = LazyLock::new(|| { + BUNDLED_FILES_MAP + .get("CodeChatEditorFramework.js") + .cloned() + .expect("Unable to find framework JS in bundled files map.") +}); +static CODECHAT_EDITOR_PROJECT_CSS: LazyLock = LazyLock::new(|| { + BUNDLED_FILES_MAP + .get("CodeChatEditorProject.css") + .cloned() + .expect("Unable to find project CSS in bundled files map.") +}); // Define the location of the root path, which contains `static/`, `log4rs.yml`, // and `hashLocations.json` in a production build, or `client/` and `server/` in // a development build. pub fn set_root_path( - // The path where this extension's files reside. `None` if this is running - // an a standalone server, instead of as an extension loaded by an IDE. - extension_base_path: Option<&Path>, + // The root path to use, already resolved by the caller. Since the correct + // value depends entirely on that caller's own build/deployment layout + // (an installed VSCode extension's directory, a dev build's location + // under `target/`, a `cargo dist`-packaged binary's directory, ...), this + // function does no further adjustment -- it's the caller's job to land on + // the right directory, typically using its own `cfg!(debug_assertions)`/ + // `cfg!(test)` checks. See `extensions/standalone/src/main.rs`'s + // `root_path` for an example. + base_path: &Path, ) -> io::Result<()> { - // If the extension provided a base path, use that; otherwise, get the path - // to this executable. - let exe_path; - let exe_dir = if let Some(ed) = extension_base_path { - ed + *ROOT_PATH.lock().unwrap() = base_path.canonicalize()?; + Ok(()) +} + +// A `base_path` for this package's own test suites to pass to +// `set_root_path`/`main` when they start a real webserver in-process +// (`ide::vscode::tests` and the `tests/overall` integration tests). Not +// `#[cfg(test)]`-gated: integration tests under +// `tests/` link this crate as a normal (non-`--test`) dependency, so a +// `#[cfg(test)]` item wouldn't be visible to them. All these test binaries +// are built under `server/target/debug/deps/...` (one directory deeper than a +// plain `cargo build`'s `server/target/debug/`), or one directory deeper +// still under `cargo llvm-cov`. +#[must_use] +pub fn test_root_path() -> PathBuf { + let exe_dir = env::current_exe() + .expect("Unable to determine path to current executable.") + .parent() + .expect("Unable to find directory name containing the current executable.") + .to_path_buf(); + let exe_dir = if cfg!(coverage) { + exe_dir.join("..") } else { - exe_path = env::current_exe().expect("Unable to determine path to current executable."); - exe_path - .parent() - .expect("Unable to find directory name containing the current executable.") + exe_dir }; - #[cfg(not(any(test, debug_assertions)))] - let root_path = PathBuf::from(exe_dir); - #[cfg(any(test, debug_assertions))] - let mut root_path = PathBuf::from(exe_dir); - // When in debug or running tests, use the layout of the Git repo to find - // client files. In release mode, we assume the static folder is a - // subdirectory of the directory containing the executable. - #[cfg(test)] - // In development, this extra directory level for the extension isn't - // needed. - if extension_base_path.is_none() { - root_path.push(".."); - } - // Note that `debug_assertions` is also enabled for testing, so this adds to - // the previous line when running tests. - #[cfg(debug_assertions)] - root_path.push(if extension_base_path.is_some() { - "../.." - } else { - "../../.." - }); - *ROOT_PATH.lock().unwrap() = root_path.canonicalize()?; - Ok(()) + exe_dir.join("../../../..") } // Webserver functionality @@ -620,7 +623,7 @@ pub fn log_capture_event(app_state: &WebAppState, wire: CaptureEventWire) -> Cap data, ); - capture.log(event); + capture.log(&event); capture.status() } else { CaptureStatus::disabled() @@ -631,12 +634,12 @@ pub fn capture_status(app_state: &WebAppState) -> CaptureStatus { app_state .capture .as_ref() - .map(EventCapture::status) - .unwrap_or_else(CaptureStatus::disabled) + .map_or_else(CaptureStatus::disabled, EventCapture::status) } // Get the `mode` query parameter to determine `is_test_mode`; default to // `false`. +#[must_use] pub fn get_test_mode(req: &HttpRequest) -> bool { let query_params = web::Query::>::from_query(req.query_string()); if let Ok(query) = query_params { @@ -666,6 +669,15 @@ pub fn get_client_framework( )); } }; + // `connection_id` may be attacker-controlled (for example, the VSCode + // extension's `/vsc/cf/{connection_id}` endpoint takes it directly from + // the URL). Since `ws_url` is embedded verbatim inside a ` @@ -722,7 +734,7 @@ pub async fn filesystem_endpoint( // with a leading slash, which gets absorbed into the URL to prevent a URL // such as "/fw/fsc/1//foo/bar/...". Restore it here. #[cfg(target_os = "windows")] - let fixed_file_path = request_file_path.replace("\\", "%5C"); + let fixed_file_path = request_file_path.replace('\\', "%5C"); // On OS X/Linux, the path starts with a leading slash, which gets absorbed // into the URL to prevent a URL such as "/fw/fsc/1//foo/bar/...". Restore // it here. @@ -769,10 +781,7 @@ pub async fn filesystem_endpoint( // Get the processing queue; only keep the lock during this block. let processing_queue_tx = app_state.processing_task_queue_tx.lock().unwrap(); let Some(processing_tx) = processing_queue_tx.get(&connection_id) else { - let msg = format!( - "Error: no processing task queue for connection id {}.", - connection_id - ); + let msg = format!("Error: no processing task queue for connection id {connection_id}."); error!("{msg}"); return http_not_found(&msg); }; @@ -815,7 +824,10 @@ pub async fn filesystem_endpoint( } v.into_response(req) } - Err(err) => http_not_found(&format!("Error opening file {path:?}: {err}.",)), + Err(err) => http_not_found(&format!( + "Error opening file \"{}\": {err}.", + path.display() + )), } } }, @@ -844,6 +856,7 @@ pub async fn try_read_as_text(file: &mut File) -> Option { // file contents itself (if it's not editable by the Client). If responding with // a Client, also return an Update message which will provided the contents for // the Client. +#[allow(clippy::too_many_lines)] pub async fn file_to_response( // The HTTP request presented to the processing task. http_request: &ProcessingTaskHttpRequest, @@ -868,9 +881,7 @@ pub async fn file_to_response( let file_path = &http_request.file_path; let Some(file_name) = file_path.file_name() else { return ( - SimpleHttpResponse::Err(SimpleHttpResponseError::ProjectPathShort( - file_path.to_path_buf(), - )), + SimpleHttpResponse::Err(SimpleHttpResponseError::ProjectPathShort(file_path.clone())), None, ); }; @@ -950,7 +961,7 @@ pub async fn file_to_response( ), ) } else { - ("".to_string(), "".to_string()) + (String::new(), String::new()) }; // Do we need to respond with a [simple viewer](#Client-simple-viewer)? @@ -992,13 +1003,13 @@ pub async fn file_to_response( let codechat_for_web = match translation_results_string { // The file type is binary. Ask the HTTP server to serve it raw. TranslationResultsString::Binary => return - (SimpleHttpResponse::Bin(file_path.to_path_buf()), None) + (SimpleHttpResponse::Bin(file_path.clone()), None) , // The file type is unknown. Serve it raw. TranslationResultsString::Unknown => { return ( SimpleHttpResponse::Raw( - file_contents.unwrap().to_string(), + file_contents.unwrap().clone(), mime_guess::from_path(file_path).first_or_text_plain(), ), None, @@ -1036,18 +1047,14 @@ pub async fn file_to_response( // Provided info from the HTTP request, determine the following parameters. let Some(raw_dir) = file_path.parent() else { return ( - SimpleHttpResponse::Err(SimpleHttpResponseError::ProjectPathShort( - file_path.to_path_buf(), - )), + SimpleHttpResponse::Err(SimpleHttpResponseError::ProjectPathShort(file_path.clone())), None, ); }; let dir = path_display(raw_dir); let Some(file_path) = file_path.to_str() else { return ( - SimpleHttpResponse::Err(SimpleHttpResponseError::PathNotString( - file_path.to_path_buf(), - )), + SimpleHttpResponse::Err(SimpleHttpResponseError::PathNotString(file_path.clone())), None, ); }; @@ -1108,24 +1115,20 @@ fn make_simple_viewer(http_request: &ProcessingTaskHttpRequest, html: &str) -> S let file_path = &http_request.file_path; let Some(file_name) = file_path.file_name() else { return SimpleHttpResponse::Err(SimpleHttpResponseError::ProjectPathShort( - file_path.to_path_buf(), + file_path.clone(), )); }; let Some(file_name) = file_name.to_str() else { - return SimpleHttpResponse::Err(SimpleHttpResponseError::PathNotString( - file_path.to_path_buf(), - )); + return SimpleHttpResponse::Err(SimpleHttpResponseError::PathNotString(file_path.clone())); }; let file_name = escape_text(file_name); let Some(path_to_toc) = find_path_to_toc(file_path) else { - return SimpleHttpResponse::Err(SimpleHttpResponseError::PathNotProject( - file_path.to_path_buf(), - )); + return SimpleHttpResponse::Err(SimpleHttpResponseError::PathNotProject(file_path.clone())); }; let Some(path_to_toc) = path_to_toc.to_str() else { return SimpleHttpResponse::Err(SimpleHttpResponseError::PathNotString( - path_to_toc.to_path_buf(), + path_to_toc.clone(), )); }; let path_to_toc = escape_text(path_to_toc); @@ -1184,13 +1187,14 @@ fn make_simple_viewer(http_request: &ProcessingTaskHttpRequest, html: &str) -> S /// allowing the user to edit the plain text of the source code in the IDE, or /// make GUI-enhanced edits of the source code rendered by the CodeChat Editor /// Client. -pub fn client_websocket( +#[allow(clippy::too_many_lines)] +pub fn client_websocket( connection_id: String, - req: HttpRequest, + req: &HttpRequest, body: web::Payload, - websocket_queues: Arc>>, + websocket_queues: Arc>>, ) -> Result { - let (response, mut session, mut msg_stream) = actix_ws::handle(&req, body)?; + let (response, mut session, mut msg_stream) = actix_ws::handle(req, body)?; // Websocket task: start a task to handle receiving `JointMessage` websocket // data from the CodeChat Editor Client and forwarding it to the IDE and @@ -1203,16 +1207,15 @@ pub fn client_websocket( // Transfer the queues from the global state to this task. let (from_websocket_tx, mut to_websocket_rx, mut pending_messages) = - match websocket_queues.lock().unwrap().remove(&connection_id) { - Some(queues) => ( + if let Some(queues) = websocket_queues.lock().unwrap().remove(&connection_id) { + ( queues.from_websocket_tx.clone(), queues.to_websocket_rx, queues.pending_messages, - ), - None => { - error!("No websocket queues for connection id {connection_id}."); - return; - } + ) + } else { + error!("No websocket queues for connection id {connection_id}."); + return; }; // Shutdown may occur in a controlled process or an immediate websocket @@ -1249,7 +1252,7 @@ pub fn client_websocket( loop { select! { // Send pings on a regular basis. - _ = sleep(WEBSOCKET_PING_DELAY) => { + () = sleep(WEBSOCKET_PING_DELAY) => { if sent_ping { // If we haven't received the answering pong, the // websocket must be broken. @@ -1270,6 +1273,7 @@ pub fn client_websocket( Some(msg_wrapped) = aggregated_msg_stream.next() => { match msg_wrapped { Ok(msg) => { + #[allow(clippy::match_wildcard_for_single_variants)] match msg { // Send a pong in response to a ping. AggregatedMessage::Ping(bytes) => { @@ -1343,6 +1347,7 @@ pub fn client_websocket( break; } + // Lint allow on `match` above allows this: it's a catch-all for anything not know here. other => { warn!("Unexpected message {other:?}"); break; @@ -1428,7 +1433,7 @@ pub fn client_websocket( // Don't stop timers; the re-connection may handle them. info!("Websocket re-enqueued."); websocket_queues.lock().unwrap().insert( - connection_id.to_string(), + connection_id.clone(), WebsocketQueues { from_websocket_tx, to_websocket_rx, @@ -1447,26 +1452,25 @@ pub fn client_websocket( // -------------- #[actix_web::main] pub async fn main( - extension_base_path: Option<&Path>, + base_path: &Path, addr: &SocketAddr, credentials: Option, level: LevelFilter, + register_routes: impl RegisterRoutes, ) -> std::io::Result<()> { - init_server(extension_base_path, level)?; - let server = setup_server(addr, credentials)?.0; + init_server(base_path, level)?; + let server = setup_server(addr, credentials, register_routes)?.0; server.await } // Perform global init of the server. This must only be called once; it must be // called before the server is run. pub fn init_server( - // If provided, the path to the location of this extension's files. This is - // used to locate static files for the webserver, etc. When None, assume the - // default layout. - extension_base_path: Option<&Path>, + // See `set_root_path`'s docs for what this should contain. + base_path: &Path, level: LevelFilter, ) -> std::io::Result<()> { - set_root_path(extension_base_path)?; + set_root_path(base_path)?; // The unit tests include a test logger; don't config the logger in a test // build. #[cfg(test)] @@ -1481,15 +1485,17 @@ pub fn init_server( pub fn setup_server( addr: &SocketAddr, credentials: Option, + register_routes: impl RegisterRoutes, ) -> std::io::Result<(Server, Data)> { let capture_spool_path = ROOT_PATH.lock().unwrap().join("capture-spool"); - setup_server_with_capture_spool(addr, credentials, capture_spool_path) + setup_server_with_capture_spool(addr, credentials, &capture_spool_path, register_routes) } pub fn setup_server_with_capture_spool( addr: &SocketAddr, credentials: Option, - capture_spool_path: PathBuf, + capture_spool_path: &Path, + register_routes: impl RegisterRoutes, ) -> std::io::Result<(Server, Data)> { // Pre-load the bundled files before starting the webserver. let _ = &*BUNDLED_FILES_MAP; @@ -1503,6 +1509,7 @@ pub fn setup_server_with_capture_spool( auth, )), &app_data_server, + ®ister_routes, ) }) // We only have one user; don't spawn lots of threads. @@ -1554,7 +1561,7 @@ pub fn configure_logger(level: LevelFilter) -> Result<(), Box Result<(), Box) -> WebAppState { let capture_spool_path = ROOT_PATH.lock().unwrap().join("capture-spool"); - make_app_data_with_capture_spool(credentials, capture_spool_path) + make_app_data_with_capture_spool(credentials, &capture_spool_path) } fn make_app_data_with_capture_spool( credentials: Option, - capture_spool_path: PathBuf, + capture_spool_path: &Path, ) -> WebAppState { // Initialize capture with a durable local upload spool. The VS Code // extension supplies the CaptureWebService endpoint and bearer token at // runtime; this server never reads database credentials from disk or env. - let capture: Option = match EventCapture::new(capture_spool_path.clone()) { + let capture: Option = match EventCapture::new(capture_spool_path.to_path_buf()) { Ok(ec) => { - info!("Capture: enabled with local upload spool at {capture_spool_path:?}"); + info!( + "Capture: enabled with local upload spool at \"{}\"", + capture_spool_path.display() + ); Some(ec) } Err(err) => { @@ -1602,13 +1613,77 @@ fn make_app_data_with_capture_spool( }) } -// Configure the web application. I'd like to make this return an -// `App`, but `AppEntry` is a private module. -pub fn configure_app(app: App, app_data: &WebAppState) -> App +// A callback which adds IDE-specific routes to the web application. +// `HttpServer::new` builds a fresh `App` per worker, and its inner service +// factory type (`AppEntry`) is private to `actix-web`; a plain +// `Fn(App) -> App` closure can't be named as a field or parameter type +// generically enough to pass through `setup_server`. This trait sidesteps +// that: its method is itself generic over `T`, so a single implementor works +// for whatever `T` `HttpServer::new` picks internally. +pub trait RegisterRoutes: Clone + Send + 'static { + fn register(&self, app: App) -> App + where + T: ServiceFactory; +} + +// A `RegisterRoutes` implementation which adds no additional routes. +#[derive(Clone)] +pub struct NoExtraRoutes; + +impl RegisterRoutes for NoExtraRoutes { + fn register(&self, app: App) -> App + where + T: ServiceFactory, + { + app + } +} + +// Combine two `RegisterRoutes` implementors into one, so a caller (such as +// the standalone CLI) can opt into more than one route group. +impl RegisterRoutes for (A, B) { + fn register(&self, app: App) -> App + where + T: ServiceFactory, + { + self.1.register(self.0.register(app)) + } +} + +// Registers the `/ping` and `/stop` process-lifecycle routes. These are only +// meaningful when the server runs as an independent OS process that another +// process must poll for liveness and can ask to shut down over HTTP -- the +// standalone CLI's `start`/`stop` subcommands. The VSCode extension embeds +// the server in-process and controls its lifecycle directly via +// [`crate::ide::CodeChatEditorServer::stop_server`], so it doesn't need these +// routes. This crate's own test harness (`ide::vscode::tests`) also registers +// them, reusing `/ping` to detect when its shared test webserver has started. +#[derive(Clone)] +pub struct LifecycleRoutes; + +impl RegisterRoutes for LifecycleRoutes { + fn register(&self, app: App) -> App + where + T: ServiceFactory, + { + app.service(ping).service(stop) + } +} + +// Configure the web application with the core, IDE-agnostic routes (static +// file serving). Every IDE integration (VSCode, the filewatcher IDE, ...) +// registers its own routes via `register_routes`, invoked after the core +// routes are added. I'd like to make this return an `App`, but +// `AppEntry` is a private module. +pub fn configure_app( + app: App, + app_data: &WebAppState, + register_routes: &impl RegisterRoutes, +) -> App where T: ServiceFactory, { - app + let app = app // Provide data to all endpoints -- the compiler lexers. .app_data(app_data.clone()) // Serve static files per the @@ -1616,22 +1691,8 @@ where .service(actix_files::Files::new( "/static", CLIENT_STATIC_PATH.as_os_str(), - )) - // These endpoints serve the files from the filesystem and the - // websockets. - .service(filewatcher_browser_endpoint) - .service(filewatcher_client_endpoint) - .service(filewatcher_websocket) - .service(serve_vscode_fs) - .service(vscode_ide_websocket) - .service(vscode_client_websocket) - .service(vscode_client_framework) - .service(ping) - .service(stop) - // Reroute to the filewatcher filesystem for typical user-requested - // URLs. - .route("/", web::get().to(filewatcher_root_fs_redirect)) - .route("/fw/fsb", web::get().to(filewatcher_root_fs_redirect)) + )); + register_routes.register(app) } // Utilities @@ -1704,7 +1765,7 @@ pub fn url_to_path( .map(|path_segment| { urlencoding::decode(path_segment) .map_err(UrlToPathError::UnableToDecode) - .map(|path_seg| path_seg.replace("\\", "%5C")) + .map(|path_seg| path_seg.replace('\\', "%5C")) }) .collect::, UrlToPathError>>()?; @@ -1766,13 +1827,14 @@ pub fn try_canonicalize(file_path: &str) -> Result, file_path: &Path) -> String { // First, convert the path to use forward slashes. let pathname = simplified(file_path) .to_slash_lossy() // The convert each part of the path to a URL-encoded string. (This // avoids encoding the slashes.) - .split("/") + .split('/') .map(|s| urlencoding::encode(s)) // Then put it all back together. .collect::>() @@ -1790,17 +1852,20 @@ pub fn path_to_url(prefix: &str, connection_id: Option<&str>, file_path: &Path) // Given a string (which is probably a pathname), drop the leading slash if it's // present. +#[must_use] pub fn drop_leading_slash(path_: &str) -> &str { path_.strip_prefix('/').unwrap_or(path_) } // Given a `Path`, transform it into a displayable HTML string (with any // necessary escaping). +#[must_use] pub fn path_display(p: &Path) -> Cow<'_, str> { escape_text(simplified(p).to_string_lossy()) } // Return a Not Found (404) error with the provided text (not HTML) body. +#[must_use] pub fn http_not_found(msg: &str) -> HttpResponse { HttpResponse::NotFound() .content_type(ContentType::html()) @@ -1808,6 +1873,7 @@ pub fn http_not_found(msg: &str) -> HttpResponse { } // Wrap the provided HTML body in DOCTYPE/html/head tags. +#[must_use] pub fn html_wrapper(body: &str) -> String { formatdoc!( r#" @@ -1858,12 +1924,12 @@ pub async fn get_server_url(port: u16) -> Result { ]) .status() .await?; - if !status.success() { - Err(GetServerUrlError::NonZeroExitStatus(status.code())) - } else { + if status.success() { Ok(format!( "https://{codespace_name}-{port}.{codespace_domain}" )) + } else { + Err(GetServerUrlError::NonZeroExitStatus(status.code())) } } else { // We're running locally, so use localhost. diff --git a/server/src/webserver/tests.rs b/server/src/webserver/tests.rs index c47a49af..e6c94fc6 100644 --- a/server/src/webserver/tests.rs +++ b/server/src/webserver/tests.rs @@ -20,39 +20,23 @@ // // ### 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::ide::vscode::tests::IP_PORT; use test_utils::{cast, prep_test_dir}; -// Support functions -// ----------------- -// -// The lint on using `cargo_bin` doesn't apply, since this is only available for -// integration tests per the -// [docs](https://docs.rs/assert_cmd/latest/assert_cmd/cargo/macro.cargo_bin_cmd.html). -// Text of the warning: -// -// ``` -// 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() -} - // Tests // ----- +// An arbitrary set of URL path segments used only as example test data for +// `url_to_path`'s `expected_prefix` parameter -- it matches the filewatcher +// IDE's own prefix (see `extensions/standalone/src/filewatcher.rs`), but any +// prefix would do here. +const PATH_PREFIX: &[&str] = &["fw", "fsc"]; + #[test] fn test_url_to_path() { let (temp_dir, test_dir) = prep_test_dir!(); @@ -65,7 +49,7 @@ fn test_url_to_path() { "http://127.0.0.1:{IP_PORT}/fw/fsc/dummy_connection_id/{}path%20spaces/foo.py", if cfg!(windows) { "C:/" } else { "" } ), - FILEWATCHER_PATH_PREFIX + PATH_PREFIX ), Ok ), @@ -83,7 +67,7 @@ fn test_url_to_path() { "http://127.0.0.1:{IP_PORT}/fw/fsc/dummy_connection_id/{}foo%5Cbar.py", if cfg!(windows) { "C:/" } else { "" } ), - FILEWATCHER_PATH_PREFIX + PATH_PREFIX ), Ok ), @@ -100,7 +84,7 @@ fn test_url_to_path() { &format!( "http://127.0.0.1:{IP_PORT}/fw/fsc/dummy_connection_id/{test_dir_str}/test%20spaces.py" ), - FILEWATCHER_PATH_PREFIX + PATH_PREFIX ) .unwrap() .canonicalize() @@ -128,33 +112,3 @@ fn test_path_to_url() { // Report any errors produced when removing the temporary directory. temp_dir.close().unwrap(); } - -// 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. 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(); - - // 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/code_chat_editor/ide/filewatcher/tests/test_websocket_timeout/test.py b/server/tests/fixtures/code_chat_editor/ide/filewatcher/tests/test_websocket_timeout/test.py deleted file mode 100644 index e69de29b..00000000 diff --git a/server/tests/fixtures/code_chat_editor/ide/filewatcher/tests/test_websocket_update_1/test.py b/server/tests/fixtures/code_chat_editor/ide/filewatcher/tests/test_websocket_update_1/test.py deleted file mode 100644 index e69de29b..00000000 diff --git a/server/tests/fixtures/overall_1/test_client/test.py b/server/tests/fixtures/overall/overall_1/test_client/test.py similarity index 100% rename from server/tests/fixtures/overall_1/test_client/test.py rename to server/tests/fixtures/overall/overall_1/test_client/test.py diff --git a/server/tests/fixtures/overall_1/test_client/toc.md b/server/tests/fixtures/overall/overall_1/test_client/toc.md similarity index 100% rename from server/tests/fixtures/overall_1/test_client/toc.md rename to server/tests/fixtures/overall/overall_1/test_client/toc.md diff --git a/server/tests/fixtures/overall_1/test_client_updates/test.py b/server/tests/fixtures/overall/overall_1/test_client_updates/test.py similarity index 100% rename from server/tests/fixtures/overall_1/test_client_updates/test.py rename to server/tests/fixtures/overall/overall_1/test_client_updates/test.py diff --git a/server/tests/fixtures/overall_1/test_client_updates/toc.md b/server/tests/fixtures/overall/overall_1/test_client_updates/toc.md similarity index 100% rename from server/tests/fixtures/overall_1/test_client_updates/toc.md rename to server/tests/fixtures/overall/overall_1/test_client_updates/toc.md diff --git a/server/tests/fixtures/overall_1/test_server/test.md b/server/tests/fixtures/overall/overall_1/test_server/test.md similarity index 100% rename from server/tests/fixtures/overall_1/test_server/test.md rename to server/tests/fixtures/overall/overall_1/test_server/test.md diff --git a/server/tests/fixtures/overall_1/test_server/test.pdf b/server/tests/fixtures/overall/overall_1/test_server/test.pdf similarity index 100% rename from server/tests/fixtures/overall_1/test_server/test.pdf rename to server/tests/fixtures/overall/overall_1/test_server/test.pdf diff --git a/server/tests/fixtures/overall_1/test_server/test.py b/server/tests/fixtures/overall/overall_1/test_server/test.py similarity index 100% rename from server/tests/fixtures/overall_1/test_server/test.py rename to server/tests/fixtures/overall/overall_1/test_server/test.py diff --git a/server/tests/fixtures/overall_1/test_server/test.txt b/server/tests/fixtures/overall/overall_1/test_server/test.txt similarity index 100% rename from server/tests/fixtures/overall_1/test_server/test.txt rename to server/tests/fixtures/overall/overall_1/test_server/test.txt diff --git a/server/tests/fixtures/overall_1/test_server/toc.md b/server/tests/fixtures/overall/overall_1/test_server/toc.md similarity index 100% rename from server/tests/fixtures/overall_1/test_server/toc.md rename to server/tests/fixtures/overall/overall_1/test_server/toc.md diff --git a/server/tests/fixtures/overall_2/test_4/test.py b/server/tests/fixtures/overall/overall_2/test_4/test.py similarity index 100% rename from server/tests/fixtures/overall_2/test_4/test.py rename to server/tests/fixtures/overall/overall_2/test_4/test.py diff --git a/server/tests/fixtures/overall_2/test_5/test.py b/server/tests/fixtures/overall/overall_2/test_5/test.py similarity index 100% rename from server/tests/fixtures/overall_2/test_5/test.py rename to server/tests/fixtures/overall/overall_2/test_5/test.py diff --git a/server/tests/fixtures/overall_2/test_6/test.md b/server/tests/fixtures/overall/overall_2/test_6/test.md similarity index 100% rename from server/tests/fixtures/overall_2/test_6/test.md rename to server/tests/fixtures/overall/overall_2/test_6/test.md diff --git a/server/tests/fixtures/overall_3/test_7/test.py b/server/tests/fixtures/overall/overall_3/test_7/test.py similarity index 100% rename from server/tests/fixtures/overall_3/test_7/test.py rename to server/tests/fixtures/overall/overall_3/test_7/test.py diff --git a/server/tests/fixtures/overall_3/test_8/test.py b/server/tests/fixtures/overall/overall_3/test_8/test.py similarity index 100% rename from server/tests/fixtures/overall_3/test_8/test.py rename to server/tests/fixtures/overall/overall_3/test_8/test.py diff --git a/server/tests/fixtures/overall_3/test_9/test.py b/server/tests/fixtures/overall/overall_3/test_9/test.py similarity index 100% rename from server/tests/fixtures/overall_3/test_9/test.py rename to server/tests/fixtures/overall/overall_3/test_9/test.py diff --git a/server/tests/fixtures/overall_4/test_arrow_key_navigation/test.py b/server/tests/fixtures/overall/overall_4/test_arrow_key_navigation/test.py similarity index 100% rename from server/tests/fixtures/overall_4/test_arrow_key_navigation/test.py rename to server/tests/fixtures/overall/overall_4/test_arrow_key_navigation/test.py diff --git a/server/tests/fixtures/overall_4/test_arrow_key_navigation_multiline_doc_block/test.py b/server/tests/fixtures/overall/overall_4/test_arrow_key_navigation_multiline_doc_block/test.py similarity index 100% rename from server/tests/fixtures/overall_4/test_arrow_key_navigation_multiline_doc_block/test.py rename to server/tests/fixtures/overall/overall_4/test_arrow_key_navigation_multiline_doc_block/test.py diff --git a/server/tests/fixtures/overall_4/test_horizontal_scroll_preserved/test.py b/server/tests/fixtures/overall/overall_4/test_horizontal_scroll_preserved/test.py similarity index 100% rename from server/tests/fixtures/overall_4/test_horizontal_scroll_preserved/test.py rename to server/tests/fixtures/overall/overall_4/test_horizontal_scroll_preserved/test.py diff --git a/server/tests/fixtures/overall_4/test_xss/test.md b/server/tests/fixtures/overall/overall_4/test_xss/test.md similarity index 100% rename from server/tests/fixtures/overall_4/test_xss/test.md rename to server/tests/fixtures/overall/overall_4/test_xss/test.md diff --git a/server/tests/fixtures/overall_5/test_cursor_home_from_code_after_doc_block/test.py b/server/tests/fixtures/overall/overall_5/test_cursor_home_from_code_after_doc_block/test.py similarity index 100% rename from server/tests/fixtures/overall_5/test_cursor_home_from_code_after_doc_block/test.py rename to server/tests/fixtures/overall/overall_5/test_cursor_home_from_code_after_doc_block/test.py diff --git a/server/tests/fixtures/overall_5/test_edit_preserves_cursor_scroll_in_large_doc_block/test.rs b/server/tests/fixtures/overall/overall_5/test_edit_preserves_cursor_scroll_in_large_doc_block/test.rs similarity index 100% rename from server/tests/fixtures/overall_5/test_edit_preserves_cursor_scroll_in_large_doc_block/test.rs rename to server/tests/fixtures/overall/overall_5/test_edit_preserves_cursor_scroll_in_large_doc_block/test.rs diff --git a/server/tests/overall.rs b/server/tests/overall.rs new file mode 100644 index 00000000..6d50bee0 --- /dev/null +++ b/server/tests/overall.rs @@ -0,0 +1,44 @@ +// Copyright (C) 2025 Bryan A. Jones. +// +// This file is part of the CodeChat Editor. The CodeChat Editor is free +// software: you can redistribute it and/or modify it under the terms of the GNU +// General Public License as published by the Free Software Foundation, either +// version 3 of the License, or (at your option) any later version. +// +// The CodeChat Editor is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with +// the CodeChat Editor. If not, see +// [http://www.gnu.org/licenses](http://www.gnu.org/licenses). +// +// This whole binary is test code, so relax the pedantic Clippy lints that +// `Cargo.toml`'s `[lints.clippy]` enables for production code. +#![allow( + clippy::too_many_lines, + clippy::cast_possible_truncation, + clippy::float_cmp +)] +/// `overall.rs` - test the overall system +/// ======================================= +/// +/// This file combines all the `overall_*` test modules into a single test +/// executable, so that a single instance of the web driver server is shared by +/// all of them. See `overall/common/mod.rs` for the shared test harness. +/// +/// To run these tests, execute `cargo test --test overall +/// ` in the `server/` directory. +#[path = "overall/common/mod.rs"] +mod common; +#[path = "overall/overall_1.rs"] +mod overall_1; +#[path = "overall/overall_2.rs"] +mod overall_2; +#[path = "overall/overall_3.rs"] +mod overall_3; +#[path = "overall/overall_4.rs"] +mod overall_4; +#[path = "overall/overall_5.rs"] +mod overall_5; diff --git a/server/tests/overall_common/mod.rs b/server/tests/overall/common/mod.rs similarity index 94% rename from server/tests/overall_common/mod.rs rename to server/tests/overall/common/mod.rs index 2139e86e..4ac63897 100644 --- a/server/tests/overall_common/mod.rs +++ b/server/tests/overall/common/mod.rs @@ -13,7 +13,7 @@ // You should have received a copy of the GNU General Public License along with // the CodeChat Editor. If not, see // [http://www.gnu.org/licenses](http://www.gnu.org/licenses). -/// `overall_core/mod.rs` - test the overall system +/// `overall/common/mod.rs` - test the overall system /// =============================================== /// /// These are functional tests of the overall system, performed by attaching a @@ -40,12 +40,14 @@ // ### Standard library use std::{ collections::HashMap, - env, error::Error, panic::AssertUnwindSafe, path::{Path, PathBuf}, time::Duration, }; +// Only used on Linux, to check whether CI is running this test. +#[cfg(target_os = "linux")] +use std::env; use assert_fs::TempDir; // ### Third-party @@ -55,7 +57,7 @@ use pretty_assertions::assert_eq; use serde_json::Value; use thirtyfour::{ BrowserLogEntry, By, ChromiumLikeCapabilities, DesiredCapabilities, Key, LoggingPrefsLogLevel, - TypingData, WebDriver, WebElement, error::WebDriverError, + TypingData, WebDriver, WebElement, error::WebDriverError, prelude::ElementQueryable, }; use tracing::{debug, error, info, warn}; use tracing_log::LogTracer; @@ -66,7 +68,7 @@ use code_chat_editor::{ ide::CodeChatEditorServer, webserver::{ CursorPosition, EditorMessage, EditorMessageContents, MESSAGE_ID_INCREMENT, ResultErrTypes, - ResultOkTypes, UpdateMessageContents, set_root_path, + ResultOkTypes, UpdateMessageContents, set_root_path, test_root_path, }, }; use test_utils::cast; @@ -190,7 +192,7 @@ impl ExpectedMessages { ); } - pub fn check(&mut self, editor_message: EditorMessage) { + pub fn check(&mut self, editor_message: &EditorMessage) { if let Some((ref mut editor_message_contents, is_dynamic)) = self.0.remove(&(editor_message.id as i64)) { @@ -198,7 +200,7 @@ impl ExpectedMessages { && let EditorMessageContents::Update(emc) = editor_message_contents && let Some(contents) = &mut emc.contents { - let version = get_version(&editor_message); + let version = get_version(editor_message); contents.version = version; } // Special case: @@ -216,7 +218,7 @@ impl ExpectedMessages { codechat_server: &CodeChatEditorServerLog, timeout: Duration, ) { - self.check(codechat_server.get_message_timeout(timeout).await.unwrap()); + self.check(&codechat_server.get_message_timeout(timeout).await.unwrap()); } pub async fn assert_all_messages( @@ -226,7 +228,7 @@ impl ExpectedMessages { ) { while !self.0.is_empty() { if let Some(editor_message) = codechat_server.get_message_timeout(timeout).await { - self.check(editor_message); + self.check(&editor_message); } else { panic!( "No matching messages found. Unmatched messages:\n{:#?}", @@ -240,7 +242,7 @@ impl ExpectedMessages { // Time to wait for browser/WebDriver-backed client-server messages. This // matches the client-side response window and gives CI enough room for autosave // and loadfile acknowledgements under matrix load. -pub const TIMEOUT: Duration = Duration::from_millis(15000); +pub const TIMEOUT: Duration = Duration::from_secs(15); // Browser-backed tests share a single WebDriver endpoint. Safari on macOS CI is // unreliable with overlapping sessions, so serialize the harness. @@ -290,6 +292,8 @@ pub async fn harness< // // Comment/uncomment these out to debug test failures. caps.add_arg("--headless")?; + // See [SO](https://stackoverflow.com/questions/78996364/chrome-129-headless-shows-blank-window) -- this prevents a blank windows popping up for each test. Tested with Chrome version 150.0.7871.47 (Official Build) (64-bit). + caps.add_arg("--window-position=-2400,-2400")?; //caps.add_arg("--auto-open-devtools-for-tabs")?; // Insert the code in a test to pause it for manual inspection. //use std::time::Duration; @@ -311,8 +315,7 @@ pub async fn harness< // certain this is correct. Hopefully, it's good enough for testing. let ret = AssertUnwindSafe(async move { // ### Setup - let p = env::current_exe().unwrap().parent().unwrap().join("../.."); - set_root_path(Some(&p)).unwrap(); + set_root_path(&test_root_path()).unwrap(); // Wrap the server so every call the test framework makes also drains // the browser's JavaScript console log (see `CodeChatEditorServerLog`). let codechat_server = CodeChatEditorServerLog::new( @@ -336,7 +339,7 @@ pub async fn harness< let client_html = cast!(&em_html.message, EditorMessageContents::ClientHtml); let find_str = "