diff --git a/CHANGELOG.md b/CHANGELOG.md index 41989a8f..808d9c8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,35 @@ Changelog [Github master](https://github.com/bjones1/CodeChat_Editor) ----------------------------------------------------------- -* No changes. +*  No changes. + +Version 0.1.53 -- 2026-Apr-14 +----------------------------- + +* Claude code reviews revealed the following issues, which were fixed manually: + * Fix multi-byte Unicode encoding bug. + * Fix incorrect websocket shutdown sequence. + * On websocket disconnect and reconnect, correctly retain timeouts for + responses. + * Change websocket response timeout to a reasonable value. + * Provide better error reporting when `hashLocations.json` file isn't found. + * Improve encoder performance. + * Correct bug in diff computation. + * Correctly handle CRLFs in files that begin with a LF. + * Fix several XSS vulnerabilities. + * Remove redundant reference counting on shared state. + * Avoid panics when validating passwords and when converting paths to strings. + * Fix bugs in nested comment parser. + * Improve CLI interface with better userid/password parsing, error reporting, + and ipv6 support. + * Include Windows drive letter Z when looking for available drives. + * Use block comment delimiter found in code, not a hard-coded C-only + delimiter. + * Correct many errors in the grammar for supported languages. + * Add lexer support for shell scripts, Swift, TOML, and Verilog, and VHDL. + * Ensure math is re-typeset after saving the document. + * Delay math un-typesetting until all typesetting is complete. + * Improve error reporting in VSCode extension. Version 0.1.52 -- 2026-Apr-09 ----------------------------- diff --git a/builder/.gitignore b/builder/.gitignore index 1b33dcd1..2d3f357d 100644 --- a/builder/.gitignore +++ b/builder/.gitignore @@ -17,7 +17,7 @@ # [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/). # # `.gitignore` -- files for Git to ignore -# ============================================================================== +# ======================================= # # Rust build output target/ diff --git a/builder/Cargo.lock b/builder/Cargo.lock index 5ae52106..20cb2340 100644 --- a/builder/Cargo.lock +++ b/builder/Cargo.lock @@ -234,9 +234,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.184" +version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" [[package]] name = "log" diff --git a/builder/Cargo.toml b/builder/Cargo.toml index 79c2667f..8662b737 100644 --- a/builder/Cargo.toml +++ b/builder/Cargo.toml @@ -17,7 +17,7 @@ # [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/). # # `Cargo.toml` -- Rust build/package management config for the builder -# ============================================================================== +# ==================================================================== [package] name = "builder" version = "0.1.0" diff --git a/builder/src/main.rs b/builder/src/main.rs index 8316a125..2a6b366d 100644 --- a/builder/src/main.rs +++ b/builder/src/main.rs @@ -249,7 +249,7 @@ fn quick_copy_dir>(src: P, dest: P, files: Option

) -> io::Resu // Per // [these docs](https://learn.microsoft.com/en-us/troubleshoot/windows-server/backup-and-storage/return-codes-used-robocopy-utility), // check the return code. - if cfg!(windows) && exit_code >= 8 || !cfg!(windows) && exit_code != 0 { + if (cfg!(windows) && exit_code >= 8) || (!cfg!(windows) && exit_code != 0) { Err(io::Error::other(format!( "Copy process return code {exit_code} indicates failure." ))) @@ -271,6 +271,8 @@ fn remove_dir_all_if_exists + std::fmt::Display>(path: P) -> io:: Ok(()) } +/// Search and replace a file using the regex. It's currently only used to +/// update the version of a file; it does only one replacement. fn search_and_replace_file< P: AsRef + std::fmt::Display, S1: AsRef + std::fmt::Display, @@ -283,6 +285,8 @@ fn search_and_replace_file< let file_contents = fs::read_to_string(&path)?; let re = Regex::new(search_regex.as_ref()) .map_err(|err| io::Error::other(format!("Error in search regex {search_regex}: {err}")))?; + // Note that this does only one replacement -- there should be exactly one + // version number to replace in a file. let file_contents_replaced = re.replace(&file_contents, replace_string.as_ref()); assert_ne!( file_contents, file_contents_replaced, @@ -413,6 +417,8 @@ fn run_install(dev: bool) -> io::Result<()> { cargo binstall cargo-outdated --no-confirm; info "cargo binstall cargo-sort"; cargo binstall cargo-sort --no-confirm; + info "cargo binstall cargo-audit"; + cargo binstall cargo-audit --no-confirm; )?; } Ok(()) @@ -425,7 +431,7 @@ fn run_update() -> io::Result<()> { run_cmd!( info "Builder: cargo update"; cargo update --manifest-path=$BUILDER_PATH/Cargo.toml; - info "VSCoe extension: cargo update"; + info "VSCode extension: cargo update"; cargo update --manifest-path=$VSCODE_PATH/Cargo.toml; info "test_utils: cargo update" cargo update --manifest-path=$TEST_UTILS_PATH/Cargo.toml; @@ -469,6 +475,16 @@ fn run_format_and_lint(check_only: bool) -> io::Result<()> { info "test_utils: cargo clippy and fmt" cargo clippy --all-targets --all-features --tests --manifest-path=$TEST_UTILS_PATH/Cargo.toml -- $clippy_check_only; cargo fmt --all $check --manifest-path=$TEST_UTILS_PATH/Cargo.toml; + + info "cargo audit"; + cargo audit; + info "Builder: cargo audit"; + cargo audit --file=$BUILDER_PATH/Cargo.lock --no-fetch; + info "VSCode extension: cargo audit"; + cargo audit --file=$VSCODE_PATH/Cargo.lock --no-fetch; + info "test_utils: cargo audit" + cargo audit --file=$TEST_UTILS_PATH/Cargo.lock --no-fetch; + info "cargo sort"; cargo sort $check; cd $BUILDER_PATH; @@ -487,7 +503,9 @@ fn run_format_and_lint(check_only: bool) -> io::Result<()> { eslint_args.push(eslint_check) } run_script("npx", &eslint_args, CLIENT_PATH, true)?; - run_script("npx", &eslint_args, VSCODE_PATH, true) + run_script("npx", &eslint_args, VSCODE_PATH, true)?; + run_script("pnpm", &["audit", "--prod"], CLIENT_PATH, false)?; + run_script("pnpm", &["audit", "--prod"], VSCODE_PATH, false) } fn run_test() -> io::Result<()> { @@ -564,7 +582,7 @@ fn run_client_build( true, )?; - // \The PDF viewer for use with VSCode. Build it separately, // since it's loaded apart from the rest of the Client. run_script( &esbuild, diff --git a/client/eslint.config.js b/client/eslint.config.js index 494ef877..37e169c0 100644 --- a/client/eslint.config.js +++ b/client/eslint.config.js @@ -2,8 +2,8 @@ // // 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 +// 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. // @@ -30,7 +30,9 @@ export default defineConfig( eslintPluginPrettierRecommended, defineConfig([ { - // This must be the only key in this dict to be treated as a global ignore. Only global ignores can ignore directories. See the [docs](https://eslint.org/docs/latest/use/configure/configuration-files#globally-ignoring-files-with-ignores). + // This must be the only key in this dict to be treated as a global + // ignore. Only global ignores can ignore directories. See the + // [docs](https://eslint.org/docs/latest/use/configure/configuration-files#globally-ignoring-files-with-ignores). ignores: ["src/third-party/**"], }, { diff --git a/client/package.json5 b/client/package.json5 index c48f7880..389c80a9 100644 --- a/client/package.json5 +++ b/client/package.json5 @@ -43,7 +43,7 @@ url: 'https://github.com/bjones1/CodeChat_editor', }, type: 'module', - version: '0.1.52', + version: '0.1.53', dependencies: { '@codemirror/commands': '^6.10.3', '@codemirror/lang-cpp': '^6.0.3', @@ -60,6 +60,8 @@ '@codemirror/lang-sql': '^6.10.0', '@codemirror/lang-xml': '^6.1.0', '@codemirror/lang-yaml': '^6.1.3', + '@codemirror/language': '^6.12.3', + '@codemirror/legacy-modes': '^6.5.2', '@codemirror/state': '^6.6.0', '@codemirror/view': '6.38.8', '@hpcc-js/wasm-graphviz': '^1.21.2', @@ -79,20 +81,20 @@ '@types/mocha': '^10.0.10', '@types/node': '^24.12.2', '@types/toastify-js': '^1.12.4', - '@typescript-eslint/eslint-plugin': '^8.58.1', - '@typescript-eslint/parser': '^8.58.1', + '@typescript-eslint/eslint-plugin': '^8.58.2', + '@typescript-eslint/parser': '^8.58.2', chai: '^6.2.2', esbuild: '^0.28.0', eslint: '^10.2.0', 'eslint-config-prettier': '^10.1.8', 'eslint-plugin-import': '^2.32.0', 'eslint-plugin-prettier': '^5.5.5', - globals: '^17.4.0', + globals: '^17.5.0', mocha: '^11.7.5', - 'npm-check-updates': '^20.0.0', - prettier: '^3.8.1', + 'npm-check-updates': '^20.0.2', + prettier: '^3.8.2', typescript: '^6.0.2', - 'typescript-eslint': '^8.58.1', + 'typescript-eslint': '^8.58.2', }, scripts: { test: 'echo "Error: no test specified" && exit 1', diff --git a/client/pnpm-lock.yaml b/client/pnpm-lock.yaml index 001e9a44..947d96ac 100644 --- a/client/pnpm-lock.yaml +++ b/client/pnpm-lock.yaml @@ -53,6 +53,12 @@ importers: '@codemirror/lang-yaml': specifier: ^6.1.3 version: 6.1.3 + '@codemirror/language': + specifier: ^6.12.3 + version: 6.12.3 + '@codemirror/legacy-modes': + specifier: ^6.5.2 + version: 6.5.2 '@codemirror/state': specifier: ^6.6.0 version: 6.6.0 @@ -106,11 +112,11 @@ importers: specifier: ^1.12.4 version: 1.12.4 '@typescript-eslint/eslint-plugin': - specifier: ^8.58.1 - version: 8.58.1(@typescript-eslint/parser@8.58.1(eslint@10.2.0)(typescript@6.0.2))(eslint@10.2.0)(typescript@6.0.2) + specifier: ^8.58.2 + version: 8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.0)(typescript@6.0.2))(eslint@10.2.0)(typescript@6.0.2) '@typescript-eslint/parser': - specifier: ^8.58.1 - version: 8.58.1(eslint@10.2.0)(typescript@6.0.2) + specifier: ^8.58.2 + version: 8.58.2(eslint@10.2.0)(typescript@6.0.2) chai: specifier: ^6.2.2 version: 6.2.2 @@ -125,28 +131,28 @@ importers: version: 10.1.8(eslint@10.2.0) eslint-plugin-import: specifier: ^2.32.0 - version: 2.32.0(@typescript-eslint/parser@8.58.1(eslint@10.2.0)(typescript@6.0.2))(eslint@10.2.0) + version: 2.32.0(@typescript-eslint/parser@8.58.2(eslint@10.2.0)(typescript@6.0.2))(eslint@10.2.0) eslint-plugin-prettier: specifier: ^5.5.5 - version: 5.5.5(eslint-config-prettier@10.1.8(eslint@10.2.0))(eslint@10.2.0)(prettier@3.8.1) + version: 5.5.5(eslint-config-prettier@10.1.8(eslint@10.2.0))(eslint@10.2.0)(prettier@3.8.2) globals: - specifier: ^17.4.0 - version: 17.4.0 + specifier: ^17.5.0 + version: 17.5.0 mocha: specifier: ^11.7.5 version: 11.7.5 npm-check-updates: - specifier: ^20.0.0 - version: 20.0.0 + specifier: ^20.0.2 + version: 20.0.2 prettier: - specifier: ^3.8.1 - version: 3.8.1 + specifier: ^3.8.2 + version: 3.8.2 typescript: specifier: ^6.0.2 version: 6.0.2 typescript-eslint: - specifier: ^8.58.1 - version: 8.58.1(eslint@10.2.0)(typescript@6.0.2) + specifier: ^8.58.2 + version: 8.58.2(eslint@10.2.0)(typescript@6.0.2) packages: @@ -222,6 +228,9 @@ packages: '@codemirror/language@6.12.3': resolution: {integrity: sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA==} + '@codemirror/legacy-modes@6.5.2': + resolution: {integrity: sha512-/jJbwSTazlQEDOQw2FJ8LEEKVS72pU0lx6oM54kGpL8t/NJ2Jda3CZ4pcltiKTdqYSRk3ug1B3pil1gsjA6+8Q==} + '@codemirror/lint@6.9.5': resolution: {integrity: sha512-GElsbU9G7QT9xXhpUg1zWGmftA/7jamh+7+ydKRuT0ORpWS3wOSP0yT1FOlIZa7mIJjpVPipErsyvVqB9cfTFA==} @@ -733,63 +742,63 @@ packages: '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} - '@typescript-eslint/eslint-plugin@8.58.1': - resolution: {integrity: sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ==} + '@typescript-eslint/eslint-plugin@8.58.2': + resolution: {integrity: sha512-aC2qc5thQahutKjP+cl8cgN9DWe3ZUqVko30CMSZHnFEHyhOYoZSzkGtAI2mcwZ38xeImDucI4dnqsHiOYuuCw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.58.1 + '@typescript-eslint/parser': ^8.58.2 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.58.1': - resolution: {integrity: sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw==} + '@typescript-eslint/parser@8.58.2': + resolution: {integrity: sha512-/Zb/xaIDfxeJnvishjGdcR4jmr7S+bda8PKNhRGdljDM+elXhlvN0FyPSsMnLmJUrVG9aPO6dof80wjMawsASg==} 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.58.1': - resolution: {integrity: sha512-gfQ8fk6cxhtptek+/8ZIqw8YrRW5048Gug8Ts5IYcMLCw18iUgrZAEY/D7s4hkI0FxEfGakKuPK/XUMPzPxi5g==} + '@typescript-eslint/project-service@8.58.2': + resolution: {integrity: sha512-Cq6UfpZZk15+r87BkIh5rDpi38W4b+Sjnb8wQCPPDDweS/LRCFjCyViEbzHk5Ck3f2QDfgmlxqSa7S7clDtlfg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.58.1': - resolution: {integrity: sha512-TPYUEqJK6avLcEjumWsIuTpuYODTTDAtoMdt8ZZa93uWMTX13Nb8L5leSje1NluammvU+oI3QRr5lLXPgihX3w==} + '@typescript-eslint/scope-manager@8.58.2': + resolution: {integrity: sha512-SgmyvDPexWETQek+qzZnrG6844IaO02UVyOLhI4wpo82dpZJY9+6YZCKAMFzXb7qhx37mFK1QcPQ18tud+vo6Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.58.1': - resolution: {integrity: sha512-JAr2hOIct2Q+qk3G+8YFfqkqi7sC86uNryT+2i5HzMa2MPjw4qNFvtjnw1IiA1rP7QhNKVe21mSSLaSjwA1Olw==} + '@typescript-eslint/tsconfig-utils@8.58.2': + resolution: {integrity: sha512-3SR+RukipDvkkKp/d0jP0dyzuls3DbGmwDpVEc5wqk5f38KFThakqAAO0XMirWAE+kT00oTauTbzMFGPoAzB0A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.58.1': - resolution: {integrity: sha512-HUFxvTJVroT+0rXVJC7eD5zol6ID+Sn5npVPWoFuHGg9Ncq5Q4EYstqR+UOqaNRFXi5TYkpXXkLhoCHe3G0+7w==} + '@typescript-eslint/type-utils@8.58.2': + resolution: {integrity: sha512-Z7EloNR/B389FvabdGeTo2XMs4W9TjtPiO9DAsmT0yom0bwlPyRjkJ1uCdW1DvrrrYP50AJZ9Xc3sByZA9+dcg==} 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.58.1': - resolution: {integrity: sha512-io/dV5Aw5ezwzfPBBWLoT+5QfVtP8O7q4Kftjn5azJ88bYyp/ZMCsyW1lpKK46EXJcaYMZ1JtYj+s/7TdzmQMw==} + '@typescript-eslint/types@8.58.2': + resolution: {integrity: sha512-9TukXyATBQf/Jq9AMQXfvurk+G5R2MwfqQGDR2GzGz28HvY/lXNKGhkY+6IOubwcquikWk5cjlgPvD2uAA7htQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.58.1': - resolution: {integrity: sha512-w4w7WR7GHOjqqPnvAYbazq+Y5oS68b9CzasGtnd6jIeOIeKUzYzupGTB2T4LTPSv4d+WPeccbxuneTFHYgAAWg==} + '@typescript-eslint/typescript-estree@8.58.2': + resolution: {integrity: sha512-ELGuoofuhhoCvNbQjFFiobFcGgcDCEm0ThWdmO4Z0UzLqPXS3KFvnEZ+SHewwOYHjM09tkzOWXNTv9u6Gqtyuw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.58.1': - resolution: {integrity: sha512-Ln8R0tmWC7pTtLOzgJzYTXSCjJ9rDNHAqTaVONF4FEi2qwce8mD9iSOxOpLFFvWp/wBFlew0mjM1L1ihYWfBdQ==} + '@typescript-eslint/utils@8.58.2': + resolution: {integrity: sha512-QZfjHNEzPY8+l0+fIXMvuQ2sJlplB4zgDZvA+NmvZsZv3EQwOcc1DuIU1VJUTWZ/RKouBMhDyNaBMx4sWvrzRA==} 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.58.1': - resolution: {integrity: sha512-y+vH7QE8ycjoa0bWciFg7OpFcipUuem1ujhrdLtq1gByKwfbC7bPeKsiny9e0urg93DqwGcHey+bGRKCnF1nZQ==} + '@typescript-eslint/visitor-keys@8.58.2': + resolution: {integrity: sha512-f1WO2Lx8a9t8DARmcWAUPJbu0G20bJlj8L4z72K00TMeJAoyLr/tHhI/pzYBLrR4dXWkcxO1cWYZEOX8DKHTqA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@upsetjs/venn.js@2.0.0': @@ -870,11 +879,11 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} - brace-expansion@1.1.13: - resolution: {integrity: sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==} + brace-expansion@1.1.14: + resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} - brace-expansion@2.0.3: - resolution: {integrity: sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==} + brace-expansion@2.1.0: + resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==} brace-expansion@5.0.5: resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} @@ -887,8 +896,8 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} - call-bind@1.0.8: - resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} engines: {node: '>= 0.4'} call-bound@1.0.4: @@ -1175,8 +1184,8 @@ packages: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} - dompurify@3.3.3: - resolution: {integrity: sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==} + dompurify@3.4.0: + resolution: {integrity: sha512-nolgK9JcaUXMSmW+j1yaSvaEaoXYHwWyGJlkoCTghc97KgGDDSnpoU/PlEnw63Ah+TGKFOyY+X5LnxaWbCSfXg==} dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} @@ -1415,8 +1424,8 @@ packages: deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true - globals@17.4.0: - resolution: {integrity: sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==} + globals@17.5.0: + resolution: {integrity: sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==} engines: {node: '>=18'} globalthis@1.0.4: @@ -1718,8 +1727,8 @@ packages: node-readable-to-web-readable-stream@0.4.2: resolution: {integrity: sha512-/cMZNI34v//jUTrI+UIo4ieHAB5EZRY/+7OmXZgBxaWBMcW2tGdceIw06RFxWxrKZ5Jp3sI2i5TsRo+CBhtVLQ==} - npm-check-updates@20.0.0: - resolution: {integrity: sha512-qCs02x51irGf0okCttwv8lHEO2NxT903IJ2bKpG82kIzkm6pfT3CoWB5YIvqY/wi/DdnYRfI7eVfCYYymQgvCg==} + npm-check-updates@20.0.2: + resolution: {integrity: sha512-nvbcXiprjMOoSX0FCHC41kjpZhNFURV53KMU0MMa0U10RPHeoHpiilMg2P8g9NLSQoo0umSH77tUqHWTOH3w7A==} engines: {node: '>=20.0.0', npm: '>=8.12.1'} hasBin: true @@ -1826,8 +1835,8 @@ packages: resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==} engines: {node: '>=6.0.0'} - prettier@3.8.1: - resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} + prettier@3.8.2: + resolution: {integrity: sha512-8c3mgTe0ASwWAJK+78dpviD+A8EqhndQPUBpNUIPt6+xWlIigCwfN01lWr9MAede4uqXGTEKeQWTvzb3vjia0Q==} engines: {node: '>=14'} hasBin: true @@ -2047,8 +2056,8 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} - typescript-eslint@8.58.1: - resolution: {integrity: sha512-gf6/oHChByg9HJvhMO1iBexJh12AqqTfnuxscMDOVqfJW3htsdRJI/GfPpHTTcyeB8cSTUY2JcZmVgoyPqcrDg==} + typescript-eslint@8.58.2: + resolution: {integrity: sha512-V8iSng9mRbdZjl54VJ9NKr6ZB+dW0J3TzRXRGcSbLIej9jV86ZRtlYeTKDR/QLxXykocJ5icNzbsl2+5TzIvcQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -2314,6 +2323,10 @@ snapshots: '@lezer/lr': 1.4.8 style-mod: 4.1.3 + '@codemirror/legacy-modes@6.5.2': + dependencies: + '@codemirror/language': 6.12.3 + '@codemirror/lint@6.9.5': dependencies: '@codemirror/state': 6.6.0 @@ -2778,14 +2791,14 @@ snapshots: '@types/trusted-types@2.0.7': optional: true - '@typescript-eslint/eslint-plugin@8.58.1(@typescript-eslint/parser@8.58.1(eslint@10.2.0)(typescript@6.0.2))(eslint@10.2.0)(typescript@6.0.2)': + '@typescript-eslint/eslint-plugin@8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.0)(typescript@6.0.2))(eslint@10.2.0)(typescript@6.0.2)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.58.1(eslint@10.2.0)(typescript@6.0.2) - '@typescript-eslint/scope-manager': 8.58.1 - '@typescript-eslint/type-utils': 8.58.1(eslint@10.2.0)(typescript@6.0.2) - '@typescript-eslint/utils': 8.58.1(eslint@10.2.0)(typescript@6.0.2) - '@typescript-eslint/visitor-keys': 8.58.1 + '@typescript-eslint/parser': 8.58.2(eslint@10.2.0)(typescript@6.0.2) + '@typescript-eslint/scope-manager': 8.58.2 + '@typescript-eslint/type-utils': 8.58.2(eslint@10.2.0)(typescript@6.0.2) + '@typescript-eslint/utils': 8.58.2(eslint@10.2.0)(typescript@6.0.2) + '@typescript-eslint/visitor-keys': 8.58.2 eslint: 10.2.0 ignore: 7.0.5 natural-compare: 1.4.0 @@ -2794,41 +2807,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.58.1(eslint@10.2.0)(typescript@6.0.2)': + '@typescript-eslint/parser@8.58.2(eslint@10.2.0)(typescript@6.0.2)': dependencies: - '@typescript-eslint/scope-manager': 8.58.1 - '@typescript-eslint/types': 8.58.1 - '@typescript-eslint/typescript-estree': 8.58.1(typescript@6.0.2) - '@typescript-eslint/visitor-keys': 8.58.1 + '@typescript-eslint/scope-manager': 8.58.2 + '@typescript-eslint/types': 8.58.2 + '@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.2) + '@typescript-eslint/visitor-keys': 8.58.2 debug: 4.4.3(supports-color@8.1.1) eslint: 10.2.0 typescript: 6.0.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.58.1(typescript@6.0.2)': + '@typescript-eslint/project-service@8.58.2(typescript@6.0.2)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.58.1(typescript@6.0.2) - '@typescript-eslint/types': 8.58.1 + '@typescript-eslint/tsconfig-utils': 8.58.2(typescript@6.0.2) + '@typescript-eslint/types': 8.58.2 debug: 4.4.3(supports-color@8.1.1) typescript: 6.0.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.58.1': + '@typescript-eslint/scope-manager@8.58.2': dependencies: - '@typescript-eslint/types': 8.58.1 - '@typescript-eslint/visitor-keys': 8.58.1 + '@typescript-eslint/types': 8.58.2 + '@typescript-eslint/visitor-keys': 8.58.2 - '@typescript-eslint/tsconfig-utils@8.58.1(typescript@6.0.2)': + '@typescript-eslint/tsconfig-utils@8.58.2(typescript@6.0.2)': dependencies: typescript: 6.0.2 - '@typescript-eslint/type-utils@8.58.1(eslint@10.2.0)(typescript@6.0.2)': + '@typescript-eslint/type-utils@8.58.2(eslint@10.2.0)(typescript@6.0.2)': dependencies: - '@typescript-eslint/types': 8.58.1 - '@typescript-eslint/typescript-estree': 8.58.1(typescript@6.0.2) - '@typescript-eslint/utils': 8.58.1(eslint@10.2.0)(typescript@6.0.2) + '@typescript-eslint/types': 8.58.2 + '@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.2) + '@typescript-eslint/utils': 8.58.2(eslint@10.2.0)(typescript@6.0.2) debug: 4.4.3(supports-color@8.1.1) eslint: 10.2.0 ts-api-utils: 2.5.0(typescript@6.0.2) @@ -2836,14 +2849,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.58.1': {} + '@typescript-eslint/types@8.58.2': {} - '@typescript-eslint/typescript-estree@8.58.1(typescript@6.0.2)': + '@typescript-eslint/typescript-estree@8.58.2(typescript@6.0.2)': dependencies: - '@typescript-eslint/project-service': 8.58.1(typescript@6.0.2) - '@typescript-eslint/tsconfig-utils': 8.58.1(typescript@6.0.2) - '@typescript-eslint/types': 8.58.1 - '@typescript-eslint/visitor-keys': 8.58.1 + '@typescript-eslint/project-service': 8.58.2(typescript@6.0.2) + '@typescript-eslint/tsconfig-utils': 8.58.2(typescript@6.0.2) + '@typescript-eslint/types': 8.58.2 + '@typescript-eslint/visitor-keys': 8.58.2 debug: 4.4.3(supports-color@8.1.1) minimatch: 10.2.5 semver: 7.7.4 @@ -2853,20 +2866,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.58.1(eslint@10.2.0)(typescript@6.0.2)': + '@typescript-eslint/utils@8.58.2(eslint@10.2.0)(typescript@6.0.2)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0) - '@typescript-eslint/scope-manager': 8.58.1 - '@typescript-eslint/types': 8.58.1 - '@typescript-eslint/typescript-estree': 8.58.1(typescript@6.0.2) + '@typescript-eslint/scope-manager': 8.58.2 + '@typescript-eslint/types': 8.58.2 + '@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.2) eslint: 10.2.0 typescript: 6.0.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.58.1': + '@typescript-eslint/visitor-keys@8.58.2': dependencies: - '@typescript-eslint/types': 8.58.1 + '@typescript-eslint/types': 8.58.2 eslint-visitor-keys: 5.0.1 '@upsetjs/venn.js@2.0.0': @@ -2906,7 +2919,7 @@ snapshots: array-includes@3.1.9: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 es-abstract: 1.24.2 @@ -2917,7 +2930,7 @@ snapshots: array.prototype.findlastindex@1.2.6: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 es-abstract: 1.24.2 @@ -2927,14 +2940,14 @@ snapshots: array.prototype.flat@1.3.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 es-abstract: 1.24.2 es-shim-unscopables: 1.1.0 array.prototype.flatmap@1.3.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 es-abstract: 1.24.2 es-shim-unscopables: 1.1.0 @@ -2942,7 +2955,7 @@ snapshots: arraybuffer.prototype.slice@1.0.4: dependencies: array-buffer-byte-length: 1.0.2 - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 es-abstract: 1.24.2 es-errors: 1.3.0 @@ -2961,12 +2974,12 @@ snapshots: balanced-match@4.0.4: {} - brace-expansion@1.1.13: + brace-expansion@1.1.14: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@2.0.3: + brace-expansion@2.1.0: dependencies: balanced-match: 1.0.2 @@ -2981,7 +2994,7 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 - call-bind@1.0.8: + call-bind@1.0.9: dependencies: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 @@ -3305,7 +3318,7 @@ snapshots: dependencies: esutils: 2.0.3 - dompurify@3.3.3: + dompurify@3.4.0: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -3326,7 +3339,7 @@ snapshots: array-buffer-byte-length: 1.0.2 arraybuffer.prototype.slice: 1.0.4 available-typed-arrays: 1.0.7 - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 data-view-buffer: 1.0.2 data-view-byte-length: 1.0.2 @@ -3448,17 +3461,17 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.58.1(eslint@10.2.0)(typescript@6.0.2))(eslint-import-resolver-node@0.3.10)(eslint@10.2.0): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.58.2(eslint@10.2.0)(typescript@6.0.2))(eslint-import-resolver-node@0.3.10)(eslint@10.2.0): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.58.1(eslint@10.2.0)(typescript@6.0.2) + '@typescript-eslint/parser': 8.58.2(eslint@10.2.0)(typescript@6.0.2) eslint: 10.2.0 eslint-import-resolver-node: 0.3.10 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.1(eslint@10.2.0)(typescript@6.0.2))(eslint@10.2.0): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.2(eslint@10.2.0)(typescript@6.0.2))(eslint@10.2.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -3469,7 +3482,7 @@ snapshots: doctrine: 2.1.0 eslint: 10.2.0 eslint-import-resolver-node: 0.3.10 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.58.1(eslint@10.2.0)(typescript@6.0.2))(eslint-import-resolver-node@0.3.10)(eslint@10.2.0) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.58.2(eslint@10.2.0)(typescript@6.0.2))(eslint-import-resolver-node@0.3.10)(eslint@10.2.0) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -3481,16 +3494,16 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.58.1(eslint@10.2.0)(typescript@6.0.2) + '@typescript-eslint/parser': 8.58.2(eslint@10.2.0)(typescript@6.0.2) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-prettier@5.5.5(eslint-config-prettier@10.1.8(eslint@10.2.0))(eslint@10.2.0)(prettier@3.8.1): + eslint-plugin-prettier@5.5.5(eslint-config-prettier@10.1.8(eslint@10.2.0))(eslint@10.2.0)(prettier@3.8.2): dependencies: eslint: 10.2.0 - prettier: 3.8.1 + prettier: 3.8.2 prettier-linter-helpers: 1.0.1 synckit: 0.11.12 optionalDependencies: @@ -3603,7 +3616,7 @@ snapshots: function.prototype.name@1.1.8: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 functions-have-names: 1.2.3 @@ -3653,7 +3666,7 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 - globals@17.4.0: {} + globals@17.5.0: {} globalthis@1.0.4: dependencies: @@ -3710,7 +3723,7 @@ snapshots: is-array-buffer@3.0.5: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 get-intrinsic: 1.3.0 @@ -3909,7 +3922,7 @@ snapshots: d3-sankey: 0.12.3 dagre-d3-es: 7.0.14 dayjs: 1.11.20 - dompurify: 3.3.3 + dompurify: 3.4.0 katex: 0.16.45 khroma: 2.1.0 lodash-es: 4.18.1 @@ -3925,11 +3938,11 @@ snapshots: minimatch@3.1.5: dependencies: - brace-expansion: 1.1.13 + brace-expansion: 1.1.14 minimatch@9.0.9: dependencies: - brace-expansion: 2.0.3 + brace-expansion: 2.1.0 minimist@1.2.8: {} @@ -3980,7 +3993,7 @@ snapshots: node-readable-to-web-readable-stream@0.4.2: optional: true - npm-check-updates@20.0.0: {} + npm-check-updates@20.0.2: {} object-inspect@1.13.4: {} @@ -3988,7 +4001,7 @@ snapshots: object.assign@4.1.7: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 @@ -3997,27 +4010,27 @@ snapshots: object.entries@1.1.9: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 object.fromentries@2.0.8: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 es-abstract: 1.24.2 es-object-atoms: 1.1.1 object.groupby@1.0.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 es-abstract: 1.24.2 object.values@1.2.1: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 @@ -4094,7 +4107,7 @@ snapshots: dependencies: fast-diff: 1.3.0 - prettier@3.8.1: {} + prettier@3.8.2: {} punycode@2.3.1: {} @@ -4106,7 +4119,7 @@ snapshots: reflect.getprototypeof@1.0.10: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 es-abstract: 1.24.2 es-errors: 1.3.0 @@ -4117,7 +4130,7 @@ snapshots: regexp.prototype.flags@1.5.4: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 es-errors: 1.3.0 get-proto: 1.0.1 @@ -4148,7 +4161,7 @@ snapshots: safe-array-concat@1.1.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 get-intrinsic: 1.3.0 has-symbols: 1.1.0 @@ -4254,7 +4267,7 @@ snapshots: string.prototype.trim@1.2.10: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-data-property: 1.1.4 define-properties: 1.2.1 @@ -4264,14 +4277,14 @@ snapshots: string.prototype.trimend@1.0.9: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 string.prototype.trimstart@1.0.8: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 es-object-atoms: 1.1.1 @@ -4341,7 +4354,7 @@ snapshots: typed-array-byte-length@1.0.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 has-proto: 1.2.0 @@ -4350,7 +4363,7 @@ snapshots: typed-array-byte-offset@1.0.4: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.8 + call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 has-proto: 1.2.0 @@ -4359,19 +4372,19 @@ snapshots: typed-array-length@1.0.7: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 is-typed-array: 1.1.15 possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript-eslint@8.58.1(eslint@10.2.0)(typescript@6.0.2): + typescript-eslint@8.58.2(eslint@10.2.0)(typescript@6.0.2): dependencies: - '@typescript-eslint/eslint-plugin': 8.58.1(@typescript-eslint/parser@8.58.1(eslint@10.2.0)(typescript@6.0.2))(eslint@10.2.0)(typescript@6.0.2) - '@typescript-eslint/parser': 8.58.1(eslint@10.2.0)(typescript@6.0.2) - '@typescript-eslint/typescript-estree': 8.58.1(typescript@6.0.2) - '@typescript-eslint/utils': 8.58.1(eslint@10.2.0)(typescript@6.0.2) + '@typescript-eslint/eslint-plugin': 8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.0)(typescript@6.0.2))(eslint@10.2.0)(typescript@6.0.2) + '@typescript-eslint/parser': 8.58.2(eslint@10.2.0)(typescript@6.0.2) + '@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.2) + '@typescript-eslint/utils': 8.58.2(eslint@10.2.0)(typescript@6.0.2) eslint: 10.2.0 typescript: 6.0.2 transitivePeerDependencies: @@ -4449,7 +4462,7 @@ snapshots: which-typed-array@1.1.20: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 for-each: 0.3.5 get-proto: 1.0.1 diff --git a/client/readme.md b/client/readme.md index 7a30e8b6..8c928fd1 100644 --- a/client/readme.md +++ b/client/readme.md @@ -1,5 +1,5 @@ `readme.md` - overview of the Client -================================================================================ +==================================== Inside the client: diff --git a/client/src/CodeChatEditor-test.mts b/client/src/CodeChatEditor-test.mts index 2ae94fbf..999953d0 100644 --- a/client/src/CodeChatEditor-test.mts +++ b/client/src/CodeChatEditor-test.mts @@ -15,19 +15,19 @@ // [http://www.gnu.org/licenses](http://www.gnu.org/licenses). // // `CodeChatEditor-test.mts` -- Tests for the CodeChat Editor client -// ============================================================================= +// ================================================================= // // To run tests, add a `?test` to any web page served by the CodeChat Editor // server. // // Imports -// ----------------------------------------------------------------------------- +// ------- import { assert } from "chai"; import "mocha/mocha.js"; import "mocha/mocha.css"; import { EditorView } from "@codemirror/view"; import { ChangeSpec, EditorState, EditorSelection } from "@codemirror/state"; -import { CodeMirror, CodeMirrorDocBlockTuple } from "./shared_types.mjs"; +import { CodeMirror, CodeMirrorDocBlockTuple } from "./shared.mjs"; import { DocBlockPlugin, CodeMirror_JSON_fields, @@ -44,7 +44,7 @@ import { const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); // Tests -// ----------------------------------------------------------------------------- +// ----- // // Defining this global variable signals the // CodeChat Editor to [run tests](CodeChatEditor.mts#CodeChatEditor_test). diff --git a/client/src/CodeChatEditor.mts b/client/src/CodeChatEditor.mts index 6f1bb026..59411f89 100644 --- a/client/src/CodeChatEditor.mts +++ b/client/src/CodeChatEditor.mts @@ -71,7 +71,7 @@ import { CodeMirror, autosave_timeout_ms, rand, -} from "./shared_types.mjs"; +} from "./shared.mjs"; import { show_toast } from "./show_toast.mjs"; // ### CSS @@ -257,10 +257,10 @@ const _open_lp = async ( // Get the mode from the page's query parameters. Default to edit using // the // [nullish coalescing operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator). - // TODO: this isn't typesafe, since the `mode` might not be a key of - // `EditorMode`. - /// @ts-expect-error("See above.") - const _editorMode = EditorMode[urlParams.get("mode") ?? "edit"]; + const mode = urlParams.get("mode") ?? EditorMode.edit; + const _editorMode = Object.values(EditorMode).includes(mode) + ? mode + : EditorMode.edit; // Get the [current_metadata](#current_metadata) from the // provided `code_chat_for_web` struct and store it as a global @@ -372,7 +372,11 @@ const _open_lp = async ( } }; -const save_lp = async (is_dirty: boolean) => { +const save_lp = async ( + // Avoid relying on the global `is_dirty`, which may change during an + // `await`. + is_dirty_now: boolean, +) => { const update: UpdateMessageContents = { // The Framework will fill in this value. file_path: "", @@ -385,7 +389,7 @@ const save_lp = async (is_dirty: boolean) => { } // Add the contents only if the document is dirty. - if (is_dirty) { + if (is_dirty_now) { /// @ts-expect-error("Declare here; it will be completed later.") let code_mirror_diffable: CodeMirrorDiffable = {}; if (is_doc_only()) { @@ -394,20 +398,25 @@ const save_lp = async (is_dirty: boolean) => { "CodeChat-body", ) as HTMLDivElement; mathJaxUnTypeset(codechat_body); - // 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.activeEditor!.save(); - ( - code_mirror_diffable as { - Plain: CodeMirror; - } - ).Plain = { - doc: doc_content, - doc_blocks: [], - }; - // Retypeset all math after saving the document. - await mathJaxTypeset(codechat_body); + // 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.activeEditor!.save(); + ( + code_mirror_diffable as { + Plain: CodeMirror; + } + ).Plain = { + doc: doc_content, + doc_blocks: [], + }; + } finally { + // Retypeset all math after saving the document. + await mathJaxTypeset(codechat_body); + } } else { code_mirror_diffable = CodeMirror_save(); assert("Plain" in code_mirror_diffable); @@ -518,15 +527,6 @@ export const restoreSelection = ({ } }; -// Per -// [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/platform#examples), -// here's the least bad way to choose between the control key and the command -// key. -const _os_is_osx = - navigator.platform.indexOf("Mac") === 0 || navigator.platform === "iPhone" - ? true - : false; - // Save CodeChat Editor contents. const on_save = async (only_if_dirty: boolean = false) => { if (only_if_dirty && !is_dirty) { diff --git a/client/src/CodeChatEditorFramework.mts b/client/src/CodeChatEditorFramework.mts index 4732628b..f871a210 100644 --- a/client/src/CodeChatEditorFramework.mts +++ b/client/src/CodeChatEditorFramework.mts @@ -17,7 +17,7 @@ // `CodeChatEditorFramework.mts` -- the CodeChat Editor Client Framework // ===================================================================== // -// This maintains a websocket connection between the CodeChat Editor Server. The +// This maintains a websocket connection with the CodeChat Editor Server. The // accompanying HTML is a full-screen iframe, allowing the Framework to change // or update the webpage in response to messages received from the websocket, or // to report navigation events to as a websocket message when the iframe's @@ -40,7 +40,7 @@ import { KeysOfRustEnum, MessageResult, UpdateMessageContents, -} from "./shared_types.mjs"; +} from "./shared.mjs"; import { console_log, on_error, @@ -207,6 +207,7 @@ class WebSocketComm { contents, current_update.is_re_translation, current_update.cursor_position, + current_update.scroll_position, ); } else { // If the page is still loading, wait until the @@ -285,8 +286,7 @@ class WebSocketComm { // `pending_messages`. const pending_message = this.pending_messages[id]; if (pending_message !== undefined) { - const { timer_id, callback } = - this.pending_messages[id]; + const { timer_id, callback } = pending_message; clearTimeout(timer_id); callback(); delete this.pending_messages[id]; @@ -523,7 +523,7 @@ export const format_struct = (complex_data_structure: any): string => /*eslint-disable-next-line @typescript-eslint/no-explicit-any */ const report_error = (text: string, ...objs: any) => { console.error(text); - if (objs !== undefined) { + if (objs.length > 0) { console.log(...objs); } show_toast(text); diff --git a/client/src/CodeMirror-integration.mts b/client/src/CodeMirror-integration.mts index cebefbb9..fa076ab6 100644 --- a/client/src/CodeMirror-integration.mts +++ b/client/src/CodeMirror-integration.mts @@ -82,6 +82,12 @@ import { python } from "@codemirror/lang-python"; import { rust } from "@codemirror/lang-rust"; import { sql } from "@codemirror/lang-sql"; import { yaml } from "@codemirror/lang-yaml"; +import { StreamLanguage } from "@codemirror/language"; +import { shell } from "@codemirror/legacy-modes/mode/shell"; +import { swift } from "@codemirror/legacy-modes/mode/swift"; +import { toml } from "@codemirror/legacy-modes/mode/toml"; +import { verilog } from "@codemirror/legacy-modes/mode/verilog"; +import { vhdl } from "@codemirror/legacy-modes/mode/vhdl"; import { tinymce, init } from "./tinymce-config.mjs"; import { Editor, EditorEvent, Events } from "tinymce"; @@ -99,7 +105,7 @@ import { CodeMirrorDocBlockTuple, StringDiff, UpdateMessageContents, -} from "./shared_types.mjs"; +} from "./shared.mjs"; import { assert } from "./assert.mjs"; import { show_toast } from "./show_toast.mjs"; @@ -179,7 +185,7 @@ export const docBlockField = StateField.define({ for (const effect of tr.effects) if (effect.is(addDocBlock)) { // Check that we're not overwriting text. - const newlines = current_view.state.doc + const newlines = tr.newDoc .slice(effect.value.from, effect.value.to) .toString(); if (newlines !== "\n".repeat(newlines.length)) { @@ -202,7 +208,7 @@ export const docBlockField = StateField.define({ widget: new DocBlockWidget( effect.value.indent, effect.value.delimiter, - effect.value.content, + effect.value.contents, false, ), ...decorationOptions, @@ -262,9 +268,7 @@ export const docBlockField = StateField.define({ to = effect.value.to ?? to; const from = effect.value.from_new ?? effect.value.from; // Check that we're not overwriting text. - const newlines = current_view.state.doc - .slice(from, to) - .toString(); + const newlines = tr.newDoc.slice(from, to).toString(); if (newlines !== "\n".repeat(newlines.length)) { report_error(`Attempt to overwrite text: "${newlines}".`); window.close(); @@ -309,11 +313,12 @@ export const docBlockField = StateField.define({ return doc_blocks; }, - // [Provide](https://codemirror.net/docs/ref/#state.StateField^define^config.provide) - // extensions based on this field. See also + // Register this `DecorationSet` as a source of decorations for the editor + // view — without it, the `StateField` holds the data but nothing tells + // CodeMirror to render the doc block widgets. See + // [Provide](https://codemirror.net/docs/ref/#state.StateField^define^config.provide), // [EditorView.decorations](https://codemirror.net/docs/ref/#view.EditorView^decorations) - // and [from](https://codemirror.net/docs/ref/#state.Facet.from). TODO: I - // don't understand what this does, but removing it breaks the extension. + // and [from](https://codemirror.net/docs/ref/#state.Facet.from). provide: (field: StateField) => EditorView.decorations.from(field), @@ -323,12 +328,18 @@ 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 = []; + const json_result = []; for (const iter = value.iter(); iter.value !== null; iter.next()) { const w = iter.value.spec.widget; - json.push([iter.from, iter.to, w.indent, w.delimiter, w.contents]); + json_result.push([ + iter.from, + iter.to, + w.indent, + w.delimiter, + w.contents, + ]); } - return json; + return json_result; }, // For loading a file from the server back into the editor, use @@ -368,9 +379,9 @@ export const addDocBlock = StateEffect.define<{ to: number; indent: string; delimiter: string; - content: string; + contents: string; }>({ - map: ({ from, to, indent, delimiter, content }, change: ChangeDesc) => ({ + map: ({ from, to, indent, delimiter, contents }, change: ChangeDesc) => ({ // Update the location (from/to) of this effect due to the transaction's // changes. See this // [thread](https://discuss.codemirror.net/t/mapping-ranges-in-a-decoration/9307/3). @@ -378,7 +389,7 @@ export const addDocBlock = StateEffect.define<{ to: change.mapPos(to), indent, delimiter, - content, + contents, }), }); @@ -436,8 +447,9 @@ class DocBlockWidget extends WidgetType { readonly contents: string, readonly is_user_change: boolean, ) { - // TODO: I don't understand why I don't need to store the provided - // parameters in the object: `this.indent = indent;`, etc. + // [Typescript parameter properties](https://www.typescriptlang.org/docs/handbook/2/classes.html#parameter-properties) + // means these parameters are automatically promoted to class + // properties. super(); } @@ -459,9 +471,9 @@ class DocBlockWidget extends WidgetType { // pasting whitespace. `

${this.indent}
` + + )}>${sanitize_html(this.indent)}` + // The contents of this doc block. - `
` + + `
` + this.contents + "
"; // TODO: this is an async call. However, CodeMirror doesn't provide @@ -480,12 +492,14 @@ class DocBlockWidget extends WidgetType { if (this.is_user_change) { return true; } - (dom.childNodes[0] as HTMLDivElement).innerHTML = this.indent; + (dom.childNodes[0] as HTMLDivElement).innerHTML = sanitize_html( + this.indent, + ); // The contents div could be a TinyMCE instance, or just a plain div. // Handle both cases. const [contents_div, is_tinymce] = get_contents(dom); - window.MathJax.typesetClear(contents_div); + window.MathJax.typesetClear([contents_div]); if (is_tinymce) { // Save the cursor location before the update, then restore it // afterwards, if TinyMCE has focus. @@ -532,6 +546,13 @@ class DocBlockWidget extends WidgetType { } } +// Native DOM sanitizer. +const sanitize_html = (html: string) => { + const div = document.createElement("div"); + div.textContent = html; + return div.innerHTML; +}; + // Typeset the provided node; taken from the // [MathJax docs](https://docs.mathjax.org/en/latest/web/typeset.html#handling-asynchronous-typesetting). export const mathJaxTypeset = async ( @@ -597,6 +618,11 @@ const element_is_in_doc_block = ( // lead to nasty bugs at some point. // 4. When an HTML doc block is assigned to the TinyMCE instance for editing, // the dirty flag is set. This must be ignored. +// +// Potential bug: race condition. If one doc block is modified and schedules +// 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 = ( // The div that's dirty. It must be a child of the doc block div. event_target: HTMLElement, @@ -648,7 +674,7 @@ const on_dirty = ( }); }; -// Handle cursur movement and mouse selection in a doc block. +// Handle cursor movement and mouse selection in a doc block. export const DocBlockPlugin = ViewPlugin.fromClass( class { constructor(_view: EditorView) {} @@ -767,8 +793,8 @@ export const DocBlockPlugin = ViewPlugin.fromClass( setTimeout(async () => { // Before untypesetting, make sure all other typesets // finish. - await new Promise((resolve) => - window.MathJax.whenReady(resolve(undefined)), + await new Promise((resolve) => + window.MathJax.whenReady(() => resolve()), ); // Untypeset math in the old doc block and the current // doc block before moving its contents around. @@ -786,7 +812,7 @@ export const DocBlockPlugin = ViewPlugin.fromClass( // // Copy the current TinyMCE instance contents into a // contenteditable div. - const old_contents_div = document.createElement("div")!; + const old_contents_div = document.createElement("div"); old_contents_div.className = "CodeChat-doc-contents"; old_contents_div.contentEditable = "true"; old_contents_div.innerHTML = @@ -865,7 +891,7 @@ const autosaveExtension = EditorView.updateListener.of( let isChanged = v.docChanged; // Look for changes to doc blocks as well; skip if a change was already // detected for efficiency. - if (!v.docChanged && v.transactions?.length) { + if (!v.docChanged && v.transactions.length) { // Check each effect of each transaction. outer: for (const tr of v.transactions) { for (const effect of tr.effects) { @@ -926,9 +952,9 @@ export const CodeMirror_load = async ( let parser; // TODO: dynamically load the parser. switch (codechat_for_web.metadata.mode) { - // Languages with a parser + // Languages with a parser. case "sh": - parser = cpp(); + parser = StreamLanguage.define(shell); break; case "cpp": parser = cpp(); @@ -960,32 +986,36 @@ export const CodeMirror_load = async ( case "sql": parser = sql(); break; + case "swift": + parser = StreamLanguage.define(swift); + break; + case "toml": + parser = StreamLanguage.define(toml); + break; case "typescript": parser = javascript({ typescript: true }); break; + case "vhdl": + parser = StreamLanguage.define(vhdl); + break; + case "verilog": + parser = StreamLanguage.define(verilog); + break; case "yaml": parser = yaml(); break; // Languages without a parser. + // + // JSON5 allows comments, but JSON doesn't. case "json5": parser = json(); break; + // Nothing available. Python isn't even close. case "matlab": parser = python(); break; - case "swift": - parser = python(); - break; - case "toml": - parser = json(); - break; - case "vhdl": - parser = cpp(); - break; - case "verilog": - parser = cpp(); - break; + // An approximation for Vlang. case "v": parser = javascript(); break; @@ -1088,7 +1118,7 @@ export const CodeMirror_load = async ( to: add[1], indent: add[2], delimiter: add[3], - content: add[4], + contents: add[4], }), ); } else if ("Update" in transaction) { diff --git a/client/src/HashReader.mts b/client/src/HashReader.mts index 514dc3bf..7d78cdd1 100644 --- a/client/src/HashReader.mts +++ b/client/src/HashReader.mts @@ -15,7 +15,7 @@ // [http://www.gnu.org/licenses](http://www.gnu.org/licenses). // // `HashReader.mts` -- post-process esbuild output -// ============================================================================= +// =============================================== // // This script reads the output produced by esbuild to determine the location of // the bundled files, which have hashes in their file names. It writes these @@ -59,7 +59,8 @@ interface Metafile { }; } -// Load the esbuild metafile. +// Load the esbuild metafile. This must always be run from the `client/` +// directory. const data = await fs.readFile("meta.json", { encoding: "utf8" }); // Interpret it as JSON. @@ -68,36 +69,37 @@ const metafile: Metafile = JSON.parse(data); // Walk the file, looking for the names of specific entry points. Transform // those into paths used to import these files. const outputContents: Record = {}; -let num_found = 0; +let numFound = 0; for (const output in metafile.outputs) { const outputInfo = metafile.outputs[output]; switch (outputInfo.entryPoint) { case "src/CodeChatEditorFramework.mts": outputContents["CodeChatEditorFramework.js"] = output; - ++num_found; + ++numFound; break; case "src/CodeChatEditor.mts": outputContents["CodeChatEditor.js"] = output; outputContents["CodeChatEditor.css"] = outputInfo.cssBundle!; - ++num_found; + ++numFound; break; case "src/CodeChatEditor-test.mts": outputContents["CodeChatEditor-test.js"] = output; outputContents["CodeChatEditor-test.css"] = outputInfo.cssBundle!; - ++num_found; + ++numFound; break; case "src/css/CodeChatEditorProject.css": outputContents["CodeChatEditorProject.css"] = output; - ++num_found; + ++numFound; break; } } -console.assert(num_found === 4); - +if (numFound !== 4) { + throw new Error(`Expected 4 entry points, found ${numFound}`); +} // Write this to disk. await fs.writeFile( "../server/hashLocations.json", diff --git a/client/src/assert.mts b/client/src/assert.mts index e3b74a4e..f594c166 100644 --- a/client/src/assert.mts +++ b/client/src/assert.mts @@ -15,7 +15,7 @@ // [http://www.gnu.org/licenses](http://www.gnu.org/licenses). // // `assert.mts` -// ============================================================================= +// ============ // // Provide a simple `assert` function to check conditions at runtime. Using // things like [assert](https://nodejs.org/api/assert.html) causes problems -- diff --git a/client/src/css/CodeChatEditor.css b/client/src/css/CodeChatEditor.css index 842c1fd4..ae923d14 100644 --- a/client/src/css/CodeChatEditor.css +++ b/client/src/css/CodeChatEditor.css @@ -17,7 +17,7 @@ [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/). `CodeChatEditor.css` -- Styles for the CodeChat Editor - ============================================================================= + ====================================================== This style sheet is used by the HTML generated by [CodeChatEditor.mts](../CodeChatEditor.mts). @@ -26,13 +26,13 @@ whether they style a code or doc block. Import a theme - ----------------------------------------------------------------------------- + -------------- Eventually, this will be a user-configurable setting. */ @import url("themes/light.css"); /* Styles for the entire page layout - ----------------------------------------------------------------------------- + --------------------------------- This is used only to store a reused variable value. See the [CSS docs](https://drafts.csswg.org/css-variables/). */ @@ -73,7 +73,7 @@ body { } } /* Misc styling - ----------------------------------------------------------------------------- + ------------ Make the filename compact. */ #CodeChat-filename p { @@ -82,7 +82,7 @@ body { } /* Doc block styling - ----------------------------------------------------------------------------- */ + ----------------- */ .CodeChat-doc { /* Use [flexbox layout](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox) @@ -130,7 +130,7 @@ body { } /* Combined code/doc block styling - ----------------------------------------------------------------------------- + ------------------------------- Remove space between a code block followed by a doc block. Doc block elements typically have top margin and/or padding that diff --git a/client/src/css/CodeChatEditorProject.css b/client/src/css/CodeChatEditorProject.css index f3cb0df8..c42d02b7 100644 --- a/client/src/css/CodeChatEditorProject.css +++ b/client/src/css/CodeChatEditorProject.css @@ -17,7 +17,7 @@ [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/). `CodeChatEditorProject.css` -- Styles for the CodeChat Editor for projects - ============================================================================= + ========================================================================== This is used only to store a reused variable value. See the [CSS docs](https://drafts.csswg.org/css-variables/). */ diff --git a/client/src/css/themes/light.css b/client/src/css/themes/light.css index 12e0575d..4e9ddc5c 100644 --- a/client/src/css/themes/light.css +++ b/client/src/css/themes/light.css @@ -17,7 +17,7 @@ [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/). `light.css` -- Styles for the light theme - ============================================================================= + ========================================= Use [CSS nesting](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting/Using_CSS_nesting) diff --git a/client/src/global.d.ts b/client/src/global.d.ts index b9f7d190..0dbec999 100644 --- a/client/src/global.d.ts +++ b/client/src/global.d.ts @@ -15,9 +15,10 @@ // [http://www.gnu.org/licenses](http://www.gnu.org/licenses). // // `global.d.ts` -- Global type definitions -// ============================================================================= +// ======================================== // -// Copied from the [TypeScript Docs](https://www.typescriptlang.org/tsconfig/#noUncheckedSideEffectImports): +// Copied from the +// [TypeScript Docs](https://www.typescriptlang.org/tsconfig/#noUncheckedSideEffectImports): // // Recognize all CSS files as module imports. declare module "*.css" {} diff --git a/client/src/graphviz-webcomponent-setup.mjs b/client/src/graphviz-webcomponent-setup.mjs index b763c6a5..ca8876a5 100644 --- a/client/src/graphviz-webcomponent-setup.mjs +++ b/client/src/graphviz-webcomponent-setup.mjs @@ -15,7 +15,7 @@ // [http://www.gnu.org/licenses](http://www.gnu.org/licenses). // // `graphviz-webcomponent-setup.mts` -- Configure graphviz webcomponent options -// ============================================================================= +// ============================================================================ // // Configure the Graphviz web component to load the (large) renderer only when a // Graphviz web component is found on a page. See the diff --git a/client/src/shared_types.mts b/client/src/shared.mts similarity index 89% rename from client/src/shared_types.mts rename to client/src/shared.mts index a30afb4e..334fc344 100644 --- a/client/src/shared_types.mts +++ b/client/src/shared.mts @@ -14,19 +14,19 @@ // the CodeChat Editor. If not, see // [http://www.gnu.org/licenses](http://www.gnu.org/licenses). // -// `shared_types.mts` -- Shared type definitions -// ============================================================================= +// `shared.mts` -- Shared definitions +// ============================================= // // The time, in ms, to wait between the last user edit and sending updated data // to the Server. export const autosave_timeout_ms = 300; // Produce a whole random number. Fractional numbers aren't consistently -// converted to the same number. Note that the mantissa of a JavaScript `Number` +// converted to the same number across JavaScript and Rust. Note that the mantissa of a JavaScript `Number` // is 53 bits per the // [docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number#number_encoding). -// To be certain, also round the result. -export const rand = () => Math.round(Math.random() * 2 ** 53); +// To be certain, also floor the result. +export const rand = () => Math.floor(Math.random() * 2 ** 53); // ### Message types // diff --git a/client/src/show_toast.mts b/client/src/show_toast.mts index 461a4d46..e1b48e0c 100644 --- a/client/src/show_toast.mts +++ b/client/src/show_toast.mts @@ -25,6 +25,7 @@ export const show_toast = (text: string) => duration: 10000, newWindow: true, close: true, + escapeMarkup: true, gravity: "top", position: "right", stopOnFocus: true, diff --git a/client/src/third-party/wc-mermaid/developer.md b/client/src/third-party/wc-mermaid/developer.md index b25ea38e..638c6099 100644 --- a/client/src/third-party/wc-mermaid/developer.md +++ b/client/src/third-party/wc-mermaid/developer.md @@ -3,5 +3,5 @@ Mermaid support This file is based on [wc-mermaid](https://github.com/manolakis/wc-mermaid). The code here was modified to allow a dynamic import of Mermaid and updated to -support modern Mermaid. This software is licensed separately under the [Mermaid -license](LICENSE.md). \ No newline at end of file +support modern Mermaid. This software is licensed separately under the +[Mermaid license](LICENSE.md). diff --git a/client/src/tinymce-config.mts b/client/src/tinymce-config.mts index 4672316f..d6c7a89b 100644 --- a/client/src/tinymce-config.mts +++ b/client/src/tinymce-config.mts @@ -16,7 +16,7 @@ // // `tinymce-config.ts` -- integrate and configure the TinyMCE editor for use // with the CodeChat Editor -// ============================================================================= +// ========================================================================= // // Import TinyMCE. import { diff --git a/client/tsconfig.json b/client/tsconfig.json index 5542e6af..520b0f2b 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -15,7 +15,7 @@ // [http://www.gnu.org/licenses](http://www.gnu.org/licenses). // // tsconfig.json -- TypeScript configuration -// ============================================================================= +// ========================================= { "compilerOptions": { "allowJs": true, @@ -33,7 +33,9 @@ "outDir": "out", "rootDir": "./src", "strict": true, - // Starting in TypeScript 6.0, no ambient type packages are auto-included. Add these explicitly. See the [TypeScript 5.x to 6.0 Migration Guide](https://gist.github.com/privatenumber/3d2e80da28f84ee30b77d53e1693378f#16-types-defaults-to-). + // Starting in TypeScript 6.0, no ambient type packages are + // auto-included. Add these explicitly. See the + // [TypeScript 5.x to 6.0 Migration Guide](https://gist.github.com/privatenumber/3d2e80da28f84ee30b77d53e1693378f#16-types-defaults-to-). "types": ["node", "mocha"] }, "exclude": ["node_modules", "static", "HashReader.js", "eslint.config.js"] diff --git a/docs/design.md b/docs/design.md index 798f2453..173ec9f4 100644 --- a/docs/design.md +++ b/docs/design.md @@ -1,8 +1,8 @@ CodeChat Editor design -================================================================================ +====================== To build from source --------------------------------------------------------------------------------- +-------------------- 1. Clone or download the repository. 2. [Install the Rust language](https://www.rust-lang.org/tools/install). I @@ -18,7 +18,7 @@ Use `./bt` tool's options update all libraries (`update`), run all tests (`test`), and more. Vision --------------------------------------------------------------------------------- +------------------------- These form a set of high-level requirements to guide the project. @@ -136,7 +136,7 @@ These form a set of high-level requirements to guide the project. * An API-only view (Doxygen/Javadoc like feature). Requirements --------------------------------------------------------------------------------- +-------------------------------------- The requirements expand on the vision by providing additional details. @@ -204,7 +204,6 @@ void foo(); ### \[Programming language support\](index.md#programming-language-support) - Initial targets come from the Stack Overflow Developer Survey 2022's section on [programming, scripting, and markup languages](https://survey.stackoverflow.co/2022/#section-most-popular-technologies-programming-scripting-and-markup-languages) and IEEE Spectrum's @@ -256,7 +255,7 @@ When a new tag is inserted, any tag-produced content should be immediately added. License --------------------------------------------------------------------------------- +------- Copyright (C) 2025 Bryan A. Jones. diff --git a/docs/implementation.md b/docs/implementation.md index 8323b354..445d7776 100644 --- a/docs/implementation.md +++ b/docs/implementation.md @@ -17,7 +17,7 @@ CodeChat Editor. If not, see [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/). Implementation -================================================================================ +============== ### System architecture @@ -65,7 +65,7 @@ a diagram as an overview might be helpful. Perhaps the server, client, etc. should have its of readme files providing some of this. Architecture --------------------------------------------------------------------------------- +------------------------------------------ Overall, the code is something like this: @@ -317,7 +317,7 @@ More complex IDE integration: everything that the simple IDE does, plus the ability to toggle between the IDE's editor and the CodeChat Editor. Build system --------------------------------------------------------------------------------- +------------ The app needs build support because of complexity: @@ -329,7 +329,7 @@ So, this project contains Rust code to automate this process -- see the [builder](../builder/Cargo.toml). Misc topics --------------------------------------------------------------------------------- +----------- ### CodeChat Editor Client Viewer Types @@ -359,7 +359,7 @@ closing tags are removed from the HTML. This is fixed by later HTML processing steps (currently, by TinyMCE), which properly closes tags. Future work --------------------------------------------------------------------------------- +----------- ### Table of contents @@ -422,7 +422,7 @@ with descriptions of each setting. * Substitutions Core development priorities --------------------------------------------------------------------------------- +------------------------------------------------------------------ 1. Bug fixes 2. Book support @@ -462,7 +462,7 @@ with descriptions of each setting. somewhat, since it wants to decode JSON into a V struct.) Organization --------------------------------------------------------------------------------- +------------ ### Client @@ -508,7 +508,7 @@ TODO: GUIs using TinyMCE. See the [how-to guide](https://www.tiny.cloud/docs/tinymce/6/dialog-components/#panel-components). Code style --------------------------------------------------------------------------------- +---------- JavaScript functions are a [disaster](https://dmitripavlutin.com/differences-between-arrow-and-regular-functions/). @@ -518,7 +518,7 @@ Other than that, follow the [MDN style guide](https://developer.mozilla.org/en-US/docs/MDN/Writing_guidelines/Writing_style_guide/Code_style_guide/JavaScript). Client modes --------------------------------------------------------------------------------- +------------ The CodeChat Editor client supports four modes: @@ -535,7 +535,7 @@ The CodeChat Editor client supports four modes: area; otherwise, it's only the main area. See: \. Misc --------------------------------------------------------------------------------- +---- Eventually, provide a read-only mode with possible auth (restrict who can view) using JWTs; see diff --git a/docs/style_guide.cpp b/docs/style_guide.cpp index 86e0912d..a62b16ca 100644 --- a/docs/style_guide.cpp +++ b/docs/style_guide.cpp @@ -1,5 +1,5 @@ // `style_guide.cpp` - Literate programming using the CodeChat Editor -// ============================================================================= +// ================================================================== // // This document, written as a C++ source file, primarily demonstrates the use // of the CodeChat Editor in literate programming. It should be viewed using the @@ -22,7 +22,7 @@ // [http://www.gnu.org/licenses](http://www.gnu.org/licenses). // // Introduction -// ----------------------------------------------------------------------------- +// ------------ // // This document provides a style guide for literate programming using the // CodeChat Editor. For basic use, see the [user manual](../README.md). @@ -47,7 +47,7 @@ const char* CODE_BLOCK = // [brief overview of Markdown](https://commonmark.org/help/). // // Approach -// ----------------------------------------------------------------------------- +// -------- // // Viewing a program as a document defines the heart of the literate programming // paradigm. A program/document -- constructed as a series of code blocks and @@ -74,7 +74,7 @@ const char* CODE_BLOCK = // person to review what you wrote, then implement their ideas and suggestions. // // Organization -// ----------------------------------------------------------------------------- +// ------------------------------------- // // The program should use headings to appropriately organize the contents. Near // the top of the file, include a single level-1 heading, providing the title of @@ -91,7 +91,7 @@ const char* CODE_BLOCK = // style. // // Location -// ----------------------------------------------------------------------------- +// -------- // // In general, place documentation before the corresponding code. For example: // @@ -111,7 +111,7 @@ class LedBlinker { }; // Use of mathematics -// ----------------------------------------------------------------------------- +// ------------------ // // Formulas should be placed near code that implements them, along with good // explanations of the equations used. For example: @@ -158,7 +158,7 @@ double accurate_g( } // Excellence in code -// ----------------------------------------------------------------------------- +// ------------------ // // Literate programming should be accompanied by excellence in authoring code. // Specifically: @@ -178,7 +178,7 @@ double accurate_g( // [test-driven development](https://en.wikipedia.org/wiki/Test-driven_development). // // Editor configuration -// ----------------------------------------------------------------------------- +// -------------------- // // Properly configuring the text editor used with the CodeChat Editor // significantly improves the authoring process. Recommended settings: @@ -197,7 +197,7 @@ double accurate_g( // * On a big monitor, place your IDE side by side with the CodeChat Editor. // // Common problems -// ----------------------------------------------------------------------------- +// --------------- // // * Don't drag and drop an image into the Editor – this creates a mess. // Instead, save all images to a file, then use an SVG or PNG image for @@ -230,13 +230,13 @@ double accurate_g( // C/C++. // // Example structure -// ----------------------------------------------------------------------------- +// ----------------- // // As discussed in [organization](#organization), the remainder of this document // presents the preferred use of headings to organize source code. // // Includes -// ----------------------------------------------------------------------------- +// ------------------------------ // // Include files (in Python, imports; Rust, use statements; JavaScript, // require/import, etc.) should be organized by category; for example, @@ -256,23 +256,23 @@ double accurate_g( #include // Global variables/constants -// ----------------------------------------------------------------------------- +// -------------------------- // // Use units when describing physical quantities. For example, this gives the // acceleration due to gravity in $m/s^2$. const double accel_m_s2 = 9.8067; // Macros -// ----------------------------------------------------------------------------- +// ------ #define LED1 (LATB16) // Structures/classes -// ----------------------------------------------------------------------------- +// ------------------ class BlinkLed { }; // Code -// ----------------------------------------------------------------------------- +// ---- int main(int argc, char* argv[]) { // Here's an example of commenting code out when using the CodeChat Editor: /** diff --git a/extensions/VSCode/.vscodeignore b/extensions/VSCode/.vscodeignore index 477eb8ad..cd2f89a4 100644 --- a/extensions/VSCode/.vscodeignore +++ b/extensions/VSCode/.vscodeignore @@ -19,8 +19,8 @@ # `vscodeignore` - Files not to package # ===================================== # -# See [Using -# .vscodeignore](https://code.visualstudio.com/api/working-with-extensions/publishing-extension#using-.vscodeignore). +# See +# [Using .vscodeignore](https://code.visualstudio.com/api/working-with-extensions/publishing-extension#using-.vscodeignore). # # Omit all TypeScript source. src/** diff --git a/extensions/VSCode/Cargo.lock b/extensions/VSCode/Cargo.lock index 262f4529..e304636a 100644 --- a/extensions/VSCode/Cargo.lock +++ b/extensions/VSCode/Cargo.lock @@ -8,7 +8,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "bytes", "futures-core", "futures-sink", @@ -29,7 +29,7 @@ dependencies = [ "actix-service", "actix-utils", "actix-web", - "bitflags 2.11.0", + "bitflags 2.11.1", "bytes", "derive_more", "futures-core", @@ -53,7 +53,7 @@ dependencies = [ "actix-service", "actix-utils", "base64", - "bitflags 2.11.0", + "bitflags 2.11.1", "brotli", "bytes", "bytestring", @@ -72,7 +72,7 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rand 0.9.2", + "rand 0.9.4", "sha1", "smallvec", "tokio", @@ -406,9 +406,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "block-buffer" @@ -494,9 +494,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.59" +version = "1.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" dependencies = [ "find-msvc-tools", "jobserver", @@ -518,7 +518,7 @@ checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "rand_core 0.10.0", + "rand_core 0.10.1", ] [[package]] @@ -582,7 +582,7 @@ checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746" [[package]] name = "codechat-editor-server" -version = "0.1.52" +version = "0.1.53" dependencies = [ "actix-files", "actix-http", @@ -601,6 +601,7 @@ dependencies = [ "futures-util", "htmd", "html5ever", + "htmlize", "imara-diff", "indoc", "lazy_static", @@ -617,7 +618,7 @@ dependencies = [ "pest_derive", "phf", "pulldown-cmark 0.13.3", - "rand 0.10.0", + "rand 0.10.1", "regex", "serde", "serde_json", @@ -634,7 +635,7 @@ dependencies = [ [[package]] name = "codechat-editor-vscode-extension" -version = "0.1.52" +version = "0.1.53" dependencies = [ "codechat-editor-server", "log", @@ -1161,7 +1162,7 @@ dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", - "rand_core 0.10.0", + "rand_core 0.10.1", "wasip2", "wasip3", ] @@ -1185,7 +1186,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "ignore", "walkdir", ] @@ -1222,9 +1223,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" [[package]] name = "heck" @@ -1261,6 +1262,16 @@ dependencies = [ "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" @@ -1472,12 +1483,12 @@ checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" [[package]] name = "indexmap" -version = "2.13.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "serde", "serde_core", ] @@ -1497,7 +1508,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd5b3eaf1a28b758ac0faa5a4254e8ab2705605496f1b1f3fbbc3988ad73d199" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "inotify-sys", "libc", ] @@ -1584,9 +1595,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.94" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" dependencies = [ "once_cell", "wasm-bindgen", @@ -1632,9 +1643,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.184" +version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" [[package]] name = "libloading" @@ -1648,9 +1659,9 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" dependencies = [ "libc", ] @@ -1725,7 +1736,7 @@ dependencies = [ "log-mdc", "mock_instant", "parking_lot", - "rand 0.9.2", + "rand 0.9.4", "serde", "serde-value", "serde_json", @@ -1832,7 +1843,7 @@ version = "3.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb7848c221fb7bb789e02f01875287ebb1e078b92a6566a34de01ef8806e7c2b" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "ctor", "futures", "napi-build", @@ -1914,7 +1925,7 @@ version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "fsevent-sys", "inotify", "kqueue", @@ -1945,7 +1956,7 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] @@ -1978,7 +1989,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] @@ -1993,7 +2004,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "objc2", ] @@ -2050,6 +2061,12 @@ dependencies = [ "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" @@ -2166,9 +2183,9 @@ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "postgres-protocol" @@ -2183,7 +2200,7 @@ dependencies = [ "hmac", "md-5", "memchr", - "rand 0.10.0", + "rand 0.10.1", "sha2 0.11.0", "stringprep", ] @@ -2282,7 +2299,7 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "679341d22c78c6c649893cbd6c3278dcbe9fc4faa62fea3a9296ae2b50c14625" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "memchr", "unicase", ] @@ -2293,7 +2310,7 @@ version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c3a14896dfa883796f1cb410461aef38810ea05f2b2c33c5aded3649095fdad" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "memchr", "pulldown-cmark-escape", "unicase", @@ -2328,9 +2345,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha", "rand_core 0.9.5", @@ -2338,13 +2355,13 @@ dependencies = [ [[package]] name = "rand" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ "chacha20", "getrandom 0.4.2", - "rand_core 0.10.0", + "rand_core 0.10.1", ] [[package]] @@ -2368,9 +2385,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] name = "redox_syscall" @@ -2378,7 +2395,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] @@ -2437,7 +2454,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "errno", "libc", "linux-raw-sys", @@ -2925,7 +2942,7 @@ dependencies = [ "pin-project-lite", "postgres-protocol", "postgres-types", - "rand 0.10.0", + "rand 0.10.1", "socket2 0.6.3", "tokio", "tokio-util", @@ -3189,9 +3206,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.117" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" dependencies = [ "cfg-if", "once_cell", @@ -3202,9 +3219,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.117" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3212,9 +3229,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.117" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" dependencies = [ "bumpalo", "proc-macro2", @@ -3225,9 +3242,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.117" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" dependencies = [ "unicode-ident", ] @@ -3260,7 +3277,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "hashbrown 0.15.5", "indexmap", "semver", @@ -3268,9 +3285,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.94" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" +checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" dependencies = [ "js-sys", "wasm-bindgen", @@ -3672,7 +3689,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.11.0", + "bitflags 2.11.1", "indexmap", "log", "serde", diff --git a/extensions/VSCode/Cargo.toml b/extensions/VSCode/Cargo.toml index c3adc2d5..aa38c4d9 100644 --- a/extensions/VSCode/Cargo.toml +++ b/extensions/VSCode/Cargo.toml @@ -17,10 +17,10 @@ # [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/). # # `Cargo.toml` -- Rust interface for the VSCode extension -# ============================================================================== +# ======================================================= # # General package configurations -# ------------------------------------------------------------------------------ +# ------------------------------ [package] authors = ["Bryan A. Jones", "Peter Loux"] categories = ["development-tools", "text-editors"] @@ -32,7 +32,7 @@ license = "GPL-3.0-only" name = "codechat-editor-vscode-extension" readme = "../README.md" repository = "https://github.com/bjones1/CodeChat_Editor" -version = "0.1.52" +version = "0.1.53" [lib] crate-type = ["cdylib"] diff --git a/extensions/VSCode/README.md b/extensions/VSCode/README.md index 91af7fb9..195ac000 100644 --- a/extensions/VSCode/README.md +++ b/extensions/VSCode/README.md @@ -1,5 +1,5 @@ The CodeChat Editor extension for Visual Studio Code -================================================================================ +==================================================== This extension provides the CodeChat Editor's capabilities within the Visual Studio Code IDE. @@ -7,7 +7,7 @@ Studio Code IDE. ![Screenshot of the CodeChat Editor extension](https://github.com/bjones1/CodeChat_Editor/blob/main/extensions/VSCode/screenshot.png?raw=true) Installation --------------------------------------------------------------------------------- +------------ First, install [Visual Studio Code](https://code.visualstudio.com/). Next: @@ -17,7 +17,7 @@ First, install [Visual Studio Code](https://code.visualstudio.com/). Next: since the CodeChat Editor only provides a light theme. Running --------------------------------------------------------------------------------- +------- 1. Open a file that the CodeChat Editor [supports](https://github.com/bjones1/CodeChat_Editor/blob/main/README.md#supported-languages) @@ -36,7 +36,7 @@ Running then select Enable the CodeChat Editor). Additional documentation --------------------------------------------------------------------------------- +------------------------ See the [user manual](https://codechat-editor.onrender.com/fw/fsb/opt/render/project/src/README.md). diff --git a/extensions/VSCode/developer.md b/extensions/VSCode/developer.md index f0412d46..3eb95ead 100644 --- a/extensions/VSCode/developer.md +++ b/extensions/VSCode/developer.md @@ -6,44 +6,44 @@ From source To install from source: -* Install [npm](https://nodejs.org/en/). +* Install [npm](https://nodejs.org/en/). -* Install this extension's manifest - ([package.json](https://code.visualstudio.com/api/references/extension-manifest)): - from this directory, open a command prompt/terminal then execute:: +* Install this extension's manifest + ([package.json](https://code.visualstudio.com/api/references/extension-manifest)): + from this directory, open a command prompt/terminal then execute:: - ``` - npm install - ``` + ``` + npm install + ``` Debugging the extension ----------------------- -* From VSCode, select File | Add Folder to Workspace... then choose the folder - containing this file. -* Press ctrl+shift+B to compile the extension. -* Press F5 or click start debugging under the Debug menu. -* A new instance of VSCode will start in a special mode (Extension Development - Host) which contains the CodeChat extension. -* Open any source code, then press Ctrl+Shift+P and type "CodeChat" to run the - CodeChat extension. You will be able to see the rendered version of your - active window. +* From VSCode, select File | Add Folder to Workspace... then choose the folder + containing this file. +* Press ctrl+shift+B to compile the extension. +* Press F5 or click start debugging under the Debug menu. +* A new instance of VSCode will start in a special mode (Extension Development + Host) which contains the CodeChat extension. +* Open any source code, then press Ctrl+Shift+P and type "CodeChat" to run the + CodeChat extension. You will be able to see the rendered version of your + active window. Release procedure ----------------- -* In the Client: - * Update the version of the plugin in `package.json`. -* In the Server: - * Update the version in `cargo.toml`. -* Here: - * Update the version of the plugin in `package.json`. - * Run `cargo run -- release` on each platform, which produces a `.vsix` - file for that platform - * Run `npx vsce publish --packagePath blah`. - ([docs](https://code.visualstudio.com/api/working-with-extensions/publishing-extension#platformspecific-extensions)) +* In the Client: + * Update the version of the plugin in `package.json`. +* In the Server: + * Update the version in `cargo.toml`. +* Here: + * Update the version of the plugin in `package.json`. + * Run `cargo run -- release` on each platform, which produces a `.vsix` file + for that platform + * Run `npx vsce publish --packagePath blah`. + ([docs](https://code.visualstudio.com/api/working-with-extensions/publishing-extension#platformspecific-extensions)) Tests ----- -TODO: tests are missing. \ No newline at end of file +TODO: tests are missing. diff --git a/extensions/VSCode/eslint.config.js b/extensions/VSCode/eslint.config.js index c0936860..a59cc0c5 100644 --- a/extensions/VSCode/eslint.config.js +++ b/extensions/VSCode/eslint.config.js @@ -2,8 +2,8 @@ // // 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 +// 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. // @@ -29,7 +29,9 @@ module.exports = defineConfig( eslintPluginPrettierRecommended, defineConfig([ { - // This must be the only key in this dict to be treated as a global ignore. Only global ignores can ignore directories. See the [docs](https://eslint.org/docs/latest/use/configure/configuration-files#globally-ignoring-files-with-ignores). + // This must be the only key in this dict to be treated as a global + // ignore. Only global ignores can ignore directories. See the + // [docs](https://eslint.org/docs/latest/use/configure/configuration-files#globally-ignoring-files-with-ignores). ignores: ["src/third-party/**"], }, { diff --git a/extensions/VSCode/package.json b/extensions/VSCode/package.json index 58658b47..78393ebb 100644 --- a/extensions/VSCode/package.json +++ b/extensions/VSCode/package.json @@ -41,7 +41,7 @@ "type": "git", "url": "https://github.com/bjones1/CodeChat_Editor" }, - "version": "0.1.52", + "version": "0.1.53", "activationEvents": [ "onCommand:extension.codeChatEditorActivate", "onCommand:extension.codeChatEditorDeactivate" @@ -88,8 +88,8 @@ "@types/escape-html": "^1.0.4", "@types/node": "^24.12.2", "@types/vscode": "1.61.0", - "@typescript-eslint/eslint-plugin": "^8.58.1", - "@typescript-eslint/parser": "^8.58.1", + "@typescript-eslint/eslint-plugin": "^8.58.2", + "@typescript-eslint/parser": "^8.58.2", "@vscode/vsce": "^3.7.1", "chalk": "^5.6.2", "esbuild": "^0.28.0", @@ -99,10 +99,10 @@ "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^5.5.5", "npm-run-all2": "^8.0.4", - "ovsx": "^0.10.10", - "prettier": "^3.8.1", + "ovsx": "^0.10.11", + "prettier": "^3.8.2", "typescript": "^6.0.2", - "typescript-eslint": "^8.58.1" + "typescript-eslint": "^8.58.2" }, "optionalDependencies": { "bufferutil": "^4.1.0" diff --git a/extensions/VSCode/pnpm-lock.yaml b/extensions/VSCode/pnpm-lock.yaml index 30ab0f53..3c5284fa 100644 --- a/extensions/VSCode/pnpm-lock.yaml +++ b/extensions/VSCode/pnpm-lock.yaml @@ -37,11 +37,11 @@ importers: specifier: 1.61.0 version: 1.61.0 '@typescript-eslint/eslint-plugin': - specifier: ^8.58.1 - version: 8.58.1(@typescript-eslint/parser@8.58.1(eslint@10.2.0)(typescript@6.0.2))(eslint@10.2.0)(typescript@6.0.2) + specifier: ^8.58.2 + version: 8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.0)(typescript@6.0.2))(eslint@10.2.0)(typescript@6.0.2) '@typescript-eslint/parser': - specifier: ^8.58.1 - version: 8.58.1(eslint@10.2.0)(typescript@6.0.2) + specifier: ^8.58.2 + version: 8.58.2(eslint@10.2.0)(typescript@6.0.2) '@vscode/vsce': specifier: ^3.7.1 version: 3.7.1 @@ -59,28 +59,28 @@ importers: version: 10.1.8(eslint@10.2.0) eslint-plugin-import: specifier: ^2.32.0 - version: 2.32.0(@typescript-eslint/parser@8.58.1(eslint@10.2.0)(typescript@6.0.2))(eslint@10.2.0) + version: 2.32.0(@typescript-eslint/parser@8.58.2(eslint@10.2.0)(typescript@6.0.2))(eslint@10.2.0) eslint-plugin-node: specifier: ^11.1.0 version: 11.1.0(eslint@10.2.0) eslint-plugin-prettier: specifier: ^5.5.5 - version: 5.5.5(eslint-config-prettier@10.1.8(eslint@10.2.0))(eslint@10.2.0)(prettier@3.8.1) + version: 5.5.5(eslint-config-prettier@10.1.8(eslint@10.2.0))(eslint@10.2.0)(prettier@3.8.2) npm-run-all2: specifier: ^8.0.4 version: 8.0.4 ovsx: - specifier: ^0.10.10 - version: 0.10.10 + specifier: ^0.10.11 + version: 0.10.11 prettier: - specifier: ^3.8.1 - version: 3.8.1 + specifier: ^3.8.2 + version: 3.8.2 typescript: specifier: ^6.0.2 version: 6.0.2 typescript-eslint: - specifier: ^8.58.1 - version: 8.58.1(eslint@10.2.0)(typescript@6.0.2) + specifier: ^8.58.2 + version: 8.58.2(eslint@10.2.0)(typescript@6.0.2) optionalDependencies: bufferutil: specifier: ^4.1.0 @@ -1116,63 +1116,63 @@ packages: '@types/vscode@1.61.0': resolution: {integrity: sha512-9k5Nwq45hkRwdfCFY+eKXeQQSbPoA114mF7U/4uJXRBJeGIO7MuJdhF1PnaDN+lllL9iKGQtd6FFXShBXMNaFg==} - '@typescript-eslint/eslint-plugin@8.58.1': - resolution: {integrity: sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ==} + '@typescript-eslint/eslint-plugin@8.58.2': + resolution: {integrity: sha512-aC2qc5thQahutKjP+cl8cgN9DWe3ZUqVko30CMSZHnFEHyhOYoZSzkGtAI2mcwZ38xeImDucI4dnqsHiOYuuCw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.58.1 + '@typescript-eslint/parser': ^8.58.2 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.58.1': - resolution: {integrity: sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw==} + '@typescript-eslint/parser@8.58.2': + resolution: {integrity: sha512-/Zb/xaIDfxeJnvishjGdcR4jmr7S+bda8PKNhRGdljDM+elXhlvN0FyPSsMnLmJUrVG9aPO6dof80wjMawsASg==} 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.58.1': - resolution: {integrity: sha512-gfQ8fk6cxhtptek+/8ZIqw8YrRW5048Gug8Ts5IYcMLCw18iUgrZAEY/D7s4hkI0FxEfGakKuPK/XUMPzPxi5g==} + '@typescript-eslint/project-service@8.58.2': + resolution: {integrity: sha512-Cq6UfpZZk15+r87BkIh5rDpi38W4b+Sjnb8wQCPPDDweS/LRCFjCyViEbzHk5Ck3f2QDfgmlxqSa7S7clDtlfg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.58.1': - resolution: {integrity: sha512-TPYUEqJK6avLcEjumWsIuTpuYODTTDAtoMdt8ZZa93uWMTX13Nb8L5leSje1NluammvU+oI3QRr5lLXPgihX3w==} + '@typescript-eslint/scope-manager@8.58.2': + resolution: {integrity: sha512-SgmyvDPexWETQek+qzZnrG6844IaO02UVyOLhI4wpo82dpZJY9+6YZCKAMFzXb7qhx37mFK1QcPQ18tud+vo6Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.58.1': - resolution: {integrity: sha512-JAr2hOIct2Q+qk3G+8YFfqkqi7sC86uNryT+2i5HzMa2MPjw4qNFvtjnw1IiA1rP7QhNKVe21mSSLaSjwA1Olw==} + '@typescript-eslint/tsconfig-utils@8.58.2': + resolution: {integrity: sha512-3SR+RukipDvkkKp/d0jP0dyzuls3DbGmwDpVEc5wqk5f38KFThakqAAO0XMirWAE+kT00oTauTbzMFGPoAzB0A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.58.1': - resolution: {integrity: sha512-HUFxvTJVroT+0rXVJC7eD5zol6ID+Sn5npVPWoFuHGg9Ncq5Q4EYstqR+UOqaNRFXi5TYkpXXkLhoCHe3G0+7w==} + '@typescript-eslint/type-utils@8.58.2': + resolution: {integrity: sha512-Z7EloNR/B389FvabdGeTo2XMs4W9TjtPiO9DAsmT0yom0bwlPyRjkJ1uCdW1DvrrrYP50AJZ9Xc3sByZA9+dcg==} 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.58.1': - resolution: {integrity: sha512-io/dV5Aw5ezwzfPBBWLoT+5QfVtP8O7q4Kftjn5azJ88bYyp/ZMCsyW1lpKK46EXJcaYMZ1JtYj+s/7TdzmQMw==} + '@typescript-eslint/types@8.58.2': + resolution: {integrity: sha512-9TukXyATBQf/Jq9AMQXfvurk+G5R2MwfqQGDR2GzGz28HvY/lXNKGhkY+6IOubwcquikWk5cjlgPvD2uAA7htQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.58.1': - resolution: {integrity: sha512-w4w7WR7GHOjqqPnvAYbazq+Y5oS68b9CzasGtnd6jIeOIeKUzYzupGTB2T4LTPSv4d+WPeccbxuneTFHYgAAWg==} + '@typescript-eslint/typescript-estree@8.58.2': + resolution: {integrity: sha512-ELGuoofuhhoCvNbQjFFiobFcGgcDCEm0ThWdmO4Z0UzLqPXS3KFvnEZ+SHewwOYHjM09tkzOWXNTv9u6Gqtyuw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.58.1': - resolution: {integrity: sha512-Ln8R0tmWC7pTtLOzgJzYTXSCjJ9rDNHAqTaVONF4FEi2qwce8mD9iSOxOpLFFvWp/wBFlew0mjM1L1ihYWfBdQ==} + '@typescript-eslint/utils@8.58.2': + resolution: {integrity: sha512-QZfjHNEzPY8+l0+fIXMvuQ2sJlplB4zgDZvA+NmvZsZv3EQwOcc1DuIU1VJUTWZ/RKouBMhDyNaBMx4sWvrzRA==} 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.58.1': - resolution: {integrity: sha512-y+vH7QE8ycjoa0bWciFg7OpFcipUuem1ujhrdLtq1gByKwfbC7bPeKsiny9e0urg93DqwGcHey+bGRKCnF1nZQ==} + '@typescript-eslint/visitor-keys@8.58.2': + resolution: {integrity: sha512-f1WO2Lx8a9t8DARmcWAUPJbu0G20bJlj8L4z72K00TMeJAoyLr/tHhI/pzYBLrR4dXWkcxO1cWYZEOX8DKHTqA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@typespec/ts-http-runtime@0.3.5': @@ -1343,8 +1343,8 @@ packages: boundary@2.0.0: resolution: {integrity: sha512-rJKn5ooC9u8q13IMCrW0RSp31pxBCHE3y9V/tp3TdWSLf8Em3p6Di4NBpfzbJge9YjjFEsD0RtFEjtvHL5VyEA==} - brace-expansion@1.1.13: - resolution: {integrity: sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==} + brace-expansion@1.1.14: + resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} brace-expansion@5.0.5: resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} @@ -1375,8 +1375,8 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} - call-bind@1.0.8: - resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} engines: {node: '>= 0.4'} call-bound@1.0.4: @@ -1813,8 +1813,8 @@ packages: flatted@3.4.2: resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} - follow-redirects@1.15.11: - resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + follow-redirects@1.16.0: + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} engines: {node: '>=4.0'} peerDependencies: debug: '*' @@ -2228,8 +2228,8 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.3.3: - resolution: {integrity: sha512-JvNw9Y81y33E+BEYPr0U7omo+U9AySnsMsEiXgwT6yqd31VQWTLNQqmT4ou5eqPFUrTfIDFta2wKhB1hyohtAQ==} + lru-cache@11.3.5: + resolution: {integrity: sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==} engines: {node: 20 || >=22} lru-cache@6.0.0: @@ -2386,8 +2386,8 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} - ovsx@0.10.10: - resolution: {integrity: sha512-/X5J4VLKPUGGaMynW9hgvsGg9jmwsK/3RhODeA2yzdeDbb8PUSNcg5GQ9aPDJW/znlqNvAwQcXAyE+Cq0RRvAQ==} + ovsx@0.10.11: + resolution: {integrity: sha512-VYYlAWO7hvcrP0EIM/Q5lCdXTumXIiGyDnSXm8ztNbGN9zCSNW6iGiJMMMUNhPRWqJjNKpo/Vc2+B4uFRy8ACg==} engines: {node: '>= 20'} hasBin: true @@ -2489,8 +2489,8 @@ packages: resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==} engines: {node: '>=6.0.0'} - prettier@3.8.1: - resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} + prettier@3.8.2: + resolution: {integrity: sha512-8c3mgTe0ASwWAJK+78dpviD+A8EqhndQPUBpNUIPt6+xWlIigCwfN01lWr9MAede4uqXGTEKeQWTvzb3vjia0Q==} engines: {node: '>=14'} hasBin: true @@ -2551,8 +2551,8 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - resolve@1.22.11: - resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} engines: {node: '>= 0.4'} hasBin: true @@ -2827,8 +2827,8 @@ packages: typed-rest-client@1.8.11: resolution: {integrity: sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==} - typescript-eslint@8.58.1: - resolution: {integrity: sha512-gf6/oHChByg9HJvhMO1iBexJh12AqqTfnuxscMDOVqfJW3htsdRJI/GfPpHTTcyeB8cSTUY2JcZmVgoyPqcrDg==} + typescript-eslint@8.58.2: + resolution: {integrity: sha512-V8iSng9mRbdZjl54VJ9NKr6ZB+dW0J3TzRXRGcSbLIej9jV86ZRtlYeTKDR/QLxXykocJ5icNzbsl2+5TzIvcQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -2852,8 +2852,8 @@ packages: undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} - undici@7.24.7: - resolution: {integrity: sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==} + undici@7.25.0: + resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==} engines: {node: '>=20.18.1'} unicorn-magic@0.1.0: @@ -3860,14 +3860,14 @@ snapshots: '@types/vscode@1.61.0': {} - '@typescript-eslint/eslint-plugin@8.58.1(@typescript-eslint/parser@8.58.1(eslint@10.2.0)(typescript@6.0.2))(eslint@10.2.0)(typescript@6.0.2)': + '@typescript-eslint/eslint-plugin@8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.0)(typescript@6.0.2))(eslint@10.2.0)(typescript@6.0.2)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.58.1(eslint@10.2.0)(typescript@6.0.2) - '@typescript-eslint/scope-manager': 8.58.1 - '@typescript-eslint/type-utils': 8.58.1(eslint@10.2.0)(typescript@6.0.2) - '@typescript-eslint/utils': 8.58.1(eslint@10.2.0)(typescript@6.0.2) - '@typescript-eslint/visitor-keys': 8.58.1 + '@typescript-eslint/parser': 8.58.2(eslint@10.2.0)(typescript@6.0.2) + '@typescript-eslint/scope-manager': 8.58.2 + '@typescript-eslint/type-utils': 8.58.2(eslint@10.2.0)(typescript@6.0.2) + '@typescript-eslint/utils': 8.58.2(eslint@10.2.0)(typescript@6.0.2) + '@typescript-eslint/visitor-keys': 8.58.2 eslint: 10.2.0 ignore: 7.0.5 natural-compare: 1.4.0 @@ -3876,41 +3876,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.58.1(eslint@10.2.0)(typescript@6.0.2)': + '@typescript-eslint/parser@8.58.2(eslint@10.2.0)(typescript@6.0.2)': dependencies: - '@typescript-eslint/scope-manager': 8.58.1 - '@typescript-eslint/types': 8.58.1 - '@typescript-eslint/typescript-estree': 8.58.1(typescript@6.0.2) - '@typescript-eslint/visitor-keys': 8.58.1 + '@typescript-eslint/scope-manager': 8.58.2 + '@typescript-eslint/types': 8.58.2 + '@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.2) + '@typescript-eslint/visitor-keys': 8.58.2 debug: 4.4.3 eslint: 10.2.0 typescript: 6.0.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.58.1(typescript@6.0.2)': + '@typescript-eslint/project-service@8.58.2(typescript@6.0.2)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.58.1(typescript@6.0.2) - '@typescript-eslint/types': 8.58.1 + '@typescript-eslint/tsconfig-utils': 8.58.2(typescript@6.0.2) + '@typescript-eslint/types': 8.58.2 debug: 4.4.3 typescript: 6.0.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.58.1': + '@typescript-eslint/scope-manager@8.58.2': dependencies: - '@typescript-eslint/types': 8.58.1 - '@typescript-eslint/visitor-keys': 8.58.1 + '@typescript-eslint/types': 8.58.2 + '@typescript-eslint/visitor-keys': 8.58.2 - '@typescript-eslint/tsconfig-utils@8.58.1(typescript@6.0.2)': + '@typescript-eslint/tsconfig-utils@8.58.2(typescript@6.0.2)': dependencies: typescript: 6.0.2 - '@typescript-eslint/type-utils@8.58.1(eslint@10.2.0)(typescript@6.0.2)': + '@typescript-eslint/type-utils@8.58.2(eslint@10.2.0)(typescript@6.0.2)': dependencies: - '@typescript-eslint/types': 8.58.1 - '@typescript-eslint/typescript-estree': 8.58.1(typescript@6.0.2) - '@typescript-eslint/utils': 8.58.1(eslint@10.2.0)(typescript@6.0.2) + '@typescript-eslint/types': 8.58.2 + '@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.2) + '@typescript-eslint/utils': 8.58.2(eslint@10.2.0)(typescript@6.0.2) debug: 4.4.3 eslint: 10.2.0 ts-api-utils: 2.5.0(typescript@6.0.2) @@ -3918,14 +3918,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.58.1': {} + '@typescript-eslint/types@8.58.2': {} - '@typescript-eslint/typescript-estree@8.58.1(typescript@6.0.2)': + '@typescript-eslint/typescript-estree@8.58.2(typescript@6.0.2)': dependencies: - '@typescript-eslint/project-service': 8.58.1(typescript@6.0.2) - '@typescript-eslint/tsconfig-utils': 8.58.1(typescript@6.0.2) - '@typescript-eslint/types': 8.58.1 - '@typescript-eslint/visitor-keys': 8.58.1 + '@typescript-eslint/project-service': 8.58.2(typescript@6.0.2) + '@typescript-eslint/tsconfig-utils': 8.58.2(typescript@6.0.2) + '@typescript-eslint/types': 8.58.2 + '@typescript-eslint/visitor-keys': 8.58.2 debug: 4.4.3 minimatch: 10.2.5 semver: 7.7.4 @@ -3935,20 +3935,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.58.1(eslint@10.2.0)(typescript@6.0.2)': + '@typescript-eslint/utils@8.58.2(eslint@10.2.0)(typescript@6.0.2)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0) - '@typescript-eslint/scope-manager': 8.58.1 - '@typescript-eslint/types': 8.58.1 - '@typescript-eslint/typescript-estree': 8.58.1(typescript@6.0.2) + '@typescript-eslint/scope-manager': 8.58.2 + '@typescript-eslint/types': 8.58.2 + '@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.2) eslint: 10.2.0 typescript: 6.0.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.58.1': + '@typescript-eslint/visitor-keys@8.58.2': dependencies: - '@typescript-eslint/types': 8.58.1 + '@typescript-eslint/types': 8.58.2 eslint-visitor-keys: 5.0.1 '@typespec/ts-http-runtime@0.3.5': @@ -4079,7 +4079,7 @@ snapshots: array-includes@3.1.9: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 es-abstract: 1.24.2 @@ -4090,7 +4090,7 @@ snapshots: array.prototype.findlastindex@1.2.6: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 es-abstract: 1.24.2 @@ -4100,14 +4100,14 @@ snapshots: array.prototype.flat@1.3.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 es-abstract: 1.24.2 es-shim-unscopables: 1.1.0 array.prototype.flatmap@1.3.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 es-abstract: 1.24.2 es-shim-unscopables: 1.1.0 @@ -4115,7 +4115,7 @@ snapshots: arraybuffer.prototype.slice@1.0.4: dependencies: array-buffer-byte-length: 1.0.2 - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 es-abstract: 1.24.2 es-errors: 1.3.0 @@ -4161,7 +4161,7 @@ snapshots: boundary@2.0.0: {} - brace-expansion@1.1.13: + brace-expansion@1.1.14: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 @@ -4198,7 +4198,7 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 - call-bind@1.0.8: + call-bind@1.0.9: dependencies: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 @@ -4239,7 +4239,7 @@ snapshots: parse5: 7.3.0 parse5-htmlparser2-tree-adapter: 7.1.0 parse5-parser-stream: 7.1.2 - undici: 7.24.7 + undici: 7.25.0 whatwg-mimetype: 4.0.0 chownr@1.1.4: @@ -4414,7 +4414,7 @@ snapshots: array-buffer-byte-length: 1.0.2 arraybuffer.prototype.slice: 1.0.4 available-typed-arrays: 1.0.7 - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 data-view-buffer: 1.0.2 data-view-byte-length: 1.0.2 @@ -4538,11 +4538,11 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.58.1(eslint@10.2.0)(typescript@6.0.2))(eslint-import-resolver-node@0.3.10)(eslint@10.2.0): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.58.2(eslint@10.2.0)(typescript@6.0.2))(eslint-import-resolver-node@0.3.10)(eslint@10.2.0): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.58.1(eslint@10.2.0)(typescript@6.0.2) + '@typescript-eslint/parser': 8.58.2(eslint@10.2.0)(typescript@6.0.2) eslint: 10.2.0 eslint-import-resolver-node: 0.3.10 transitivePeerDependencies: @@ -4554,7 +4554,7 @@ snapshots: eslint-utils: 2.1.0 regexpp: 3.2.0 - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.1(eslint@10.2.0)(typescript@6.0.2))(eslint@10.2.0): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.2(eslint@10.2.0)(typescript@6.0.2))(eslint@10.2.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -4565,7 +4565,7 @@ snapshots: doctrine: 2.1.0 eslint: 10.2.0 eslint-import-resolver-node: 0.3.10 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.58.1(eslint@10.2.0)(typescript@6.0.2))(eslint-import-resolver-node@0.3.10)(eslint@10.2.0) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.58.2(eslint@10.2.0)(typescript@6.0.2))(eslint-import-resolver-node@0.3.10)(eslint@10.2.0) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -4577,7 +4577,7 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.58.1(eslint@10.2.0)(typescript@6.0.2) + '@typescript-eslint/parser': 8.58.2(eslint@10.2.0)(typescript@6.0.2) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -4590,13 +4590,13 @@ snapshots: eslint-utils: 2.1.0 ignore: 5.3.2 minimatch: 3.1.5 - resolve: 1.22.11 + resolve: 1.22.12 semver: 6.3.1 - eslint-plugin-prettier@5.5.5(eslint-config-prettier@10.1.8(eslint@10.2.0))(eslint@10.2.0)(prettier@3.8.1): + eslint-plugin-prettier@5.5.5(eslint-config-prettier@10.1.8(eslint@10.2.0))(eslint@10.2.0)(prettier@3.8.2): dependencies: eslint: 10.2.0 - prettier: 3.8.1 + prettier: 3.8.2 prettier-linter-helpers: 1.0.1 synckit: 0.11.12 optionalDependencies: @@ -4737,7 +4737,7 @@ snapshots: flatted@3.4.2: {} - follow-redirects@1.15.11: {} + follow-redirects@1.16.0: {} for-each@0.3.5: dependencies: @@ -4769,7 +4769,7 @@ snapshots: function.prototype.name@1.1.8: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 functions-have-names: 1.2.3 @@ -4926,7 +4926,7 @@ snapshots: is-array-buffer@3.0.5: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 get-intrinsic: 1.3.0 @@ -5169,7 +5169,7 @@ snapshots: lru-cache@10.4.3: {} - lru-cache@11.3.3: {} + lru-cache@11.3.5: {} lru-cache@6.0.0: dependencies: @@ -5214,7 +5214,7 @@ snapshots: minimatch@3.1.5: dependencies: - brace-expansion: 1.1.13 + brace-expansion: 1.1.14 minimist@1.2.8: {} @@ -5286,7 +5286,7 @@ snapshots: object.assign@4.1.7: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 @@ -5295,27 +5295,27 @@ snapshots: object.entries@1.1.9: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 object.fromentries@2.0.8: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 es-abstract: 1.24.2 es-object-atoms: 1.1.1 object.groupby@1.0.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 es-abstract: 1.24.2 object.values@1.2.1: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 @@ -5343,11 +5343,11 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 - ovsx@0.10.10: + ovsx@0.10.11: dependencies: '@vscode/vsce': 3.7.1 commander: 6.2.1 - follow-redirects: 1.15.11 + follow-redirects: 1.16.0 is-ci: 2.0.0 leven: 3.1.0 semver: 7.7.4 @@ -5406,7 +5406,7 @@ snapshots: path-scurry@2.0.2: dependencies: - lru-cache: 11.3.3 + lru-cache: 11.3.5 minipass: 7.1.3 path-type@6.0.0: {} @@ -5449,7 +5449,7 @@ snapshots: dependencies: fast-diff: 1.3.0 - prettier@3.8.1: {} + prettier@3.8.2: {} pump@3.0.4: dependencies: @@ -5510,7 +5510,7 @@ snapshots: reflect.getprototypeof@1.0.10: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 es-abstract: 1.24.2 es-errors: 1.3.0 @@ -5521,7 +5521,7 @@ snapshots: regexp.prototype.flags@1.5.4: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 es-errors: 1.3.0 get-proto: 1.0.1 @@ -5532,8 +5532,9 @@ snapshots: require-from-string@2.0.2: {} - resolve@1.22.11: + resolve@1.22.12: dependencies: + es-errors: 1.3.0 is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -5557,7 +5558,7 @@ snapshots: safe-array-concat@1.1.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 get-intrinsic: 1.3.0 has-symbols: 1.1.0 @@ -5705,7 +5706,7 @@ snapshots: string.prototype.trim@1.2.10: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-data-property: 1.1.4 define-properties: 1.2.1 @@ -5715,14 +5716,14 @@ snapshots: string.prototype.trimend@1.0.9: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 string.prototype.trimstart@1.0.8: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 es-object-atoms: 1.1.1 @@ -5846,7 +5847,7 @@ snapshots: typed-array-byte-length@1.0.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 has-proto: 1.2.0 @@ -5855,7 +5856,7 @@ snapshots: typed-array-byte-offset@1.0.4: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.8 + call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 has-proto: 1.2.0 @@ -5864,7 +5865,7 @@ snapshots: typed-array-length@1.0.7: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 is-typed-array: 1.1.15 @@ -5877,12 +5878,12 @@ snapshots: tunnel: 0.0.6 underscore: 1.13.8 - typescript-eslint@8.58.1(eslint@10.2.0)(typescript@6.0.2): + typescript-eslint@8.58.2(eslint@10.2.0)(typescript@6.0.2): dependencies: - '@typescript-eslint/eslint-plugin': 8.58.1(@typescript-eslint/parser@8.58.1(eslint@10.2.0)(typescript@6.0.2))(eslint@10.2.0)(typescript@6.0.2) - '@typescript-eslint/parser': 8.58.1(eslint@10.2.0)(typescript@6.0.2) - '@typescript-eslint/typescript-estree': 8.58.1(typescript@6.0.2) - '@typescript-eslint/utils': 8.58.1(eslint@10.2.0)(typescript@6.0.2) + '@typescript-eslint/eslint-plugin': 8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.0)(typescript@6.0.2))(eslint@10.2.0)(typescript@6.0.2) + '@typescript-eslint/parser': 8.58.2(eslint@10.2.0)(typescript@6.0.2) + '@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.2) + '@typescript-eslint/utils': 8.58.2(eslint@10.2.0)(typescript@6.0.2) eslint: 10.2.0 typescript: 6.0.2 transitivePeerDependencies: @@ -5903,7 +5904,7 @@ snapshots: undici-types@7.16.0: {} - undici@7.24.7: {} + undici@7.25.0: {} unicorn-magic@0.1.0: {} @@ -5971,7 +5972,7 @@ snapshots: which-typed-array@1.1.20: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 for-each: 0.3.5 get-proto: 1.0.1 diff --git a/extensions/VSCode/src/extension.ts b/extensions/VSCode/src/extension.ts index 1f2cc70a..808de9b3 100644 --- a/extensions/VSCode/src/extension.ts +++ b/extensions/VSCode/src/extension.ts @@ -46,7 +46,7 @@ import { MessageResult, rand, UpdateMessageContents, -} from "../../../client/src/shared_types.mjs"; +} from "../../../client/src/shared.mjs"; import { DEBUG_ENABLED, MAX_MESSAGE_LENGTH, @@ -417,7 +417,9 @@ export const activate = (context: vscode.ExtensionContext) => { const editor = get_text_editor(doc); const scroll_line = current_update.scroll_position; if (scroll_line !== undefined && editor) { - ignore_selection_change = true; + // Don't set `ignore_scroll_position` here, + // since `revealRange` doesn't change the + // editor's text selection. const scroll_position = new vscode.Position( // The VSCode line is zero-based; the // CodeMirror line is one-based. @@ -450,6 +452,7 @@ export const activate = (context: vscode.ExtensionContext) => { cursor_position, ), ]; + ignore_selection_change = false; } await sendResult(id); break; @@ -522,9 +525,7 @@ export const activate = (context: vscode.ExtensionContext) => { // Report if this was an error. const result_contents = value as MessageResult; if ("Err" in result_contents) { - const err = result_contents[ - "Err" - ] as ResultErrTypes; + const err = result_contents["Err"]; if ( err instanceof Object && "OutOfSync" in err @@ -535,11 +536,10 @@ export const activate = (context: vscode.ExtensionContext) => { ); send_update(true); } else { - // If the client is out of sync, re-sync it. - if (result_contents) - show_error( - `Error in message ${id}: ${JSON.stringify(err)}`, - ); + // Report the error. + show_error( + `Error in message ${id}: ${JSON.stringify(err)}`, + ); } } break; @@ -662,7 +662,7 @@ const send_update = (this_is_dirty: boolean) => { // the user to rapidly cycle through several editors without // needing to reload the Client with each cycle. current_editor = ate; - const current_file = ate!.document.fileName; + const current_file = ate.document.fileName; console_log( `CodeChat Editor extension: sending CurrentFile(${current_file}}).`, ); diff --git a/extensions/VSCode/src/lib.rs b/extensions/VSCode/src/lib.rs index bef47679..ceec586a 100644 --- a/extensions/VSCode/src/lib.rs +++ b/extensions/VSCode/src/lib.rs @@ -15,10 +15,10 @@ // [http://www.gnu.org/licenses](http://www.gnu.org/licenses). // // `lib.rs` -- Interface to the CodeChat Editor for VSCode -// ============================================================================= +// ======================================================= // // Imports -// ----------------------------------------------------------------------------- +// ------- // // ### Standard library use std::path::PathBuf; @@ -32,7 +32,7 @@ use napi_derive::napi; use code_chat_editor::{ide, webserver}; // Code -// ----------------------------------------------------------------------------- +// ---- #[napi] pub fn init_server(extension_base_path: String) -> Result<(), Error> { webserver::init_server( @@ -89,7 +89,8 @@ impl CodeChatEditorServer { pub async fn send_message_update_plain( &self, file_path: String, - // `null` to send no source code; a string to send the source code. + // `null` to send no source code; a `(string, version)` to send the + // source code. option_contents: Option<(String, f64)>, cursor_position: Option, scroll_position: Option, diff --git a/extensions/VSCode/tsconfig.json b/extensions/VSCode/tsconfig.json index 20c3fe7c..ddf87b24 100644 --- a/extensions/VSCode/tsconfig.json +++ b/extensions/VSCode/tsconfig.json @@ -25,7 +25,9 @@ "strict": true, "rootDirs": ["src", "../../client/src/*"], "rootDir": "../..", - // Starting in TypeScript 6.0, no ambient type packages are auto-included. Add these explicitly. See the [TypeScript 5.x to 6.0 Migration Guide](https://gist.github.com/privatenumber/3d2e80da28f84ee30b77d53e1693378f#16-types-defaults-to-). + // Starting in TypeScript 6.0, no ambient type packages are + // auto-included. Add these explicitly. See the + // [TypeScript 5.x to 6.0 Migration Guide](https://gist.github.com/privatenumber/3d2e80da28f84ee30b77d53e1693378f#16-types-defaults-to-). "types": ["node"], "allowJs": true }, diff --git a/extensions/readme.md b/extensions/readme.md index 43f52547..4bdf0a79 100644 --- a/extensions/readme.md +++ b/extensions/readme.md @@ -1,5 +1,5 @@ `readme.py` - Overview of extensions -================================================================================ +==================================== The goal of the CodeChat Editor is to provide extensions for a number of IDEs and environments. To support this, an explicit design goal of the Editor is to diff --git a/server/.cargo/config.toml b/server/.cargo/config.toml index c5dd86a9..62c59ce9 100644 --- a/server/.cargo/config.toml +++ b/server/.cargo/config.toml @@ -1,11 +1,11 @@ # `config` - a Cargo configuration file -# ============================================================================== +# ===================================== # # See the [docs](https://doc.rust-lang.org/cargo/reference/config.html) for this # file. # # ts\_rs config -# ------------------------------------------------------------------------------ +# ------------- # # Configure the directory where `ts-rs` places the generated files per the docs. # See also the diff --git a/server/Cargo.lock b/server/Cargo.lock index 30a5f5fb..66b558fb 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -8,7 +8,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "bytes", "futures-core", "futures-sink", @@ -29,7 +29,7 @@ dependencies = [ "actix-service", "actix-utils", "actix-web", - "bitflags 2.11.0", + "bitflags 2.11.1", "bytes", "derive_more", "futures-core", @@ -53,7 +53,7 @@ dependencies = [ "actix-service", "actix-utils", "base64", - "bitflags 2.11.0", + "bitflags 2.11.1", "brotli", "bytes", "bytestring", @@ -72,7 +72,7 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rand 0.9.2", + "rand 0.9.4", "sha1", "smallvec", "tokio", @@ -546,9 +546,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "block-buffer" @@ -664,9 +664,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.59" +version = "1.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" dependencies = [ "find-msvc-tools", "jobserver", @@ -711,7 +711,7 @@ checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "rand_core 0.10.0", + "rand_core 0.10.1", ] [[package]] @@ -785,7 +785,7 @@ checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746" [[package]] name = "codechat-editor-server" -version = "0.1.52" +version = "0.1.53" dependencies = [ "actix-files", "actix-http", @@ -808,6 +808,7 @@ dependencies = [ "futures-util", "htmd", "html5ever", + "htmlize", "imara-diff", "indoc", "lazy_static", @@ -826,7 +827,7 @@ dependencies = [ "predicates", "pretty_assertions", "pulldown-cmark 0.13.3", - "rand 0.10.0", + "rand 0.10.1", "regex", "serde", "serde_json", @@ -1215,7 +1216,7 @@ dependencies = [ "anyhow", "bumpalo", "hashbrown 0.15.5", - "indexmap 2.13.1", + "indexmap 2.14.0", "rustc-hash", "serde", "unicode-width", @@ -1554,7 +1555,7 @@ dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", - "rand_core 0.10.0", + "rand_core 0.10.1", "wasip2", "wasip3", ] @@ -1578,7 +1579,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "ignore", "walkdir", ] @@ -1595,7 +1596,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.13.1", + "indexmap 2.14.0", "slab", "tokio", "tokio-util", @@ -1621,9 +1622,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" [[package]] name = "heck" @@ -1666,6 +1667,16 @@ dependencies = [ "markup5ever", ] +[[package]] +name = "htmlize" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e815d50d9e411ba2690d730e6ec139c08260dddb756df315dbd16d01a587226" +dependencies = [ + "memchr", + "pastey 0.1.1", +] + [[package]] name = "http" version = "0.2.12" @@ -1765,15 +1776,14 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.7" +version = "0.27.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "c2b52f86d1d4bc0d6b4e6826d960b1b333217e07d36b882dca570a5e1c48895b" dependencies = [ "http 1.4.0", "hyper", "hyper-util", "rustls", - "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", @@ -1980,12 +1990,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "serde", "serde_core", ] @@ -2023,7 +2033,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd5b3eaf1a28b758ac0faa5a4254e8ab2705605496f1b1f3fbbc3988ad73d199" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "inotify-sys", "libc", ] @@ -2184,9 +2194,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.94" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" dependencies = [ "cfg-if", "futures-util", @@ -2240,20 +2250,20 @@ checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" [[package]] name = "libc" -version = "0.2.184" +version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" [[package]] name = "libredox" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "libc", "plain", - "redox_syscall 0.7.3", + "redox_syscall 0.7.4", ] [[package]] @@ -2326,7 +2336,7 @@ dependencies = [ "log-mdc", "mock_instant", "parking_lot", - "rand 0.9.2", + "rand 0.9.4", "serde", "serde-value", "serde_json", @@ -2487,7 +2497,7 @@ version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "fsevent-sys", "inotify", "kqueue", @@ -2518,7 +2528,7 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] @@ -2561,7 +2571,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] @@ -2576,7 +2586,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "objc2", ] @@ -2645,6 +2655,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + [[package]] name = "pastey" version = "0.2.1" @@ -2777,9 +2793,9 @@ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "plain" @@ -2815,7 +2831,7 @@ dependencies = [ "hmac", "md-5 0.11.0", "memchr", - "rand 0.10.0", + "rand 0.10.1", "sha2 0.11.0", "stringprep", ] @@ -2927,7 +2943,7 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "679341d22c78c6c649893cbd6c3278dcbe9fc4faa62fea3a9296ae2b50c14625" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "memchr", "unicase", ] @@ -2938,7 +2954,7 @@ version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c3a14896dfa883796f1cb410461aef38810ea05f2b2c33c5aded3649095fdad" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "memchr", "pulldown-cmark-escape", "unicase", @@ -2980,7 +2996,7 @@ dependencies = [ "bytes", "getrandom 0.3.4", "lru-slab", - "rand 0.9.2", + "rand 0.9.4", "ring", "rustc-hash", "rustls", @@ -3040,9 +3056,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -3050,13 +3066,13 @@ dependencies = [ [[package]] name = "rand" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ "chacha20", "getrandom 0.4.2", - "rand_core 0.10.0", + "rand_core 0.10.1", ] [[package]] @@ -3099,9 +3115,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] name = "redox_syscall" @@ -3109,16 +3125,16 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] name = "redox_syscall" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" +checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] @@ -3279,7 +3295,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "errno", "libc", "linux-raw-sys", @@ -3288,9 +3304,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.37" +version = "0.23.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" dependencies = [ "aws-lc-rs", "once_cell", @@ -3352,9 +3368,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.10" +version = "0.103.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +checksum = "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06" dependencies = [ "aws-lc-rs", "ring", @@ -3424,7 +3440,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation", "core-foundation-sys", "libc", @@ -3539,7 +3555,7 @@ version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ - "indexmap 2.13.1", + "indexmap 2.14.0", "itoa", "memchr", "serde", @@ -3585,7 +3601,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.13.1", + "indexmap 2.14.0", "itoa", "ryu", "serde", @@ -3919,9 +3935,9 @@ dependencies = [ "const_format", "futures-util", "http 1.4.0", - "indexmap 2.13.1", + "indexmap 2.14.0", "libc", - "pastey", + "pastey 0.2.1", "reqwest 0.13.2", "selenium-manager", "serde", @@ -4098,7 +4114,7 @@ dependencies = [ "pin-project-lite", "postgres-protocol", "postgres-types", - "rand 0.10.0", + "rand 0.10.1", "socket2 0.6.3", "tokio", "tokio-util", @@ -4147,7 +4163,7 @@ version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ - "indexmap 2.13.1", + "indexmap 2.14.0", "serde_core", "serde_spanned", "toml_datetime", @@ -4201,7 +4217,7 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "bytes", "futures-util", "http 1.4.0", @@ -4296,7 +4312,7 @@ dependencies = [ "http 1.4.0", "httparse", "log", - "rand 0.9.2", + "rand 0.9.4", "sha1", "thiserror 2.0.18", ] @@ -4524,9 +4540,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.117" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" dependencies = [ "cfg-if", "once_cell", @@ -4537,9 +4553,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.67" +version = "0.4.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" +checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" dependencies = [ "js-sys", "wasm-bindgen", @@ -4547,9 +4563,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.117" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4557,9 +4573,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.117" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" dependencies = [ "bumpalo", "proc-macro2", @@ -4570,9 +4586,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.117" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" dependencies = [ "unicode-ident", ] @@ -4594,7 +4610,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap 2.13.1", + "indexmap 2.14.0", "wasm-encoder", "wasmparser", ] @@ -4605,17 +4621,17 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "hashbrown 0.15.5", - "indexmap 2.13.1", + "indexmap 2.14.0", "semver", ] [[package]] name = "web-sys" -version = "0.3.94" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" +checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" dependencies = [ "js-sys", "wasm-bindgen", @@ -5102,7 +5118,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck", - "indexmap 2.13.1", + "indexmap 2.14.0", "prettyplease", "syn 2.0.117", "wasm-metadata", @@ -5132,8 +5148,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.11.0", - "indexmap 2.13.1", + "bitflags 2.11.1", + "indexmap 2.14.0", "log", "serde", "serde_derive", @@ -5152,7 +5168,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap 2.13.1", + "indexmap 2.14.0", "log", "semver", "serde", @@ -5354,7 +5370,7 @@ dependencies = [ "arbitrary", "crc32fast", "flate2", - "indexmap 2.13.1", + "indexmap 2.14.0", "memchr", "zopfli", ] diff --git a/server/Cargo.toml b/server/Cargo.toml index 1bffd640..d8e5070b 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -32,7 +32,7 @@ license = "GPL-3.0-only" name = "codechat-editor-server" readme = "../README.md" repository = "https://github.com/bjones1/CodeChat_Editor" -version = "0.1.52" +version = "0.1.53" # This library allows other packages to use core CodeChat Editor features. [lib] @@ -68,6 +68,7 @@ futures-util = "0.3.29" htmd = { git = "https://github.com/bjones1/htmd.git", branch = "dom-interface", version = "0.5" } # This must match the version of `markup5ever_rcdom`. html5ever = "0.38" +htmlize = "1.0.6" imara-diff = { version = "0.2", features = [] } indoc = "2.0.5" lazy_static = "1" diff --git a/server/dist.toml b/server/dist.toml index 2af96cc6..1ec46151 100644 --- a/server/dist.toml +++ b/server/dist.toml @@ -17,9 +17,9 @@ # [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/). # # `dist.toml` - Configure [cargo-dist](https://opensource.axo.dev/cargo-dist/) -# ============================================================================== +# ============================================================================ # -# Config for 'dist' +# Config for `dist`. [dist] # Extra static files to include in each App (path relative to this Cargo.toml's # dir) diff --git a/server/src/ide.rs b/server/src/ide.rs index 21037d95..b609b441 100644 --- a/server/src/ide.rs +++ b/server/src/ide.rs @@ -13,13 +13,29 @@ // 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). -/// `ide.rs` -- Provide interfaces with common IDEs -/// ============================================================================ +//! `ide.rs` -- Provide interfaces with common IDEs +//! =============================================== +//! +//! This module bridges IDE extensions and the CodeChat Editor's core server. +//! Its central type, `CodeChatEditorServer`, starts the Actix web server in a +//! dedicated OS thread (isolating its async runtime from the IDE's own runtime) +//! and exposes a typed message-passing interface for the extension to use: +//! +//! - **`send_message_*`** methods push editor events (file opened, file +//! changed, cursor moved, …) into the server's translation pipeline. +//! - **`get_message`** / **`get_message_timeout`** retrieve responses from the +//! server (including synthetic timeout errors for unacknowledged messages). +//! - **`stop_server`** gracefully shuts the server down. +//! +//! 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; pub mod vscode; // Imports -// ----------------------------------------------------------------------------- +// ------- // // ### Standard library use std::{ @@ -57,7 +73,7 @@ use crate::{ }; // Code -// ----------------------------------------------------------------------------- +// ---- // // Using this macro is critical -- otherwise, the Actix system doesn't get // correctly initialized, which makes calls to `actix_rt::spawn` fail. In @@ -87,6 +103,7 @@ pub struct CodeChatEditorServer { } impl CodeChatEditorServer { + // Creating the server could fail, so this must return an `io::Result`. pub fn new() -> std::io::Result { // Start the server. let (server, app_state) = setup_server( @@ -100,11 +117,8 @@ impl CodeChatEditorServer { let connection_id_raw = random::().to_string(); let connection_id = connection_id_raw_to_str(&connection_id_raw); let app_state_task = app_state.clone(); - let translation_queues = create_translation_queues( - connection_id_raw_to_str(connection_id_raw.as_str()), - &app_state, - ) - .map_err(|err| std::io::Error::other(format!("Unable to create queues: {err}")))?; + let translation_queues = create_translation_queues(connection_id.clone(), &app_state) + .map_err(|err| std::io::Error::other(format!("Unable to create queues: {err}")))?; thread::spawn(move || { start_server( connection_id_raw, @@ -177,7 +191,7 @@ impl CodeChatEditorServer { } // Send the provided message contents; add in an ID and add this to the list - // of pending messages. This produces a timeout of a matching `Result` + // of pending messages. This produces a timeout if a matching `Result` // message isn't received with the timeout. async fn send_message_timeout( &self, @@ -262,6 +276,8 @@ impl CodeChatEditorServer { 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(), }, source: CodeMirrorDiffable::Plain(CodeMirror { @@ -308,11 +324,11 @@ impl CodeChatEditorServer { // This returns after the server shuts down. pub async fn stop_server(&self) { self.server_handle.stop(true).await; - // Stop all running timers. + // Since the server is closing, don't report any expired message. + self.expired_messages_rx.lock().await.close(); + // Stop all running timers, now that no new messages will arrive. for (_id, join_handle) in self.pending_messages.lock().await.drain() { join_handle.abort(); } - // Since the server is closing, don't report any expired message. - self.expired_messages_rx.lock().await.close(); } } diff --git a/server/src/ide/filewatcher.rs b/server/src/ide/filewatcher.rs index 93b2829a..72abcf8b 100644 --- a/server/src/ide/filewatcher.rs +++ b/server/src/ide/filewatcher.rs @@ -14,9 +14,9 @@ // the CodeChat Editor. If not, see // [http://www.gnu.org/licenses](http://www.gnu.org/licenses). /// `filewatcher.rs` -- Implement the File Watcher "IDE" -/// ============================================================================ +/// ==================================================== // Imports -// ----------------------------------------------------------------------------- +// ------- // // ### Standard library use std::{ @@ -67,13 +67,13 @@ use crate::{ translation::{create_translation_queues, translation_task}, webserver::{ EditorMessage, EditorMessageContents, RESERVED_MESSAGE_ID, UpdateMessageContents, - client_websocket, get_client_framework, html_not_found, html_wrapper, path_display, + client_websocket, get_client_framework, 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(); @@ -82,7 +82,7 @@ lazy_static! { pub const FILEWATCHER_PATH_PREFIX: &[&str] = &["fw", "fsc"]; /// File browser endpoints -/// ---------------------------------------------------------------------------- +/// ---------------------- /// /// The file browser provides a very crude interface, allowing a user to select /// a file from the local filesystem for editing. Long term, this should be @@ -135,8 +135,8 @@ async fn filewatcher_browser_endpoint( let canon_path = match Path::new(&fixed_path).canonicalize() { Ok(p) => p, Err(err) => { - return Ok(html_not_found(&format!( - "

The requested path {fixed_path} is not valid: {err}.

" + return Ok(http_not_found(&format!( + "The requested path {fixed_path} is not valid: {err}." ))); } }; @@ -150,8 +150,8 @@ async fn filewatcher_browser_endpoint( // It's not a directory or a file...we give up. For simplicity, don't handle // symbolic links. - Ok(html_not_found(&format!( - "

The requested path {} is not a directory or a file.

", + Ok(http_not_found(&format!( + "The requested path {} is not a directory or a file.", path_display(&canon_path) ))) } @@ -172,7 +172,7 @@ async fn dir_listing(web_path: &str, dir_path: &Path) -> HttpResponse { let mut drive_html = String::new(); let logical_drives = match get_logical_drive() { Ok(v) => v, - Err(err) => return html_not_found(&format!("Unable to list drive letters: {err}.")), + Err(err) => return http_not_found(&format!("Unable to list drive letters: {err}.")), }; for drive_letter in logical_drives { drive_html.push_str(&format!( @@ -196,8 +196,8 @@ async fn dir_listing(web_path: &str, dir_path: &Path) -> HttpResponse { let mut unwrapped_read_dir = match fs::read_dir(dir_path).await { Ok(p) => p, Err(err) => { - return html_not_found(&format!( - "

Unable to list the directory {}: {err}/

", + return http_not_found(&format!( + "Unable to list the directory {}: {err}", path_display(dir_path) )); } @@ -213,8 +213,8 @@ async fn dir_listing(web_path: &str, dir_path: &Path) -> HttpResponse { let file_type = match dir_entry.file_type().await { Ok(x) => x, Err(err) => { - return html_not_found(&format!( - "

Unable to determine the type of {}: {err}.", + return http_not_found(&format!( + "Unable to determine the type of {}: {err}.", path_display(&dir_entry.path()), )); } @@ -230,7 +230,7 @@ async fn dir_listing(web_path: &str, dir_path: &Path) -> HttpResponse { } } Err(err) => { - return html_not_found(&format!("

Unable to read file in directory: {err}.")); + return http_not_found(&format!("Unable to read file in directory: {err}.")); } }; } @@ -256,8 +256,8 @@ async fn dir_listing(web_path: &str, dir_path: &Path) -> HttpResponse { let dir_name = match dir.file_name().into_string() { Ok(v) => v, Err(err) => { - return html_not_found(&format!( - "

Unable to decode directory name '{err:?}' as UTF-8." + return http_not_found(&format!( + "Unable to decode directory name '{err:?}' as UTF-8." )); } }; @@ -273,9 +273,7 @@ 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 html_not_found( - &format!("

Unable to decode file name {err:?} as UTF-8.",), - ); + return http_not_found(&format!("Unable to decode file name {err:?} as UTF-8.",)); } }; let encoded_file = urlencoding::encode(&file_name); @@ -305,10 +303,13 @@ async fn dir_listing(web_path: &str, dir_path: &Path) -> HttpResponse { .body(html_wrapper(&body)) } -/// Calls the [GetLogicalDrives](https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getlogicaldrives) Windows API function -/// and returns a `Vector` of drive letters. +/// Calls the +/// [GetLogicalDrives](https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getlogicaldrives) +/// Windows API function and returns a `Vector` of drive letters. /// -/// 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. +/// 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(target_os = "windows")] pub fn get_logical_drive() -> Result, std::io::Error> { let bitmask = unsafe { GetLogicalDrives() }; @@ -318,7 +319,8 @@ pub fn get_logical_drive() -> Result, std::io::Error> { let mut mask = 1; let mut result: Vec = vec![]; - for index in 1..26 { + // Recall that the range 1..27 ends a 26, covering all drive letters. + for index in 1..27 { if mask & bitmask == mask { let char = std::char::from_u32(index + 64); result.push(char.unwrap()); @@ -399,7 +401,7 @@ async fn processing_task( match app_state.ide_queues.lock().unwrap().remove(&connection_id) { Some(queues) => (queues.from_websocket_tx.clone(), queues.to_websocket_rx), None => { - let err = "No websocket queues for connection id {connection_id}."; + let err = format!("No websocket queues for connection id {connection_id}."); error!("{err}"); return Err(error::ErrorBadRequest(err)); } @@ -588,11 +590,12 @@ async fn processing_task( let result = 'process: { // Check that the file path matches the current // file. If `canonicalize` fails, then the files - // don't match. + // don't match. Note that `file_path` is already + // canonicalized. if Some(Path::new(&update_message_contents.file_path).to_path_buf()) != current_filepath { break 'process Err(ResultErrTypes::WrongFileUpdate(update_message_contents.file_path, current_filepath.clone())); } - // With code or a path, there's nothing to save. + // 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, @@ -632,7 +635,8 @@ async fn processing_task( { break 'err_exit Err(ResultErrTypes::FileUnwatchError(cfp.to_path_buf(), err.to_string())); } - // Update to the new path. + // Update to the new path, which is already + // canonicalized. current_filepath = Some(file_path.to_path_buf()); // Watch the new file. @@ -662,7 +666,7 @@ async fn processing_task( EditorMessageContents::LoadFile(..) => { // We never have the requested file loaded in this - // "IDE". Intead, it's always on disk. + // "IDE". Instead, it's always on disk. send_response(&from_ide_tx, m.id, Ok(ResultOkTypes::LoadFile(None))).await; } @@ -724,7 +728,7 @@ pub fn get_connection_id_raw(app_state: &WebAppState) -> u32 { } // Tests -// ----------------------------------------------------------------------------- +// ----- #[cfg(test)] mod tests { use std::{ @@ -991,7 +995,10 @@ mod tests { .unwrap(); let (id_rx, msg_rx) = get_message_as!(to_client_rx, EditorMessageContents::Result); assert_eq!(id, id_rx); - matches!(cast!(&msg_rx, Err), ResultErrTypes::ClientIllegalMessage); + assert!(matches!( + cast!(&msg_rx, Err), + ResultErrTypes::ClientIllegalMessage + )); } // 5. Send an update message with no path. @@ -1007,7 +1014,7 @@ mod tests { is_re_translation: false, contents: Some(CodeChatForWeb { metadata: SourceFileMetadata { - mode: "".to_string(), + mode: "cpp".to_string(), }, source: CodeMirrorDiffable::Plain(CodeMirror { doc: "".to_string(), diff --git a/server/src/ide/vscode.rs b/server/src/ide/vscode.rs index 94d8883a..b6fa791e 100644 --- a/server/src/ide/vscode.rs +++ b/server/src/ide/vscode.rs @@ -15,14 +15,14 @@ // [http://www.gnu.org/licenses](http://www.gnu.org/licenses). /// `vscode.rs` -- Implement server-side functionality for the Visual Studio /// Code IDE -/// ============================================================================ +/// ======================================================================== // Modules -// ----------------------------------------------------------------------------- +// ------- #[cfg(test)] pub mod tests; // Imports -// ----------------------------------------------------------------------------- +// ------- // // ### Standard library // @@ -34,6 +34,7 @@ use actix_web::{ error::{Error, ErrorBadRequest}, get, web, }; +use htmlize::escape_text; use indoc::formatdoc; use log::{debug, error}; @@ -46,18 +47,18 @@ use crate::{ }, webserver::{ EditorMessage, EditorMessageContents, IdeType, RESERVED_MESSAGE_ID, ResultErrTypes, - ResultOkTypes, WebAppState, client_websocket, escape_html, filesystem_endpoint, - get_client_framework, get_server_url, html_wrapper, send_response, + ResultOkTypes, WebAppState, client_websocket, filesystem_endpoint, get_client_framework, + get_server_url, html_wrapper, send_response, }, }; // Globals -// ----------------------------------------------------------------------------- +// ------- const VSCODE_PATH_PREFIX: &[&str] = &["vsc", "fs"]; const VSC: &str = "vsc-"; // Code -// ----------------------------------------------------------------------------- +// ---- #[get("/vsc/ws-ide/{connection_id_raw}")] pub async fn vscode_ide_websocket( connection_id_raw: web::Path, @@ -76,7 +77,7 @@ pub async fn vscode_ide_websocket( } CreateTranslationQueuesError::IdeInUse(connection_id_str) => { return client_websocket( - connection_id_str.clone(), + connection_id_str, req, body, app_state.ide_queues.clone(), @@ -125,10 +126,9 @@ pub fn vscode_ide_core( // Make sure it's the `Opened` message. let EditorMessageContents::Opened(ide_type) = first_message.message else { - let id = first_message.id; let err = ResultErrTypes::UnexpectedMessage(format!("{:#?}", first_message)); error!("{err}"); - send_response(&to_ide_tx, id, Err(err)).await; + send_response(&to_ide_tx, first_message.id, Err(err)).await; // Send a `Closed` message to shut down the websocket. queue_send!(to_ide_tx.send(EditorMessage { id: RESERVED_MESSAGE_ID, message: EditorMessageContents::Closed}), 'task); @@ -155,8 +155,8 @@ pub fn vscode_ide_core( first_message.id ); send_response(&to_ide_tx, first_message.id, Ok(ResultOkTypes::Void)).await; - - // Send the HTML for the internal browser. + // Send the HTML for the internal browser. The ID of + // this message is `RESERVED_MESSAGE_ID`. let client_html = formatdoc!( r#" @@ -194,7 +194,7 @@ pub fn vscode_ide_core( Ok(()) } else { Err(format!( - "Unexpected message LoadFile contents {result_ok:?}." + "Unexpected Result message with LoadFile contents {result_ok:?}." )) } }, @@ -203,7 +203,8 @@ pub fn vscode_ide_core( }; if let Err(err) = res { error!("{err}"); - // Send a `Closed` message. + // 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 @@ -219,7 +220,9 @@ pub fn vscode_ide_core( error!("{err:?}"); send_response(&to_ide_tx, first_message.id, Err(err)).await; - // Send a `Closed` message. + // 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 @@ -236,7 +239,8 @@ pub fn vscode_ide_core( error!("{err:?}"); send_response(&to_ide_tx, first_message.id, Err(err)).await; - // Close the connection. + // 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; } @@ -268,7 +272,7 @@ pub async fn vscode_client_framework(connection_id: web::Path) -> HttpRe Ok(web_page) => web_page, Err(html_string) => { error!("{html_string}"); - html_wrapper(&escape_html(&html_string)) + html_wrapper(&escape_text(&html_string)) } }, ) diff --git a/server/src/ide/vscode/tests.rs b/server/src/ide/vscode/tests.rs index d9919b35..bf431a5e 100644 --- a/server/src/ide/vscode/tests.rs +++ b/server/src/ide/vscode/tests.rs @@ -64,7 +64,7 @@ use crate::{ }; use test_utils::{ cast, - test_utils::{_prep_test_dir, check_logger_errors, configure_testing_logger}, + test_utils::{check_logger_errors, configure_testing_logger, prep_test_dir_impl}, }; // Globals @@ -147,7 +147,7 @@ async fn connect_async_client(connection_id: &str) -> WebSocketStreamTcp { /// /// Message ids at function end: IDE - 4, Server - 3, Client - 2. async fn open_client(ws_ide: &mut WebSocketStream) { - // 1. Send the `Opened` message. + // 1. Send the `Opened` message. // // Message ids: IDE - 1->4, Server - 0, Client - 2. send_message( @@ -168,7 +168,7 @@ async fn open_client(ws_ide: &mut WebSocketSt } ); - // 2. Next, wait for the next message -- the HTML. + // 2. Next, wait for the next message -- the HTML. // // Message ids: IDE - 4, Server - 0->3, Client - 2. let em = read_message(ws_ide).await; @@ -197,7 +197,7 @@ async fn _prep_test( test_full_name: &str, ) -> (TempDir, PathBuf, WebSocketStreamTcp, WebSocketStreamTcp) { configure_testing_logger(); - let (temp_dir, test_dir) = _prep_test_dir(test_full_name); + let (temp_dir, test_dir) = prep_test_dir_impl(test_full_name); // Ensure the webserver is running. let _ = &*WEBSERVER_HANDLE; let now = SystemTime::now(); @@ -973,7 +973,8 @@ async fn test_vscode_ide_websocket4() { // // Message ids: IDE - 0, Server - 2->3, Client - 0. // - // Since the version is randomly generated, copy that from the received message. + // Since the version is randomly generated, copy that from the received + // message. let msg = read_message(&mut ws_client).await; assert_eq!( msg, @@ -1061,7 +1062,9 @@ async fn test_vscode_ide_websocket4() { .await; join_handle.join().unwrap(); - // What makes sense here? If the IDE didn't load the file, either the Client shouldn't edit it or the Client should switch to using a filewatcher for edits. + // What makes sense here? If the IDE didn't load the file, either the Client + // shouldn't edit it or the Client should switch to using a filewatcher for + // edits. /*** // Send an update from the Client, which should produce a diff. // diff --git a/server/src/lexer.rs b/server/src/lexer.rs index e86396bb..1be1dda1 100644 --- a/server/src/lexer.rs +++ b/server/src/lexer.rs @@ -15,13 +15,13 @@ // [http://www.gnu.org/licenses](http://www.gnu.org/licenses). mod pest_parser; /// `lexer.rs` -- Lex source code into code and doc blocks -/// ============================================================================ +/// ====================================================== // Submodule definitions -// ----------------------------------------------------------------------------- +// --------------------- pub mod supported_languages; // Imports -// ----------------------------------------------------------------------------- +// ------- // // ### Standard library #[cfg(feature = "lexer_explain")] @@ -36,7 +36,7 @@ use regex::Regex; use supported_languages::get_language_lexer_vec; /// Data structures -/// ---------------------------------------------------------------------------- +/// --------------- /// /// ### Language definition /// @@ -49,7 +49,7 @@ use supported_languages::get_language_lexer_vec; /// so, must newlines be escaped? /// * It defines heredocs in a flexible form (see `HeredocDelim` for more /// details). -/// * It associates an Ace mode and filename extensions with the lexer. +/// * It associates a CodeMirror mode and filename extensions with the lexer. /// /// This lexer ignores line continuation characters; in C/C++/Python, it's a `\` /// character followed immediately by a newline @@ -59,7 +59,7 @@ use supported_languages::get_language_lexer_vec; /// /// 1. It would allow the lexer to recognize the following C/C++ snippet as a /// doc block: `// This is an odd\` `two-line inline comment.` However, this -/// such such unusual syntax (most authors would instead use either a block +/// is such unusual syntax (most authors would instead use either a block /// comment or another inline comment) that recognizing it adds little value. /// 2. I'm unaware of any valid syntax in which ignoring a line continuation /// would cause the lexer to mis-recognize code as a comment. (Escaped @@ -185,7 +185,7 @@ pub struct LanguageLexersCompiled { pub language_lexer_compiled_vec: Vec>, // Maps a file extension to indices into the lexers vector. pub map_ext_to_lexer_vec: HashMap, Vec>>, - // Maps an Ace mode to an index into the lexers vector. + // Maps a CodeMirror mode to an index into the lexers vector. pub map_mode_to_lexer: HashMap, Arc>, } @@ -260,7 +260,7 @@ pub enum CodeDocBlock { } // Globals -// ----------------------------------------------------------------------------- +// ------- // // 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). @@ -323,7 +323,7 @@ fn build_lexer_regex( let mut regex_builder = |// // An array of alternative delimiters, which will // be combined with a regex or (`|`) operator. - string_arr: &Vec, + string_arr: &[String], // The type of delimiter in `string_arr`. regex_delim_type: RegexDelimType| { // If there are no delimiters, then there's nothing to do. @@ -339,11 +339,9 @@ fn build_lexer_regex( // Add the opening block comment delimiter to the overall regex; add the // closing block comment delimiter to the map for the corresponding group. - let mut block_comment_opening_delim: Vec = vec!["".to_string()]; for block_comment_delim in &language_lexer.block_comment_delim_arr { - block_comment_opening_delim[0].clone_from(&block_comment_delim.opening); regex_builder( - &block_comment_opening_delim, + std::slice::from_ref(&block_comment_delim.opening), // Determine the block closing regex: RegexDelimType::BlockComment( Regex::new(&if block_comment_delim.is_nestable { @@ -363,7 +361,7 @@ fn build_lexer_regex( ); } regex_builder( - &language_lexer.inline_comment_delim_arr.to_vec(), + &language_lexer.inline_comment_delim_arr, RegexDelimType::InlineComment, ); // Build regexes for each string delimiter. @@ -494,7 +492,7 @@ fn build_lexer_regex( } .unwrap(); regex_builder( - &[regex::escape(&string_delim_spec.delimiter)].to_vec(), + std::slice::from_ref(&string_delim_spec.delimiter), RegexDelimType::String(end_of_string_regex), ); } @@ -504,7 +502,7 @@ fn build_lexer_regex( // A C# verbatim string has asymmetric opening and closing delimiters, // making it a special case. SpecialCase::CSharpVerbatimStringLiteral => regex_builder( - &vec!["@\"".to_string()], + &["@\"".to_string()], RegexDelimType::String(Regex::new(C_SHARP_VERBATIM_STRING_CLOSING).unwrap()), ), SpecialCase::TemplateLiteral => { @@ -519,7 +517,7 @@ fn build_lexer_regex( // // TODO: match either an unescaped `${` -- which causes a nested // parse -- or the closing backtick (which must be unescaped). - regex_builder(&vec!["`".to_string()], RegexDelimType::TemplateLiteral); + regex_builder(&["`".to_string()], RegexDelimType::TemplateLiteral); } SpecialCase::Matlab => { // MATLAB supports block comments, when the comment delimiters @@ -596,7 +594,7 @@ fn build_lexer_regex( } // Compile lexers -// ----------------------------------------------------------------------------- +// -------------- pub fn compile_lexers(language_lexer_arr: Vec) -> LanguageLexersCompiled { let mut language_lexers_compiled = LanguageLexersCompiled { language_lexer_compiled_vec: Vec::new(), @@ -634,7 +632,7 @@ pub fn compile_lexers(language_lexer_arr: Vec) -> LanguageLexersC } /// Source lexer -/// ---------------------------------------------------------------------------- +/// ------------ /// /// This lexer categorizes source code into code blocks or doc blocks. /// @@ -687,6 +685,10 @@ pub fn source_lexer( // Provide a method to intelligently append to the code/doc block vec. Empty // appends are ignored; appends of the same type append to `contents` // instead of creating a new entry. + // + // See if we have a PEG-based parser for this language; use that if + // available. This parser normalizes line endings on its own, so don't + // normalize here. if let Some(parser) = language_lexer_compiled.language_lexer.parser { return parser(source_code); } @@ -1011,15 +1013,13 @@ pub fn source_lexer( // strings/heredocs, in particular) until we leave the // nested comment block. Therefore, keep track of the // nesting depth; when this returns to 0, we've found - // outermost closing block comment delimiter, and can - // return to normal parsing. At this point in the code, - // we've found one opening block comment delimiter, so - // the nesting depth starts at 1. - let mut nesting_depth = 1; - let mut loop_count = 0; + // the outermost closing block comment delimiter, and + // can return to normal parsing. At this point in the + // code, we've found one opening block comment + // delimiter, so the nesting depth starts at 1. + let mut nesting_depth: u32 = 1; // Loop until we've outside all nested block comments. - while nesting_depth != 0 && loop_count < 10 { - loop_count += 1; + while nesting_depth != 0 { // Get the index of the next block comment // delimiter. #[cfg(feature = "lexer_explain")] @@ -1079,7 +1079,7 @@ pub fn source_lexer( opening_delimiter.start(), opening_delimiter.len() ); - source_code_unlexed_index += + source_code_unlexed_index = comment_start_index + opening_delimiter.start(); comment_start_index = source_code_unlexed_index + opening_delimiter.len(); @@ -1091,8 +1091,8 @@ pub fn source_lexer( continue; } else { // This is a closing comment delimiter. + assert!(nesting_depth > 0); nesting_depth -= 1; - assert!(nesting_depth >= 0); let closing_delimiter_match = if delimiter_captures.len() == 3 { delimiter_captures.get(2).unwrap() } else { @@ -1103,10 +1103,9 @@ pub fn source_lexer( // then mark this text as code and continue the // loop. if !last_delimiter_was_opening { - source_code_unlexed_index += comment_start_index + source_code_unlexed_index = comment_start_index + closing_delimiter_match.start() + closing_delimiter_match.len(); - last_delimiter_was_opening = false; #[cfg(feature = "lexer_explain")] println!( "Found a non-innermost closing block comment delimiter. Nesting depth: {}", @@ -1114,6 +1113,12 @@ pub fn source_lexer( ); continue; } + // Now that we've used this variable to + // determine that the current comment is a doc + // block, update it: this is a closing + // delimited, so the last delimiter for the next + // iteration is not an opening delimiter. + last_delimiter_was_opening = false; // Otherwise, this is a potential doc block: // it's an innermost nested block comment. See @@ -1441,7 +1446,7 @@ pub fn source_lexer( } // Tests -// ----------------------------------------------------------------------------- +// ----- // // Rust // [almost mandates](https://doc.rust-lang.org/book/ch11-03-test-organization.html) diff --git a/server/src/lexer/pest/c.pest b/server/src/lexer/pest/c.pest index 295648ef..0cebf315 100644 --- a/server/src/lexer/pest/c.pest +++ b/server/src/lexer/pest/c.pest @@ -15,10 +15,10 @@ // [http://www.gnu.org/licenses](http://www.gnu.org/licenses). // // `c.pest` - Pest parser definition for the C language -// ============================================================================= +// ==================================================== // // Comments -// ----------------------------------------------------------------------------- +// -------- doc_block = _{ inline_comment | block_comment } // Per the @@ -50,7 +50,7 @@ block_comment_closing_delim_1 = { unused } block_comment_closing_delim_2 = { unused } // Code -// ----------------------------------------------------------------------------- +// ---- // // Per the // [C standard, section 5.1.1.2](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3220.pdf#page=24), @@ -67,7 +67,7 @@ block_comment_closing_delim_2 = { unused } // [hard line break](https://spec.commonmark.org/0.31.2/#hard-line-breaks) in // Markdown, which means inserting a hard line break this way in an inline // comment requires the next line to omit the inline comment delimiters. For -// example:  +// example: // // ```C // // This is a hard line break\ @@ -83,7 +83,7 @@ logical_line_char = _{ ("\\" ~ NEWLINE) | not_newline } code_line_token = _{ logical_line_char } // Dedenter -// ----------------------------------------------------------------------------- +// -------- // // This parser runs separately; it dedents block comments. There are several // cases: diff --git a/server/src/lexer/pest/python.pest b/server/src/lexer/pest/python.pest index e07812aa..07b523d3 100644 --- a/server/src/lexer/pest/python.pest +++ b/server/src/lexer/pest/python.pest @@ -15,7 +15,7 @@ // [http://www.gnu.org/licenses](http://www.gnu.org/licenses). // // `python.pest` - Pest parser definition for Python -// ============================================================================= +// ================================================= doc_block = _{ inline_comment } // Per the @@ -25,7 +25,7 @@ doc_block = _{ inline_comment } white_space = { (" " | "\t")* } // Inline comments -// ----------------------------------------------------------------------------- +// --------------- inline_comment_delims = _{ inline_comment_delim_0 } inline_comment_delim_0 = { "#" } inline_comment_delim_1 = { unused } @@ -37,7 +37,7 @@ inline_comment_delim_2 = { unused } inline_comment_char = { not_newline } // Block comments -// ----------------------------------------------------------------------------- +// -------------- // // Other languages support block comments; even though Python doesn't, the // following must be defined. Block comments never combine. @@ -50,7 +50,7 @@ block_comment_closing_delim_1 = { unused } block_comment_closing_delim_2 = { unused } // Code blocks -// ----------------------------------------------------------------------------- +// ----------- code_line_token = _{ long_string | short_string | not_newline } long_string = _{ // The opening string delimiter. @@ -73,7 +73,7 @@ short_string = _{ } // Dedenter -// ----------------------------------------------------------------------------- +// -------- dedenter = { unused } /// CodeChat Editor lexer: cpp. diff --git a/server/src/lexer/pest/shared.pest b/server/src/lexer/pest/shared.pest index 6abcfa76..1df626df 100644 --- a/server/src/lexer/pest/shared.pest +++ b/server/src/lexer/pest/shared.pest @@ -15,11 +15,11 @@ // [http://www.gnu.org/licenses](http://www.gnu.org/licenses). // // `shared.pest` - Pest parser definition shared by all languages -// ============================================================================= +// ============================================================== file = { SOI ~ (doc_block | code_block)* ~ EOI } // Inline comments -// ----------------------------------------------------------------------------- +// --------------- // // Use this approach to match a group of inline comments with the same // whitespace indentation. @@ -46,7 +46,7 @@ inline_comment_line = { (" " ~ inline_comment_body) | newline_eoi } inline_comment_body = { inline_comment_char* ~ newline_eoi } // Block comments -// ----------------------------------------------------------------------------- +// -------------- // // Support multiple opening and closing delimiters using some repetition. block_comment_0 = _{ @@ -72,12 +72,12 @@ optional_space = { " "? } block_comment_ending = { newline_eoi } // Code blocks -// ----------------------------------------------------------------------------- +// ----------- code_block = { code_line+ } code_line = _{ (!doc_block ~ code_line_token* ~ NEWLINE) | (!doc_block ~ code_line_token+ ~ EOI) } // Other commonly-used tokens -// ----------------------------------------------------------------------------- +// -------------------------- newline_eoi = _{ NEWLINE | EOI } not_newline = _{ !NEWLINE ~ ANY } // Indicates this token isn't used by the parser. diff --git a/server/src/lexer/pest_parser.rs b/server/src/lexer/pest_parser.rs index 4b1a43e9..5924706f 100644 --- a/server/src/lexer/pest_parser.rs +++ b/server/src/lexer/pest_parser.rs @@ -15,10 +15,10 @@ // [http://www.gnu.org/licenses](http://www.gnu.org/licenses). // // `pest_parser.rs` -- Lex source code into code and doc blocks -// ============================================================================= +// ============================================================ // // Imports -// ----------------------------------------------------------------------------- +// ------- // // ### Standard library // @@ -26,18 +26,18 @@ // // ### Third-party // -// None. +// None. Though note that each parser module has local third-party imports. // // ### Local // // None. /// Parser generator -/// ---------------------------------------------------------------------------- +/// ---------------- /// /// This macro generates a parser function that converts the provided string /// into a series of code and doc blocks. I'd prefer to use traits, but don't -/// see a way to pass the `Rule` types as a usable. (Using `RuleType` means we -/// can't access `Rule::file`, etc.) +/// see a way to pass the `Rule` types as a parameter. (Using `RuleType` means +/// we can't access `Rule::file`, etc.) #[macro_export] macro_rules! make_parse_to_code_doc_blocks { ($parser: ty) => { @@ -48,7 +48,7 @@ macro_rules! make_parse_to_code_doc_blocks { // CodeMirror indexes work. let normalized_input = &String::from_iter(normalize_line_endings::normalized(input.chars())); - let pairs = match <$parser>::parse(Rule::file, normalized_input) { + let mut pairs = match <$parser>::parse(Rule::file, normalized_input) { Ok(pairs) => pairs, Err(e) => panic!("Parse error: {e:#?}"), } @@ -63,10 +63,8 @@ macro_rules! make_parse_to_code_doc_blocks { assert_eq!(pairs.clone().last().unwrap().as_rule(), Rule::EOI); // Transform these tokens into code and doc blocks; ignore the last // token (EOI). + pairs.next_back(); pairs - .rev() - .skip(1) - .rev() .map(|block| match block.as_rule() { Rule::inline_comment => { // Gather all tokens in the inline comment. @@ -76,15 +74,14 @@ macro_rules! make_parse_to_code_doc_blocks { let whitespace = whitespace_pair.as_str(); let inline_comment_delim = inline_comment.next().unwrap(); // Combine the text of all the inline comments. - let comment = &mut inline_comment.fold( - String::new(), - |mut acc, inline_comment_body| { + let comment = + &inline_comment.fold(String::new(), |mut acc, inline_comment_body| { assert_eq!( inline_comment_body.as_rule(), Rule::inline_comment_line ); let s = inline_comment_body.as_str(); - let inner = &mut inline_comment_body.into_inner(); + let mut inner = inline_comment_body.into_inner(); // See the notes on inline comments in // [c.pest](pest/c.pest) for the expected // structure of the `inline_comment_body`. @@ -103,8 +100,7 @@ macro_rules! make_parse_to_code_doc_blocks { // comment contents) to the accumulator. acc.push_str(contents); acc - }, - ); + }); // Determine which opening delimiter was used. let _delimiter_index = match inline_comment_delim.as_rule() { @@ -130,7 +126,7 @@ macro_rules! make_parse_to_code_doc_blocks { let pre_whitespace_pair = block_comment.next().unwrap(); assert_eq!(pre_whitespace_pair.as_rule(), Rule::white_space); let pre_whitespace = pre_whitespace_pair.as_str(); - let block_comment_opening_delim = block_comment.next().unwrap().as_rule(); + let block_comment_opening_delim = block_comment.next().unwrap(); let block_comment_pre_pair = block_comment.next().unwrap(); assert_eq!(block_comment_pre_pair.as_rule(), Rule::block_comment_pre); let block_comment_pre = block_comment_pre_pair.as_str(); @@ -167,13 +163,6 @@ macro_rules! make_parse_to_code_doc_blocks { let block_comment_ending = block_comment_ending_pair.as_str(); assert!(block_comment.next().is_none()); - // Determine which opening delimiter was used. - let _opening_delim_index = match block_comment_opening_delim { - Rule::block_comment_opening_delim_0 => 0, - Rule::block_comment_opening_delim_1 => 1, - Rule::block_comment_opening_delim_2 => 2, - _ => unreachable!(), - }; // TODO -- use this in the future. //println!("Opening delimiter index: {}", opening_delim_index); @@ -197,7 +186,9 @@ macro_rules! make_parse_to_code_doc_blocks { let mut full_comment = parse_block_comment(&pre_whitespace, &full_comment); // Trim the optional space, if it exists. if !optional_space.is_empty() && full_comment.ends_with(optional_space) { - full_comment.pop(); + // Don't `pop()`, in case the space is a multi-byte + // character is some bizarre grammar. + full_comment.truncate(full_comment.len() - optional_space.len()); } // Transform this to a doc block. @@ -205,7 +196,7 @@ macro_rules! make_parse_to_code_doc_blocks { let lines = full_comment.lines().count(); $crate::lexer::CodeDocBlock::DocBlock($crate::lexer::DocBlock { indent: pre_whitespace.to_string(), - delimiter: "/*".to_string(), + delimiter: block_comment_opening_delim.to_string(), contents: full_comment.to_string(), lines, }) @@ -251,7 +242,7 @@ macro_rules! make_parse_block_comment { } // Parsers -// ----------------------------------------------------------------------------- +// ------- // // Each parser is kept in a separate module to avoid name conflicts, since Pest // generates a `Rule` enum for each grammar. @@ -280,7 +271,7 @@ pub mod python { } // Tests -// ----------------------------------------------------------------------------- +// ----- #[cfg(test)] mod test { use indoc::indoc; diff --git a/server/src/lexer/supported_languages.rs b/server/src/lexer/supported_languages.rs index f95f3103..1097ee10 100644 --- a/server/src/lexer/supported_languages.rs +++ b/server/src/lexer/supported_languages.rs @@ -15,13 +15,13 @@ /// [http://www.gnu.org/licenses](http://www.gnu.org/licenses). /// /// `supported_languages.rs` - Provide lexer info for all supported languages -/// ============================================================================ +/// ========================================================================= /// /// This file contains a data structure which describes all supported languages; /// the [lexer](../lexer.rs) uses this lex a given language. /// /// Lexer implementation -/// ---------------------------------------------------------------------------- +/// -------------------- /// /// Ordering matters: all these delimiters end up in a large regex separated by /// an or operator. The regex or operator matches from left to right. So, longer @@ -31,8 +31,9 @@ /// then if that's not found, the single quote. A regex of `"|"""` would never /// match the triple quote, since the single quote would match first. /// -/// Note that the lexers here should be complemented by the appropriate Ace mode -/// in [ace-webpack.mts](../../../client/src/ace-webpack.mts). +/// Note that the lexers here should be complemented by the appropriate +/// CodeMirror mode in +/// [CodeMirror-integration.mts](../../../client/src/CodeMirror-integration.mts). /// /// ### String delimiter doubling /// @@ -45,7 +46,7 @@ /// doesn't parse the string correctly, it does correctly identify where /// comments can't be, which is all that the lexer needs to do. // Imports -// ----------------------------------------------------------------------------- +// ------- // // ### Standard library use std::sync::Arc; @@ -59,7 +60,7 @@ use super::{ pub const MARKDOWN_MODE: &str = "markdown"; // Helper functions -// ----------------------------------------------------------------------------- +// ---------------- // // These functions simplify the syntax needed to create a `LanguageLexer`. #[allow(clippy::too_many_arguments)] @@ -125,7 +126,7 @@ fn make_block_comment_delim(opening: &str, closing: &str, is_nestable: bool) -> } // Define lexers for each supported language. -// ----------------------------------------------------------------------------- +// ------------------------------------------ pub fn get_language_lexer_vec() -> Vec { vec![ // ### Linux shell scripts @@ -182,19 +183,23 @@ pub fn get_language_lexer_vec() -> Vec { // [6.3.3 Comments](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/lexical-structure#633-comments). // Also provide support for // [documentation comments](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/documentation-comments). - &["//", "///"], + &["///", "//"], &[ - make_block_comment_delim("/*", "*/", false), make_block_comment_delim("/**", "*/", false), + make_block_comment_delim("/*", "*/", false), ], &[make_string_delimiter_spec( // See // [6.4.5.6 String literals](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/lexical-structure#6456-string-literals). + // This should also handle interpolated string literals. "\"", "\\", NewlineSupport::None, )], + // TODO: support + // [C# 11 raw string literals](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/raw-string). None, + // This also handles interpolated verbatim string literals. SpecialCase::CSharpVerbatimStringLiteral, None, ), @@ -219,7 +224,7 @@ pub fn get_language_lexer_vec() -> Vec { // See // [The Go Programming Language Specification](https://go.dev/ref/spec) // on [Comments](https://go.dev/ref/spec#Comments). - &[], + &["//"], &[make_block_comment_delim("/*", "*/", false)], // See [String literals](https://go.dev/ref/spec#String_literals). &[ @@ -237,8 +242,8 @@ pub fn get_language_lexer_vec() -> Vec { &[], &[make_block_comment_delim("", false)], &[ - make_string_delimiter_spec("\"", "\\", NewlineSupport::Unescaped), - make_string_delimiter_spec("'", "\\", NewlineSupport::Unescaped), + make_string_delimiter_spec("\"", "", NewlineSupport::Unescaped), + make_string_delimiter_spec("'", "", NewlineSupport::Unescaped), ], None, SpecialCase::None, @@ -259,17 +264,17 @@ pub fn get_language_lexer_vec() -> Vec { // See // [§3.10.5. String Literals](https://docs.oracle.com/javase/specs/jls/se19/html/jls-3.html#jls-3.10.5). &[ + // See + // [§3.10.6. Text Blocks](https://docs.oracle.com/javase/specs/jls/se19/html/jls-3.html#jls-3.10.6). + make_string_delimiter_spec("\"\"\"", "\\", NewlineSupport::Unescaped), make_string_delimiter_spec( "\"", "\\", - // Per the previous link, It is a compile-time error for - // a line terminator (§3.4) to appear after the opening " - // and before the matching closing "." + // Per §3.10.5, It is a compile-time error for a line + // terminator (§3.4) to appear after the opening " and + // before the matching closing "." NewlineSupport::None, ), - // See - // [§3.10.6. Text Blocks](https://docs.oracle.com/javase/specs/jls/se19/html/jls-3.html#jls-3.10.6). - make_string_delimiter_spec("\"\"\"", "\\", NewlineSupport::Unescaped), ], None, SpecialCase::None, @@ -370,7 +375,7 @@ pub fn get_language_lexer_vec() -> Vec { ], // Likewise, raw byte strings behave identically to raw strings from // this lexer's perspective. - make_heredoc_delim("r", "#+", "\"", "\"", ""), + make_heredoc_delim("r", "#*", "\"", "\"", ""), SpecialCase::None, None, ), @@ -446,7 +451,7 @@ pub fn get_language_lexer_vec() -> Vec { // Basic strings make_string_delimiter_spec("\"", "\\", NewlineSupport::None), // Literal strings - make_string_delimiter_spec("'", "\\", NewlineSupport::Escaped), + make_string_delimiter_spec("'", "", NewlineSupport::None), ], None, SpecialCase::None, @@ -459,8 +464,8 @@ pub fn get_language_lexer_vec() -> Vec { &["//"], &[make_block_comment_delim("/*", "*/", false)], &[ - make_string_delimiter_spec("\"", "\\", NewlineSupport::Unescaped), - make_string_delimiter_spec("'", "\\", NewlineSupport::Unescaped), + make_string_delimiter_spec("\"", "\\", NewlineSupport::Escaped), + make_string_delimiter_spec("'", "\\", NewlineSupport::Escaped), ], None, SpecialCase::TemplateLiteral, @@ -501,8 +506,10 @@ pub fn get_language_lexer_vec() -> Vec { ), // ### [V](https://vlang.io/) make_language_lexer( - // Ace doesn't support V yet. - "", + // CodeMirror doesn't support V yet. Go ahead and include a name for + // the future. + "v", + // Note that this overlaps with Verilog. &["v"], // See // [Comments](https://github.com/vlang/v/blob/master/doc/docs.md#comments). diff --git a/server/src/lexer/tests.rs b/server/src/lexer/tests.rs index b72eedec..266f3e7d 100644 --- a/server/src/lexer/tests.rs +++ b/server/src/lexer/tests.rs @@ -15,9 +15,9 @@ /// [http://www.gnu.org/licenses](http://www.gnu.org/licenses). /// /// `test.rs` -- Unit tests for the lexer -/// ============================================================================ +/// ===================================== // Imports -// ----------------------------------------------------------------------------- +// ------- use super::supported_languages::get_language_lexer_vec; use super::{CodeDocBlock, DocBlock, compile_lexers, source_lexer}; use indoc::indoc; @@ -25,7 +25,7 @@ use pretty_assertions::assert_eq; use test_utils::test_utils::stringit; // Utilities -// ----------------------------------------------------------------------------- +// --------- // // Provide a compact way to create a `CodeDocBlock`. fn build_doc_block(indent: &str, delimiter: &str, contents: &str) -> CodeDocBlock { @@ -590,19 +590,28 @@ 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 */"#, rust ), [ - build_code_block("/* Depth 1\n"), + build_code_block(" /* Depth 1\n"), build_doc_block(" ", "/*", "Depth 2 comment\n"), build_code_block( r#" /* Depth 2 /* Depth 3 */ */ + /* Depth 2 +"# + ), + build_doc_block(" ", "/*", "Depth 3 comment\n"), + build_code_block( + r#" */ More depth 1 */"# ), ] diff --git a/server/src/lib.rs b/server/src/lib.rs index 7e178fc2..16e71f18 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -15,7 +15,7 @@ /// [http://www.gnu.org/licenses](http://www.gnu.org/licenses). /// /// `lib.rs` -- Define library modules for the CodeChat Editor Server -/// ============================================================================ +/// ================================================================= /// /// TODO: Add the ability to use /// [plugins](https://zicklag.github.io/rust-tutorials/rust-plugins.html). diff --git a/server/src/main.rs b/server/src/main.rs index 276002a3..bc443035 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -13,16 +13,27 @@ // 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 Server -/// ============================================================================ +//! `main.rs` -- Entrypoint for the CodeChat Editor Server +//! ====================================================== +//! +//! This file implements the command-line interface (CLI) for the CodeChat +//! Editor server binary. It provides three subcommands: +//! +//! - `serve`: Start the webserver in the foreground. +//! - `start`: Spawn the webserver as a background child process, polling +//! until it responds to a ping, then optionally open a browser. +//! - `stop`: Send a stop request to a running server instance. +//! +//! All subcommands accept `--host` and `--port` options to control the +//! server's network address. // Imports -// ----------------------------------------------------------------------------- +// ------- // // ### Standard library use std::{ env, fs, io::{self, Read}, - net::{IpAddr, Ipv4Addr, SocketAddr}, + net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, ops::RangeInclusive, path::PathBuf, process::{Child, Command, Stdio}, @@ -39,7 +50,7 @@ use log::LevelFilter; use code_chat_editor::webserver::{self, Credentials, GetServerUrlError, path_to_url}; // Data structures -// ----------------------------------------------------------------------------- +// --------------- // // ### Command-line interface // @@ -80,7 +91,7 @@ enum Commands { log: Option, /// Define the username:password used to limit access to the server. By - /// default, access is unlimited. + /// default, access is unlimited. The username may not contain a colon.- #[arg(short, long, value_parser = parse_credentials)] auth: Option, }, @@ -94,7 +105,7 @@ enum Commands { } // Code -// ----------------------------------------------------------------------------- +// ---- // // The following code implements the command-line interface for the CodeChat // Editor. @@ -116,8 +127,7 @@ impl Cli { addr, credentials.clone(), log.unwrap_or(LevelFilter::Info), - ) - .unwrap(); + )?; } Commands::Start { open } => { // Poll the server to ensure it starts. @@ -321,24 +331,23 @@ fn port_in_range(s: &str) -> Result { } fn parse_credentials(s: &str) -> Result { - let split_: Vec<_> = s.split(":").collect(); - if split_.len() != 2 { - Err(format!( - "Unable to parse credentials as username:password; found {} colon-separated string(s), but expected 2", - split_.len() - )) - } else { - Ok(Credentials { - username: split_[0].to_string(), - password: split_[1].to_string(), - }) - } + // For simplicity, require a username to have no colons. + let split: Vec<_> = s.splitn(2, ":").collect(); + Ok(Credentials { + username: split[0].to_string(), + password: split[1].to_string(), + }) } +/// 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)) { let mut addr = *addr; - addr.set_ip(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))); + addr.set_ip(IpAddr::V4(Ipv4Addr::LOCALHOST)); + addr + } else if addr.ip() == IpAddr::V6(Ipv6Addr::UNSPECIFIED) { + let mut addr = *addr; + addr.set_ip(IpAddr::V6(Ipv6Addr::LOCALHOST)); addr } else { *addr @@ -356,7 +365,7 @@ fn main() -> Result<(), Box> { #[tokio::main] async fn get_server_url(port: u16) -> Result { - return code_chat_editor::webserver::get_server_url(port).await; + code_chat_editor::webserver::get_server_url(port).await } #[cfg(test)] diff --git a/server/src/processing.rs b/server/src/processing.rs index 2d9073f9..dcb2ecc9 100644 --- a/server/src/processing.rs +++ b/server/src/processing.rs @@ -15,20 +15,11 @@ // [http://www.gnu.org/licenses](http://www.gnu.org/licenses). /// `processing.rs` -- Transform source code to its web-editable equivalent and /// back -/// ============================================================================ +/// =========================================================================== // Imports -// ----------------------------------------------------------------------------- +// ------- // // ### Standard library -// -// For commented-out caching code. -/** -use std::collections::{HashMap, HashSet}; -use std::fs::Metadata; -use std::io; -use std::ops::Deref; -use std::rc::{Rc, Weak}; -*/ use std::{ borrow::Cow, cell::RefCell, @@ -76,7 +67,7 @@ use crate::lexer::{ }; // Data structures -// ----------------------------------------------------------------------------- +// --------------- // // ### Translation between a local (traditional) source file and its web-editable, client-side representation // @@ -249,7 +240,7 @@ pub enum TranslationResultsString { // On save, the process is CodeChatForWeb -> Vec\ -> source code. // // Globals -// ----------------------------------------------------------------------------- +// ------- lazy_static! { /// Match the lexer directive in a source file. static ref LEXER_DIRECTIVE: Regex = Regex::new(r"CodeChat Editor lexer: (\w+)").unwrap(); @@ -328,7 +319,7 @@ const WORD_WRAP_COLUMN: usize = 80; const WORD_WRAP_MIN_WIDTH: usize = 40; // Serialization for `CodeMirrorDocBlock` -// ----------------------------------------------------------------------------- +// -------------------------------------- #[derive(Serialize, Deserialize, TS)] #[ts(export)] struct CodeMirrorDocBlockTuple<'a>( @@ -380,7 +371,7 @@ impl<'de> Deserialize<'de> for CodeMirrorDocBlock { } // Determine if the provided file is part of a project -// ----------------------------------------------------------------------------- +// --------------------------------------------------- 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 @@ -420,7 +411,7 @@ pub enum CodechatForWebToSourceError { } // Transform `CodeChatForWeb` to source code -// ----------------------------------------------------------------------------- +// ----------------------------------------- /// This function takes in a source file in web-editable format (the /// `CodeChatForWeb` struct) and transforms it into source code. pub fn codechat_for_web_to_source( @@ -465,7 +456,7 @@ pub fn codechat_for_web_to_source( .map_err(CodechatForWebToSourceError::CannotTranslateCodeChat) } -/// Return the byte index of `s[u16_16_index]`, where the indexing operation is +/// Return the byte index of `s[utf_16_index]`, where the indexing operation is /// in UTF-16 code units. fn byte_index_of(s: &str, utf_16_index: usize) -> usize { let mut byte_index = 0; @@ -657,7 +648,7 @@ pub enum CodeDocBlockVecToSourceError { // Turn this vec of CodeDocBlocks into a string of source code. fn code_doc_block_vec_to_source( - code_doc_block_vec: &Vec, + code_doc_block_vec: &[CodeDocBlock], lexer: &LanguageLexerCompiled, ) -> Result { let mut file_contents = String::new(); @@ -814,7 +805,7 @@ pub enum SourceToCodeChatForWebError { } // Transform from source code to `CodeChatForWeb` -// ----------------------------------------------------------------------------- +// ---------------------------------------------- // // Given the contents of a file, classify it and (for CodeChat Editor files) // convert it to the `CodeChatForWeb` format. @@ -893,7 +884,7 @@ pub fn source_to_codechat_for_web( // Walk through the code/doc blocks, ... let doc_contents = code_doc_block_arr .iter() - // ...selcting only the doc block contents... + // ...selecting only the doc block contents... .filter_map(|cdb| { if let CodeDocBlock::DocBlock(db) = cdb { Some(db.contents.as_str()) @@ -919,21 +910,21 @@ pub fn source_to_codechat_for_web( // 3. Hydrate the cleaned HTML. let html = hydrate_html(&html) .map_err(|e| SourceToCodeChatForWebError::ParseFailed(e.to_string()))?; - // 3. Split on the separator. + // 4. Split on the separator. let mut doc_block_contents_iter = html.split(DOC_BLOCK_SEPARATOR_SPLIT_STRING); // // Translate each `CodeDocBlock` to its `CodeMirror` equivalent. + let mut len = len_utf16(&code_mirror.doc); for code_or_doc_block in code_doc_block_arr { let source = &mut code_mirror.doc; match code_or_doc_block { - CodeDocBlock::CodeBlock(code_string) => source.push_str(&code_string), + CodeDocBlock::CodeBlock(code_string) => { + source.push_str(&code_string); + len += len_utf16(&code_string) + } CodeDocBlock::DocBlock(doc_block) => { // Create the doc block. - // - // Get the length of the string in characters (not - // bytes, which is what `len()` returns). - let len = source.chars().count(); code_mirror.doc_blocks.push(CodeMirrorDocBlock { from: len, // To. Note that the last doc block could be zero @@ -949,6 +940,7 @@ pub fn source_to_codechat_for_web( // replace these in the editor. This keeps the line // numbering of non-doc blocks correct. source.push_str(&"\n".repeat(doc_block.lines)); + len += doc_block.lines; } } } @@ -959,6 +951,11 @@ pub fn source_to_codechat_for_web( Ok(TranslationResults::CodeChat(codechat_for_web)) } +// Compute the length of the provided string in UTF16 characters. +fn len_utf16(s: &str) -> usize { + s.chars().map(|c| c.len_utf16()).sum() +} + // Like `source_to_codechat_for_web`, translate a source file to the CodeChat // Editor client format. This wraps a call to that function with additional // processing (determine if this is part of a project, encode the output as @@ -1313,7 +1310,7 @@ static CUSTOM_ELEMENT_TO_CODE_BLOCK_LANGUAGE: phf::Map<&'static str, &'static st // this approach is to modify only what changed, rather than changing // everything. As a secondary goal, this hopefully improves overall performance // by sending less data between the server and the client, in spite of the -// additional computational requirements for compting the diff. +// additional computational requirements for computing the diff. // // Fundamentally, diffs of a string and diff of this vector require different // approaches: @@ -1489,7 +1486,7 @@ pub fn diff_code_mirror_doc_blocks( &prev_after_range_start_val.delimiter, ), contents: diff_str( - &prev_after_range_start_val.contents, + &prev_before_range_start_val.contents, &prev_after_range_start_val.contents, ), }, @@ -1596,15 +1593,10 @@ pub fn diff_code_mirror_doc_blocks( let mut immediate_sequence_start_index: Option = None; for index in 0..change_specs.len() { let is_add = matches!(&change_specs[index], CodeMirrorDocBlockTransaction::Add(_)); - let is_inserted_update = if let CodeMirrorDocBlockTransaction::Update(update) = - &change_specs[index] - && let Some(from_new) = update.from_new - && from_new > update.from - { - true - } else { - false - }; + let is_inserted_update = matches!( + &change_specs[index], + CodeMirrorDocBlockTransaction::Update(u) if u.from_new.is_some_and(|f| f > u.from) + ); if is_add || is_inserted_update { // This is an update produced by inserting lines. if immediate_sequence_start_index.is_none() { @@ -1629,217 +1621,7 @@ pub fn diff_code_mirror_doc_blocks( change_specs } -// Goal: make it easy to update the data structure. We update on every -// load/save, then do some accesses during those processes. -// -// Top-level data structures: a file HashSet\ and an id -// HashMap\}>. Some FileAnchors in the file -// HashSet are also in a pending load list.. -// -// * To update a file: -// * Remove the old file from the file HasHMap. Add an empty FileAnchor to the -// file HashMap. -// * For each id, see if that id already exists. -// * If the id exists: if it refers to an id in the old FileAnchor, replace -// it with the new one. If not, need to perform resolution on this id (we -// have a non-unique id; how to fix?). -// * If the id doesn't exist: create a new one. -// * For each hyperlink, see if that id already exists. -// * If so, upsert the referring id. Check the metadata on the id to make -// sure that data is current. If not, add this to the pending hyperlinks -// list. If the file is missing, delete it from the cache. -// * If not, create a new entry in the id HashSet and add the referring id -// to the HashSet. Add the file to a pending hyperlinks list. -// * When the file is processed: -// * Look for all entries in the pending file list that refer to the current -// file and resolve these. Start another task to load in all pending -// files. -// * Look at the old file; remove each id that's still in the id HashMap. If -// the id was in the HashMap and it also was a Hyperlink, remove that from -// the HashSet. -// * To remove a file from the HashMap: -// * Remove it from the file HashMap. -// * For each hyperlink, remove it from the HashSet of referring links (if -// that id still exists). -// * For each id, remove it from the id HashMap. -// * To add a file from the HashSet: -// * Perform an update with an empty FileAnchor. -// -// Pending hyperlinks list: for each hyperlink, -// -// * check if the id is now current in the cache. If so, add the referring id to -// the HashSet then move to the next hyperlink. -// * check if the file is now current in the cache. If not, load the file and -// update the cache, then go to step 1. -// * The id was not found, even in the expected file. Add the hyperlink to a -// broken links set? -// -// Global operations: -// -// * Scan all files, then perform add/upsert/removes based on differences with -// the cache. -// -// Functions: -// -// * Upsert an Anchor. -// * Upsert a Hyperlink. -// * Upsert a file. -// * Remove a file. -/*x -/// There are two types of files that can serve as an anchor: these are file -/// anchor targets. -enum FileAnchor { - Plain(PlainFileAnchor), - Html(HtmlFileAnchor), -} - -/// This is the cached metadata for a file that serves as an anchor: perhaps an -/// image, a PDF, or a video. -struct PlainFileAnchor { - /// A relative path to this file, rooted at the project's TOC. - path: Rc, - /// The globally-unique anchor used to link to this file. It's generated - /// based on hash of the file's contents, so that each file will have a - /// unique identifier. - anchor: String, - /// Metadata captured when this data was cached. If it disagrees with the - /// file's current state, then this cached data should be re=generated from - /// the file. - file_metadata: Metadata, -} - -/// Cached metadata for an HTML file. -struct HtmlFileAnchor { - /// The file containing this HTML. - file_anchor: PlainFileAnchor, - /// The TOC numbering of this file. - numbering: Vec>, - /// The headings in this file. - headings: Vec, - /// Anchors which appear before the first heading. - pre_anchors: Vec, -} - -/// Cached metadata shared by both headings (which are also anchors) and -/// non-heading anchors. -struct AnchorCommon { - /// The HTML file containing this anchor. - html_file_anchor: Weak, - /// The globally-unique anchor used to link to this object. - anchor: String, - /// The inner HTML of this anchor. - inner_html: String, - /// The hyperlink this anchor contains. - hyperlink: Option>, -} - -/// An anchor is defined only in these two places: the anchor source. -enum HtmlAnchor { - Heading(HeadingAnchor), - NonHeading(NonHeadingAnchor), -} - -/// Cached metadata for a heading (which is always also an anchor). -struct HeadingAnchor { - anchor_common: AnchorCommon, - /// The numbering of this heading on the HTML file containing it. - numbering: Vec>, - /// Non-heading anchors which appear after this heading but before the next - /// heading. - non_heading_anchors: Vec, -} - -/// Cached metadata for a non-heading anchor. -struct NonHeadingAnchor { - anchor_common: AnchorCommon, - /// The heading this anchor appears after (unless it appears before the - /// first heading in this file). - parent_heading: Option>, - /// A snippet of HTML preceding this anchor. - pre_snippet: String, - /// A snippet of HTML following this anchor. - post_snippet: String, - /// If this is a numbered item, the name of the numbering group it belongs - /// to. - numbering_group: Option, - /// If this is a numbered item, its number. - number: u32, -} - -/// An anchor can refer to any of these structs: these are all possible anchor -/// targets. -enum Anchor { - Html(HtmlAnchor), - File(FileAnchor), -} - -/// The metadata for a hyperlink. -struct Hyperlink { - /// The file this hyperlink refers to. - file: PathBuf, - /// The anchor this hyperlink refers to. - html_anchor: String, -} - -/// The value stored in the id HashMap. -struct AnchorVal { - /// The target anchor this id refers to. - anchor: Anchor, - /// All hyperlinks which target this anchor. - referring_links: Rc>, -} - -// Given HTML, catalog all link targets and link-like items, ensuring that they -// have a globally unique id. -fn html_analyze( - file_path: &Path, - html: &str, - mut file_map: HashMap, Rc>, - mut anchor_map: HashMap, HashSet>, -) -> io::Result { - // Create the missing anchors: - // - // A missing file. - let missing_html_file_anchor = Rc::new(FileAnchor::Html(HtmlFileAnchor { - file_anchor: PlainFileAnchor { - path: Rc::new(PathBuf::new()), - anchor: "".to_string(), - // TODO: is there some way to create generic/empty metadata? - file_metadata: Path::new(".").metadata().unwrap(), - }, - numbering: Vec::new(), - headings: Vec::new(), - pre_anchors: Vec::new(), - })); - // Define an anchor in this file. - let missing_anchor = NonHeadingAnchor { - anchor_common: AnchorCommon { - html_file_anchor: Rc::downgrade(&missing_html_file_anchor), - anchor: "".to_string(), - hyperlink: None, - inner_html: "".to_string(), - }, - parent_heading: None, - pre_snippet: "".to_string(), - post_snippet: "".to_string(), - numbering_group: None, - number: 0, - }; - // Add this to the top-level hashes. - let anchor_val = AnchorVal { - anchor: Anchor::Html(HtmlAnchor::NonHeading(missing_anchor)), - referring_links: Rc::new(HashSet::new()), - }; - //file_map.insert(mfa.file_anchor.path, missing_html_file_anchor); - //let anchor_val_set: HashSet = HashSet::new(); - //anchor_val_set.insert(anchor_val); - //anchor_map.insert(&mfa.file_anchor.anchor, anchor_val_set); - - Ok("".to_string()) -} -*/ - // Tests -// ----------------------------------------------------------------------------- +// ----- #[cfg(test)] mod tests; diff --git a/server/src/processing/tests.rs b/server/src/processing/tests.rs index f47c1a85..d54f3848 100644 --- a/server/src/processing/tests.rs +++ b/server/src/processing/tests.rs @@ -288,64 +288,57 @@ fn test_code_doc_blocks_to_source_py() { let py_lexer = llc.map_mode_to_lexer.get(&stringit("python")).unwrap(); // An empty document. - assert_eq!(code_doc_block_vec_to_source(&vec![], py_lexer).unwrap(), ""); + assert_eq!(code_doc_block_vec_to_source(&[], py_lexer).unwrap(), ""); // A one-line comment. assert_eq!( - code_doc_block_vec_to_source(&vec![build_doc_block("", "#", "Test")], py_lexer).unwrap(), + code_doc_block_vec_to_source(&[build_doc_block("", "#", "Test")], py_lexer).unwrap(), "# Test" ); assert_eq!( - code_doc_block_vec_to_source(&vec![build_doc_block("", "#", "Test\n")], py_lexer).unwrap(), + code_doc_block_vec_to_source(&[build_doc_block("", "#", "Test\n")], py_lexer).unwrap(), "# Test\n" ); // Check empty doc block lines and multiple lines. assert_eq!( - code_doc_block_vec_to_source( - &vec![build_doc_block("", "#", "Test 1\n\nTest 2")], - py_lexer - ) - .unwrap(), + code_doc_block_vec_to_source(&[build_doc_block("", "#", "Test 1\n\nTest 2")], py_lexer) + .unwrap(), "# Test 1\n#\n# Test 2" ); // Repeat the above tests with an indent. assert_eq!( - code_doc_block_vec_to_source(&vec![build_doc_block(" ", "#", "Test")], py_lexer).unwrap(), + code_doc_block_vec_to_source(&[build_doc_block(" ", "#", "Test")], py_lexer).unwrap(), " # Test" ); assert_eq!( - code_doc_block_vec_to_source(&vec![build_doc_block(" ", "#", "Test\n")], py_lexer) - .unwrap(), + code_doc_block_vec_to_source(&[build_doc_block(" ", "#", "Test\n")], py_lexer).unwrap(), " # Test\n" ); assert_eq!( - code_doc_block_vec_to_source( - &vec![build_doc_block(" ", "#", "Test 1\n\nTest 2")], - py_lexer - ) - .unwrap(), + code_doc_block_vec_to_source(&[build_doc_block(" ", "#", "Test 1\n\nTest 2")], py_lexer) + .unwrap(), " # Test 1\n #\n # Test 2" ); // Basic code. assert_eq!( - code_doc_block_vec_to_source(&vec![build_code_block("Test")], py_lexer).unwrap(), + code_doc_block_vec_to_source(&[build_code_block("Test")], py_lexer).unwrap(), "Test" ); // An incorrect delimiter. assert_eq!( - code_doc_block_vec_to_source(&vec![build_doc_block("", "?", "Test")], py_lexer), + code_doc_block_vec_to_source(&[build_doc_block("", "?", "Test")], py_lexer), Err(CodeDocBlockVecToSourceError::UnknownCommentOpeningDelimiter("?".to_string())) ); // Empty doc blocks separated by an empty code block. assert_eq!( code_doc_block_vec_to_source( - &vec![ + &[ build_doc_block("", "#", "\n"), build_code_block("\n"), - build_doc_block("", "#", ""), + build_doc_block("", "#", "") ], py_lexer ) @@ -355,10 +348,10 @@ fn test_code_doc_blocks_to_source_py() { assert_eq!( code_doc_block_vec_to_source( - &vec![ + &[ build_doc_block("", "#", "σ\n"), build_code_block("σ\n"), - build_doc_block("", "#", "σ"), + build_doc_block("", "#", "σ") ], py_lexer ) @@ -375,24 +368,20 @@ fn test_code_doc_blocks_to_source_css() { let css_lexer = llc.map_mode_to_lexer.get(&stringit("css")).unwrap(); // An empty document. - assert_eq!( - code_doc_block_vec_to_source(&vec![], css_lexer).unwrap(), - "" - ); + assert_eq!(code_doc_block_vec_to_source(&[], css_lexer).unwrap(), ""); // A one-line comment. assert_eq!( - code_doc_block_vec_to_source(&vec![build_doc_block("", "/*", "Test\n")], css_lexer) - .unwrap(), + code_doc_block_vec_to_source(&[build_doc_block("", "/*", "Test\n")], css_lexer).unwrap(), "/* Test */\n" ); assert_eq!( - code_doc_block_vec_to_source(&vec![build_doc_block("", "/*", "Test")], css_lexer).unwrap(), + code_doc_block_vec_to_source(&[build_doc_block("", "/*", "Test")], css_lexer).unwrap(), "/* Test */" ); // Check empty doc block lines and multiple lines. assert_eq!( code_doc_block_vec_to_source( - &vec![ + &[ build_code_block("Test_0\n"), build_doc_block("", "/*", "Test 1\n\nTest 2\n") ], @@ -408,13 +397,12 @@ fn test_code_doc_blocks_to_source_css() { // Repeat the above tests with an indent. assert_eq!( - code_doc_block_vec_to_source(&vec![build_doc_block(" ", "/*", "Test\n")], css_lexer) - .unwrap(), + code_doc_block_vec_to_source(&[build_doc_block(" ", "/*", "Test\n")], css_lexer).unwrap(), " /* Test */\n" ); assert_eq!( code_doc_block_vec_to_source( - &vec![ + &[ build_code_block("Test_0\n"), build_doc_block(" ", "/*", "Test 1\n\nTest 2\n") ], @@ -430,13 +418,13 @@ fn test_code_doc_blocks_to_source_css() { // Basic code. assert_eq!( - code_doc_block_vec_to_source(&vec![build_code_block("Test")], css_lexer).unwrap(), + code_doc_block_vec_to_source(&[build_code_block("Test")], css_lexer).unwrap(), "Test" ); // An incorrect delimiter. assert_eq!( - code_doc_block_vec_to_source(&vec![build_doc_block("", "?", "Test")], css_lexer), + code_doc_block_vec_to_source(&[build_doc_block("", "?", "Test")], css_lexer), Err(CodeDocBlockVecToSourceError::UnknownCommentOpeningDelimiter("?".to_string())) ); } @@ -448,37 +436,32 @@ fn test_code_doc_blocks_to_source_csharp() { let csharp_lexer = llc.map_mode_to_lexer.get(&stringit("csharp")).unwrap(); // An empty document. - assert_eq!( - code_doc_block_vec_to_source(&vec![], csharp_lexer).unwrap(), - "" - ); + assert_eq!(code_doc_block_vec_to_source(&[], csharp_lexer).unwrap(), ""); // An invalid comment. assert_eq!( - code_doc_block_vec_to_source(&vec![build_doc_block("", "?", "Test\n")], csharp_lexer), + code_doc_block_vec_to_source(&[build_doc_block("", "?", "Test\n")], csharp_lexer), Err(CodeDocBlockVecToSourceError::UnknownCommentOpeningDelimiter("?".to_string())) ); // Inline comments. assert_eq!( - code_doc_block_vec_to_source(&vec![build_doc_block("", "//", "Test\n")], csharp_lexer) - .unwrap(), + code_doc_block_vec_to_source(&[build_doc_block("", "//", "Test\n")], csharp_lexer).unwrap(), "// Test\n" ); assert_eq!( - code_doc_block_vec_to_source(&vec![build_doc_block("", "///", "Test\n")], csharp_lexer) + code_doc_block_vec_to_source(&[build_doc_block("", "///", "Test\n")], csharp_lexer) .unwrap(), "/// Test\n" ); // Block comments. assert_eq!( - code_doc_block_vec_to_source(&vec![build_doc_block("", "/*", "Test\n")], csharp_lexer) - .unwrap(), + code_doc_block_vec_to_source(&[build_doc_block("", "/*", "Test\n")], csharp_lexer).unwrap(), "/* Test */\n" ); assert_eq!( - code_doc_block_vec_to_source(&vec![build_doc_block("", "/**", "Test\n")], csharp_lexer) + code_doc_block_vec_to_source(&[build_doc_block("", "/**", "Test\n")], csharp_lexer) .unwrap(), "/** Test */\n" ); @@ -644,23 +627,48 @@ fn test_source_to_codechat_for_web_1() { ))) ); - // Test Unicode characters in code. + // Test Unicode characters and multi-byte Unicode characters in code. + // + // ``` + // \u03c3 \ud83d \ude04 \ud83d \udc49 \ud83c \udfff \ud83d \udc68 \u200d \ud83d \udc66 \ud83c \uddfa \ud83c \uddf3 + // index: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + // char: --σ-- ---😄--- ----------👉🏿--------- --------------👨‍👦-------------- -----------🇺🇳---------- + // ``` + // + // These are taken from the [MDN UTF-16 + // docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#utf-16_characters_unicode_code_points_and_grapheme_clusters). assert_eq!( - source_to_codechat_for_web("; // σ\n//", &"cpp".to_string(), 0.0, false, false), + source_to_codechat_for_web("; // σ😄👉🏿👨‍👦🇺🇳\n//", &"cpp".to_string(), 0.0, false, false), Ok(TranslationResults::CodeChat(build_codechat_for_web( "cpp", - "; // σ\n", - vec![build_codemirror_doc_block(7, 8, "", "//", ""),] + "; // σ😄👉🏿👨‍👦🇺🇳\n", + vec![build_codemirror_doc_block(22, 23, "", "//", ""),] ))) ); - // Test Unicode characters in strings. + // Test Unicode characters and multi-byte Unicode characters in strings. assert_eq!( - source_to_codechat_for_web("\"σ\";\n//", &"cpp".to_string(), 0.0, false, false), + source_to_codechat_for_web("\"σ😄👉🏿👨‍👦🇺🇳\";\n//", &"cpp".to_string(), 0.0, false, false), Ok(TranslationResults::CodeChat(build_codechat_for_web( "cpp", - "\"σ\";\n", - vec![build_codemirror_doc_block(5, 6, "", "//", ""),] + "\"σ😄👉🏿👨‍👦🇺🇳\";\n", + vec![build_codemirror_doc_block(20, 21, "", "//", ""),] + ))) + ); + + // Test Unicode characters and multi-byte Unicode characters in comments. + assert_eq!( + source_to_codechat_for_web("// σ😄👉🏿👨‍👦🇺🇳\n;", &"cpp".to_string(), 0.0, false, false), + Ok(TranslationResults::CodeChat(build_codechat_for_web( + "cpp", + "\n;", + vec![build_codemirror_doc_block( + 0, + 1, + "", + "//", + "

σ😄👉🏿👨‍👦🇺🇳

\n" + ),] ))) ); diff --git a/server/src/translation.rs b/server/src/translation.rs index 141d822b..5bbf5908 100644 --- a/server/src/translation.rs +++ b/server/src/translation.rs @@ -305,10 +305,9 @@ pub fn create_translation_queues( // // 1. It hasn't been used before. In this case, create the appropriate // queues and start websocket and processing tasks. - // 2. It's in use, but was disconnected. In this case, re-use the queues and - // start the websocket task; the processing task is still running. - // 3. It's in use by another IDE. This is an error, but I don't have a way - // to detect it yet. + // 2. It's in use, but was disconnected. Return this as an error; the caller + // can resume the connection if appropriate. + // 3. It's in use by another IDE. Return this as an error. // // Check case 3. if app_state @@ -344,6 +343,7 @@ pub fn create_translation_queues( WebsocketQueues { from_websocket_tx: from_ide_tx, to_websocket_rx: to_ide_rx, + pending_messages: HashMap::new(), }, ) .is_none() @@ -360,6 +360,7 @@ pub fn create_translation_queues( WebsocketQueues { from_websocket_tx: from_client_tx, to_websocket_rx: to_client_rx, + pending_messages: HashMap::new(), }, ) .is_none() @@ -422,12 +423,12 @@ struct TranslationTask { /// Therefore, assign each file a version number. All files are sent with a /// unique, randomly-generated version number which define the file's /// version after this update is applied. Diffs also include the version - /// number of the file before applying the diff; the - // receiver's current version number must match with the sender's - /// pre-diff version number in order to apply the diff. When the versions - /// don't match, the IDE must send a full text file to the Server and Client - /// to re-sync. When a file is first loaded, its version number is None, - /// signaling that the sender must always provide the full text, not a diff. + /// number of the file before applying the diff; the receiver's current + /// version number must match with the sender's pre-diff version number in + /// order to apply the diff. When the versions don't match, the IDE must + /// send a full text file to the Server and Client to re-sync. When a file + /// is first loaded, its version number is None, signaling that the sender + /// must always provide the full text, not a diff. version: f64, /// Has the full (non-diff) version of the current file been sent? Don't /// send diffs until this is sent. @@ -453,7 +454,7 @@ pub async fn translation_task( if !shutdown_only { debug!("VSCode processing task started."); - // Create a queue for HTTP requests fo communicate with this task. + // Create a queue for HTTP requests to communicate with this task. let (from_http_tx, from_http_rx) = mpsc::channel(10); app_state .processing_task_queue_tx @@ -1202,7 +1203,9 @@ impl TranslationTask { // (Unix style). fn eol_convert(s: String, eol_type: &EolType) -> String { if eol_type == &EolType::Crlf { - s.replace("\n", "\r\n") + // There shouldn't be any Windows-style CRLFs -- but if there are, + // handle this nicely. + s.replace("\r\n", "\n").replace("\n", "\r\n") } else { s } @@ -1245,7 +1248,7 @@ fn compare_html( } } -// Given a vector of two doc blocks, compare them, ignoring newlines. +// Given vectors of two doc blocks, compare them, ignoring newlines. fn doc_block_compare(a: &CodeMirrorDocBlockVec, b: &CodeMirrorDocBlockVec) -> bool { if a.len() != b.len() { return false; diff --git a/server/src/webserver.rs b/server/src/webserver.rs index 145172c5..b4fb389e 100644 --- a/server/src/webserver.rs +++ b/server/src/webserver.rs @@ -14,17 +14,18 @@ // the CodeChat Editor. If not, see // [http://www.gnu.org/licenses](http://www.gnu.org/licenses). /// `webserver.rs` -- Serve CodeChat Editor Client webpages -/// ============================================================================ +/// ======================================================= // Submodules -// ----------------------------------------------------------------------------- +// ---------- #[cfg(test)] pub mod tests; // Imports -// ----------------------------------------------------------------------------- +// ------- // // ### Standard library use std::{ + borrow::Cow, collections::{HashMap, HashSet}, env, fs, io, net::SocketAddr, @@ -51,6 +52,7 @@ use actix_ws::AggregatedMessage; use bytes::Bytes; use dunce::simplified; use futures_util::StreamExt; +use htmlize::{escape_attribute, escape_text}; use indoc::{concatdoc, formatdoc}; use lazy_static::lazy_static; use log::{LevelFilter, error, info, warn}; @@ -94,7 +96,7 @@ use crate::{ }; // Data structures -// ----------------------------------------------------------------------------- +// --------------- // // ### Data structures supporting a websocket connection between the IDE, this // @@ -104,6 +106,7 @@ use crate::{ pub struct WebsocketQueues { pub from_websocket_tx: Sender, pub to_websocket_rx: Receiver, + pub pending_messages: HashMap>, } #[derive(Debug)] @@ -327,7 +330,7 @@ pub enum IdeType { /// should be hosted in an external browser. VSCode(bool), /// Another option -- temporary -- to allow for future expansion. - DeleteMe, + Other, } /// Contents of the `Update` message. @@ -335,7 +338,7 @@ pub enum IdeType { #[ts(export, optional_fields)] pub struct UpdateMessageContents { /// The filesystem path to this file. This is only used by the IDE to - /// determine which file to apply Update contents to. The Client stores then + /// determine which file to apply Update contents to. The Client stores this /// then sends it back to the IDE in `Update` messages. This helps deal with /// transition times when the IDE and Client have different files loaded, /// guaranteeing to updates are still applied to the correct file. @@ -367,15 +370,15 @@ pub struct AppState { /// The number of the next connection ID to assign for the filewatcher. pub filewatcher_next_connection_id: Mutex, /// The port this server listens on. - pub port: Arc>, + pub port: Mutex, /// For each connection ID, store a queue tx for the HTTP server to send /// requests to the processing task for that ID. - pub processing_task_queue_tx: Arc>>>, + pub processing_task_queue_tx: Mutex>>, /// For each connection ID, store the queues for the IDE and Client. pub ide_queues: Arc>>, pub client_queues: Arc>>, /// Connection IDs that are currently in use. - pub connection_id: Arc>>, + pub connection_id: Mutex>, /// The auth credentials if authentication is used. credentials: Option, } @@ -389,7 +392,7 @@ pub struct Credentials { } // Macros -// ----------------------------------------------------------------------------- +// ------ /// Create a macro to report an error when enqueueing an item. #[macro_export] macro_rules! queue_send { @@ -418,13 +421,13 @@ macro_rules! queue_send_func { } /// Globals -/// ---------------------------------------------------------------------------- +/// ------- // The timeout for a reply from a websocket, in ms. Use a short timeout to speed // up unit tests. pub const REPLY_TIMEOUT_MS: Duration = if cfg!(test) { Duration::from_millis(500) } else { - Duration::from_millis(1500000) + Duration::from_millis(15000) }; /// The time to wait for a pong from the websocket in response to a ping sent by @@ -501,13 +504,16 @@ lazy_static! { #[cfg(debug_assertions)] hl.push("server"); hl.push("hashLocations.json"); - let json = fs::read_to_string(hl.clone()).unwrap_or_else(|_| format!(r#"{{"error": "Unable to read {:#?}"}}"#, hl.to_string_lossy())); - let hmm: HashMap = serde_json::from_str(&json).unwrap_or_else(|_| HashMap::new()); + 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 ref CODECHAT_EDITOR_FRAMEWORK_JS: String = BUNDLED_FILES_MAP.get("CodeChatEditorFramework.js").cloned().unwrap_or("Not found".to_string()); - static ref CODECHAT_EDITOR_PROJECT_CSS: String = BUNDLED_FILES_MAP.get("CodeChatEditorProject.css").cloned().unwrap_or("Not found".to_string()); + 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."); } // Define the location of the root path, which contains `static/`, `log4rs.yml`, @@ -524,8 +530,10 @@ pub fn set_root_path( let exe_dir = if let Some(ed) = extension_base_path { ed } else { - exe_path = env::current_exe().unwrap(); - exe_path.parent().unwrap() + 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.") }; #[cfg(not(any(test, debug_assertions)))] let root_path = PathBuf::from(exe_dir); @@ -553,7 +561,7 @@ pub fn set_root_path( } // Webserver functionality -// ----------------------------------------------------------------------------- +// ----------------------- #[get("/ping")] async fn ping() -> HttpResponse { HttpResponse::Ok().body("pong") @@ -664,12 +672,14 @@ pub async fn filesystem_endpoint( // it here. #[cfg(not(target_os = "windows"))] let fixed_file_path = format!("/{request_file_path}"); + // TODO: security: ensure the resulting path is within the current project / + // some expected directory. let file_path = match try_canonicalize(&fixed_file_path) { Ok(v) => v, Err(err) => { let msg = format!("Error: unable to convert path {request_file_path}: {err}."); error!("{msg}"); - return html_not_found(&msg); + return http_not_found(&msg); } }; @@ -686,8 +696,8 @@ pub async fn filesystem_endpoint( .is_ok_and(|query| query.get("raw").is_some()); let is_test_mode = get_test_mode(req); let flags = if is_toc { - // Both flags should never be set. - assert!(!is_raw); + // This ignores the raw flag if it's set; since this makes not sense, + // this is ignored. ProcessingTaskHttpRequestFlags::Toc } else if is_raw { ProcessingTaskHttpRequestFlags::Raw @@ -708,7 +718,7 @@ pub async fn filesystem_endpoint( &connection_id ); error!("{msg}"); - return html_not_found(&msg); + return http_not_found(&msg); }; processing_tx.clone() }; @@ -726,7 +736,7 @@ pub async fn filesystem_endpoint( { let msg = format!("Error: unable to enqueue: {err}."); error!("{msg}"); - return html_not_found(&msg); + return http_not_found(&msg); } // Return the response provided by the processing task. @@ -735,7 +745,7 @@ pub async fn filesystem_endpoint( SimpleHttpResponse::Ok(body) => HttpResponse::Ok() .content_type(ContentType::html()) .body(body), - SimpleHttpResponse::Err(body) => html_not_found(&format!("{body}")), + SimpleHttpResponse::Err(body) => http_not_found(&format!("{body}")), SimpleHttpResponse::Raw(body, content_type) => { HttpResponse::Ok().content_type(content_type).body(body) } @@ -749,11 +759,11 @@ pub async fn filesystem_endpoint( } v.into_response(req) } - Err(err) => html_not_found(&format!("

Error opening file {path:?}: {err}.",)), + Err(err) => http_not_found(&format!("Error opening file {path:?}: {err}.",)), } } }, - Err(err) => html_not_found(&format!("Error: {err}")), + Err(err) => http_not_found(&format!("Error: {err}")), } } @@ -808,7 +818,7 @@ pub async fn file_to_response( None, ); }; - let name = escape_html(&file_name.to_string_lossy()); + let name = escape_text(file_name.to_string_lossy()); // Get the locations for bundled files. let js_test_suffix = if http_request.is_test_mode { @@ -826,7 +836,7 @@ pub async fn file_to_response( ); }; let codechat_editor_css_name = format!("CodeChatEditor{js_test_suffix}.css"); - let Some(codehat_editor_css) = BUNDLED_FILES_MAP.get(&codechat_editor_css_name) else { + let Some(codechat_editor_css) = BUNDLED_FILES_MAP.get(&codechat_editor_css_name) else { return ( SimpleHttpResponse::Err(SimpleHttpResponseError::BundledFileNotFound( codechat_editor_css_name, @@ -876,7 +886,7 @@ pub async fn file_to_response( ( format!( r#"

"#, - path_to_toc.unwrap().to_slash_lossy() + escape_attribute(path_to_toc.unwrap().to_slash_lossy()) ), format!( r#""#, @@ -910,11 +920,12 @@ pub async fn file_to_response( // as the query parameter. format!( r#""#, - http_request.url + escape_attribute(&http_request.url) ) } else { format!( - r#""# + r#""#, + escape_attribute(file_name) ) }, ), @@ -953,7 +964,7 @@ pub async fn file_to_response( {name} - The CodeChat Editor {MATHJAX_TAGS} - +
@@ -997,7 +1008,7 @@ pub async fn file_to_response( {name} - The CodeChat Editor {MATHJAX_TAGS} - + {sidebar_css} @@ -1050,7 +1061,7 @@ fn make_simple_viewer(http_request: &ProcessingTaskHttpRequest, html: &str) -> S file_path.to_path_buf(), )); }; - let file_name = escape_html(file_name); + let file_name = escape_text(file_name); let Some(path_to_toc) = find_path_to_toc(file_path) else { return SimpleHttpResponse::Err(SimpleHttpResponseError::PathNotProject( @@ -1062,7 +1073,7 @@ fn make_simple_viewer(http_request: &ProcessingTaskHttpRequest, html: &str) -> S path_to_toc.to_path_buf(), )); }; - let path_to_toc = escape_html(path_to_toc); + let path_to_toc = escape_text(path_to_toc); SimpleHttpResponse::Ok( // The JavaScript is a stripped-down version of @@ -1111,7 +1122,7 @@ fn make_simple_viewer(http_request: &ProcessingTaskHttpRequest, html: &str) -> S } /// Websockets -/// ---------------------------------------------------------------------------- +/// ---------- /// /// Each CodeChat Editor IDE instance pairs with a CodeChat Editor Client /// through the CodeChat Editor Server. Together, these form a joint editor, @@ -1136,18 +1147,19 @@ pub fn client_websocket( aggregated_msg_stream = aggregated_msg_stream.max_continuation_size(10_000_000); // Transfer the queues from the global state to this task. - let (from_websocket_tx, mut to_websocket_rx) = + let (from_websocket_tx, mut to_websocket_rx, mut pending_messages) = match websocket_queues.lock().unwrap().remove(&connection_id) { - Some(queues) => (queues.from_websocket_tx.clone(), queues.to_websocket_rx), + Some(queues) => ( + queues.from_websocket_tx.clone(), + queues.to_websocket_rx, + queues.pending_messages, + ), None => { error!("No websocket queues for connection id {connection_id}."); return; } }; - // Keep track of pending messages. - let mut pending_messages: HashMap> = HashMap::new(); - // Shutdown may occur in a controlled process or an immediate websocket // close. If the Client needs to close, it can simply close its // websocket, since the IDE maintains all state (case 2). However, if @@ -1353,18 +1365,19 @@ pub fn client_websocket( while let Some(m) = to_websocket_rx.recv().await { warn!("Dropped queued message {m:?}"); } - to_websocket_rx.close(); // Stop all timers. for (_id, join_handle) in pending_messages.drain() { join_handle.abort(); } } else { + // Don't stop timers; the re-connection may handle them. info!("Websocket re-enqueued."); websocket_queues.lock().unwrap().insert( connection_id.to_string(), WebsocketQueues { from_websocket_tx, to_websocket_rx, + pending_messages, }, ); } @@ -1376,7 +1389,7 @@ pub fn client_websocket( } // Webserver core -// ----------------------------------------------------------------------------- +// -------------- #[actix_web::main] pub async fn main( extension_base_path: Option<&Path>, @@ -1458,13 +1471,9 @@ async fn basic_validator( credentials: BasicAuth, ) -> Result { // Get the provided credentials. - let expected_credentials = &req - .app_data::() - .unwrap() - .credentials - .as_ref() - .unwrap(); - if credentials.user_id() == expected_credentials.username + if let Some(app_state) = &req.app_data::() + && let Some(expected_credentials) = app_state.credentials.as_ref() + && credentials.user_id() == expected_credentials.username && credentials.password() == Some(&expected_credentials.password) { Ok(req) @@ -1500,11 +1509,11 @@ pub fn make_app_data(credentials: Option) -> WebAppState { server_handle: Mutex::new(None), filewatcher_next_connection_id: Mutex::new(0), // Use a dummy value until the server binds to a port. - port: Arc::new(Mutex::new(0)), - processing_task_queue_tx: Arc::new(Mutex::new(HashMap::new())), + port: Mutex::new(0), + processing_task_queue_tx: Mutex::new(HashMap::new()), ide_queues: Arc::new(Mutex::new(HashMap::new())), client_queues: Arc::new(Mutex::new(HashMap::new())), - connection_id: Arc::new(Mutex::new(HashSet::new())), + connection_id: Mutex::new(HashSet::new()), credentials, }) } @@ -1542,7 +1551,7 @@ where } // Utilities -// ----------------------------------------------------------------------------- +// --------- // // Send a response to the client after processing a message from the client. pub async fn send_response(client_tx: &Sender, id: f64, result: MessageResult) { @@ -1676,8 +1685,7 @@ 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() - .unwrap() + .to_slash_lossy() // The convert each part of the path to a URL-encoded string. (This // avoids encoding the slashes.) .split("/") @@ -1699,26 +1707,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. pub fn drop_leading_slash(path_: &str) -> &str { - if path_.starts_with("/") { - let mut chars = path_.chars(); - chars.next(); - chars.as_str() - } else { - path_ - } + path_.strip_prefix('/').unwrap_or(path_) } // Given a `Path`, transform it into a displayable HTML string (with any // necessary escaping). -pub fn path_display(p: &Path) -> String { - escape_html(&simplified(p).to_string_lossy()) +pub fn path_display(p: &Path) -> Cow<'_, str> { + escape_text(simplified(p).to_string_lossy()) } -// Return a Not Found (404) error with the provided HTML body. -pub fn html_not_found(msg: &str) -> HttpResponse { +// Return a Not Found (404) error with the provided text (not HTML) body. +pub fn http_not_found(msg: &str) -> HttpResponse { HttpResponse::NotFound() .content_type(ContentType::html()) - .body(html_wrapper(msg)) + .body(html_wrapper(&escape_text(msg))) } // Wrap the provided HTML body in DOCTYPE/html/head tags. @@ -1739,15 +1741,6 @@ pub fn html_wrapper(body: &str) -> String { ) } -// Given text, escape it so it formats correctly as HTML. This is a translation -// of Python's `html.escape` function. -pub fn escape_html(unsafe_text: &str) -> String { - unsafe_text - .replace('&', "&") - .replace('<', "<") - .replace('>', ">") -} - // This lists all errors produced by calling `get_server_url`. TODO: rework and // re-think the overall error framework. How should I group errors? #[derive(Debug, thiserror::Error)] diff --git a/server/src/webserver/tests.rs b/server/src/webserver/tests.rs index 41ce140f..c47a49af 100644 --- a/server/src/webserver/tests.rs +++ b/server/src/webserver/tests.rs @@ -14,9 +14,9 @@ // the CodeChat Editor. If not, see // [http://www.gnu.org/licenses](http://www.gnu.org/licenses). /// `test.rs` -- Unit tests for the vscode interface -/// ============================================================================ +/// ================================================ // Imports -// ----------------------------------------------------------------------------- +// ------- // // ### Standard library use std::path::{MAIN_SEPARATOR_STR, PathBuf}; @@ -34,7 +34,7 @@ use crate::ide::{filewatcher::FILEWATCHER_PATH_PREFIX, 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 @@ -52,7 +52,7 @@ fn get_server() -> Command { } // Tests -// ----------------------------------------------------------------------------- +// ----- #[test] fn test_url_to_path() { let (temp_dir, test_dir) = prep_test_dir!(); diff --git a/server/tests/cli.rs b/server/tests/cli.rs index 0f3fca96..4d94ade5 100644 --- a/server/tests/cli.rs +++ b/server/tests/cli.rs @@ -14,9 +14,9 @@ // the CodeChat Editor. If not, see // [http://www.gnu.org/licenses](http://www.gnu.org/licenses). /// `cli.rs` - Test the CLI interface -/// ============================================================================ +/// ================================= // Imports -// ----------------------------------------------------------------------------- +// ------- // // ### Standard library // @@ -31,7 +31,7 @@ use predicates::{prelude::predicate, str::contains}; use tokio::task::spawn_blocking; // Support functions -// ----------------------------------------------------------------------------- +// ----------------- // // The lint on using `cargo_bin` doesn't apply, since this is only available for // integration tests per the @@ -48,7 +48,7 @@ fn get_server() -> Command { } // Tests -// ----------------------------------------------------------------------------- +// ----- #[test] fn test_start_not_found() { let mut cmd = get_server(); diff --git a/test_utils/readme.md b/test_utils/readme.md index 74a0e0e5..f3eadf9d 100644 --- a/test_utils/readme.md +++ b/test_utils/readme.md @@ -1,5 +1,5 @@ `readme.md` - Overview of test utilities -================================================================================ +======================================== These test utilities are used both in unit tests and in integration tests. Integration tests can't access code inside a `#[cfg(test)]` configuration diff --git a/test_utils/src/test_macros.rs b/test_utils/src/test_macros.rs index b1220e8d..3253eb54 100644 --- a/test_utils/src/test_macros.rs +++ b/test_utils/src/test_macros.rs @@ -15,14 +15,14 @@ /// [http://www.gnu.org/licenses](http://www.gnu.org/licenses). /// /// `test_macros.rs` -- Reusable macros for testing -/// ============================================================================ +/// =============================================== // Imports -// ----------------------------------------------------------------------------- +// ------- // // None. // // Macros -// ----------------------------------------------------------------------------- +// ------ // // Extract a known enum variant or fail. More concise than the alternative (`if // let`, or `let else`). From [SO](https://stackoverflow.com/a/69324393). The @@ -31,7 +31,7 @@ macro_rules! cast { // For an enum containing a single value (the typical case). ($target: expr, $pat: path) => {{ - // The `if let` exploits recent Rust compiler's smart pattern matching. + // The `if let` exploits the Rust compiler's smart pattern matching. // Contrary to other solutions like `into_variant` and friends, this one // macro covers all ownership usage like `self`, `&self` and `&mut // self`. On the other hand `{into,as,as_mut}_{variant}` solution @@ -52,7 +52,11 @@ macro_rules! cast { if let $pat($($tup,)*) = $target { ($($tup,)*) } else { - panic!("mismatch variant when cast to {}", stringify!($pat)); + panic!( + "mismatch variant when cast to {} with values {}", + stringify!($pat), + stringify!($tup) + ); } }}; } @@ -62,23 +66,13 @@ macro_rules! cast { #[macro_export] macro_rules! function_name { () => {{ - fn f() {} - fn type_name_of(_: T) -> &'static str { + const fn f() {} + const fn type_name_of(_: T) -> &'static str { std::any::type_name::() } let name = type_name_of(f); - // Since we just called the nested function f, strip this off the end of + // Since we just called the nested function `f``, strip this off the end of // the returned value. name.strip_suffix("::f").unwrap() }}; } - -// Call `_prep_test_dir` with the correct parameter -- `function_name!()`. -#[macro_export] -macro_rules! prep_test_dir { - () => {{ - use $crate::function_name; - use $crate::test_utils::_prep_test_dir; - _prep_test_dir(function_name!()) - }}; -} diff --git a/test_utils/src/test_utils.rs b/test_utils/src/test_utils.rs index a947b3c9..f764323d 100644 --- a/test_utils/src/test_utils.rs +++ b/test_utils/src/test_utils.rs @@ -15,9 +15,9 @@ /// [http://www.gnu.org/licenses](http://www.gnu.org/licenses). /// /// `test_utils.rs` -- Reusable routines for testing. -/// ============================================================================ +/// ================================================= // Imports -// ----------------------------------------------------------------------------- +// ------- // // ### Standard library use std::env; @@ -33,7 +33,7 @@ use log::Level; use crate::testing_logger; // Macros -// ----------------------------------------------------------------------------- +// ------ // // Extract a known enum variant or fail. More concise than the alternative (`if // let`, or `let else`). From [SO](https://stackoverflow.com/a/69324393). The @@ -86,31 +86,31 @@ macro_rules! function_name { }}; } -// Call `_prep_test_dir` with the correct parameter -- `function_name!()`. +// Call `prep_test_dir_impl` with the correct parameter -- `function_name!()`. #[macro_export] macro_rules! prep_test_dir { () => {{ use $crate::function_name; - use $crate::test_utils::_prep_test_dir; - _prep_test_dir(function_name!()) + use $crate::test_utils::prep_test_dir_impl; + prep_test_dir_impl(function_name!()) }}; } // Code -// ----------------------------------------------------------------------------- +// ---- // // Use the `tests/fixtures` path (relative to the root of this Rust project) to // store files for testing. A subdirectory tree, named by the module path then // name of the test function by convention, contains everything needed for this // test. Copy this over to a temporary directory, then run tests there. -pub fn _prep_test_dir( +pub fn prep_test_dir_impl( // The name of and Rust path to the test function to prepare files for. Call // `prep_test_dir!()` to provide this parameter. test_full_name: &str, ) -> ( // The temporary directory created which stores files to use in testing. TempDir, - // The + // The directory which contains copied test fixture files. PathBuf, ) { // Get rid of closures in the path. @@ -183,7 +183,7 @@ pub fn check_logger_errors(num_expected_errors: usize) { assert_le!( error_logs.len(), num_expected_errors, - "Error(s) in logs: saw {}, expected {num_expected_errors}.", + "Error(s) in logs: saw {}, expected {num_expected_errors} or fewer.", error_logs.len() ); }); diff --git a/test_utils/src/testing_logger.rs b/test_utils/src/testing_logger.rs index 03edb377..4389b9a3 100644 --- a/test_utils/src/testing_logger.rs +++ b/test_utils/src/testing_logger.rs @@ -1,5 +1,5 @@ // `testing_logger.rs` -- a logger to support unit testing. -// ============================================================================= +// ======================================================== // // This is a minimally-modified version of the // [testing\_logger](https://github.com/brucechapman/rust_testing_logger) crate: @@ -42,12 +42,12 @@ //! Log events are captured in a thread\_local variable so this module behaves //! correctly when tests are run multithreaded. //! -//! All log levels are captured, but none are sent to any logging system. The +//! All debug and higher log levels are captured, but none are sent to any logging system. The //! test developer should use the `validate()` function in order to check the //! captured log messages. //! //! Examples -//! ============================================================================ +//! ======== //! //! ``` //! #[macro_use] @@ -114,15 +114,14 @@ thread_local!(static LOG_RECORDS: RefCell> = RefCell::new(Vec:: struct TestingLogger {} impl Log for TestingLogger { - #[allow(unused_variables)] - fn enabled(&self, metadata: &Metadata) -> bool { + fn enabled(&self, _metadata: &Metadata) -> bool { true // capture all log levels } fn log(&self, record: &Record) { LOG_RECORDS.with(|records| { let captured_record = CapturedLog { - body: format!("{}", record.args()), + body: record.args().to_string(), level: record.level(), target: record.target().to_string(), }; @@ -151,7 +150,7 @@ pub fn setup() { .unwrap(); }); LOG_RECORDS.with(|records| { - records.borrow_mut().truncate(0); + records.borrow_mut().clear(); }); } @@ -161,10 +160,10 @@ pub fn setup() { /// captured log events. As a side effect, the records are cleared. pub fn validate(asserter: F) where - F: Fn(&Vec), + F: Fn(&[CapturedLog]), { LOG_RECORDS.with(|records| { asserter(&records.borrow()); - records.borrow_mut().truncate(0); + records.borrow_mut().clear(); }); } diff --git a/toc.md b/toc.md index f5916827..4c39fde3 100644 --- a/toc.md +++ b/toc.md @@ -1,21 +1,21 @@ The CodeChat Editor -================================================================================ +=================== User documentation -================================================================================ +================== * [The CodeChat Editor manual](README.md) * [The CodeChat Editor extension for Visual Studio Code manual](extensions/VSCode/README.md) * [Literate programming using the CodeChat Editor](docs/style_guide.cpp) Design -================================================================================ +====== * [CodeChat Editor Design](docs/design.md) * [Implementation](docs/implementation.md) Implementation -================================================================================ +============== * [Server](server/readme.md) * [main.rs](server/src/main.rs) @@ -52,9 +52,9 @@ Implementation * [CodeChatEditor.mts](client/src/CodeChatEditor.mts) * [CodeMirror-integration.mts](client/src/CodeMirror-integration.mts) * [tinymce-config.mts](client/src/tinymce-config.mts) - * [graphviz-webcomponent-setup.mjs](client/src/graphviz-webcomponent-setup.mts) + * [graphviz-webcomponent-setup.mjs](client/src/graphviz-webcomponent-setup.mjs) * [Mermaid](client/src/third-party/wc-mermaid/developer.md) - * [shared\_types.mts](client/src/shared_types.mts) + * [shared.mts](client/src/shared.mts) * [assert.mts](client/src/assert.mts) * [show\_toast.mts](client/src/show_toast.mts) * [global.d.ts](client/src/global.d.ts) @@ -76,8 +76,8 @@ Implementation * [Developer documentation](extensions/VSCode/developer.md) * Development tools * Builder - * [builder/Cargo.toml](Cargo.toml) - * [builder/src/main.rs](main.rs) + * [builder/Cargo.toml](builder/Cargo.toml) + * [builder/src/main.rs](builder/src/main.rs) * Git * [server/.gitignore](server/.gitignore) * [client/static/.gitignore](client/static/.gitignore) @@ -101,7 +101,7 @@ Implementation * [dist.toml](server/dist.toml) - additional cargo-dist configuration Misc -================================================================================ +==== * [New project template](new-project-template/README.md) * [Table of contents](toc.md)