diff --git a/CHANGELOG.md b/CHANGELOG.md index a5bd154a..5e3e3e2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,16 @@ Changelog [Github master](https://github.com/bjones1/CodeChat_Editor) -------------------------------------------------------------------------------- -* No changes. +* Fix errors in Client when editing a file with adjacent doc blocks. +* Fix out of sync errors when the table of contents file is open. +* Fix incorrect version info sent by the VSCode extension. +* Change lexer name for C/C++ code to avoid escaping when converting from HTML + to Markdown. +* Fix Client so that edits produces updates to the IDE; previous, edits would + occasionally stop updating the IDE. +* Prevent editing the `` that wraps math expressions. +* Automatically re-sync Client with IDE when out of sync. +* Update to latest release of MathJax. Version 0.1.46 -- 2025-Dec-15 -------------------------------------------------------------------------------- diff --git a/builder/.gitignore b/builder/.gitignore index 2d3f357d..1b33dcd1 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.toml b/builder/Cargo.toml index acfe4994..080ec4ae 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 7849dc8d..333775c6 100644 --- a/builder/src/main.rs +++ b/builder/src/main.rs @@ -14,19 +14,19 @@ // the CodeChat Editor. If not, see // [http://www.gnu.org/licenses](http://www.gnu.org/licenses). /// `main.rs` -- Entrypoint for the CodeChat Editor Builder -/// ======================================================= +/// ============================================================================ /// /// This code uses [dist](https://opensource.axo.dev/cargo-dist/book/) as a part /// of the release process. To update the `./release.yaml` file this tool /// creates: /// -/// 1. Edit `server/dist-workspace.toml`: change `allow-dirty` to `[]`. -/// 2. Run `dist init` and accept the defaults, then run `dist generate`. -/// 3. Review changes to `./release.yaml`, reapplying hand edits. -/// 4. Revert the changes to `server/dist-workspace.toml`. -/// 5. Test +/// 1. Edit `server/dist-workspace.toml`: change `allow-dirty` to `[]`. +/// 2. Run `dist init` and accept the defaults, then run `dist generate`. +/// 3. Review changes to `./release.yaml`, reapplying hand edits. +/// 4. Revert the changes to `server/dist-workspace.toml`. +/// 5. Test // Imports -// ------- +// ----------------------------------------------------------------------------- // // ### Standard library use std::{ @@ -50,7 +50,7 @@ use regex::Regex; // None // // Data structures -// --------------- +// ----------------------------------------------------------------------------- // // The following defines the command-line interface for the CodeChat Editor. #[derive(Parser)] @@ -117,7 +117,7 @@ struct TypeScriptBuildOptions { } // Constants -// --------- +// ----------------------------------------------------------------------------- static VSCODE_PATH: &str = "../extensions/VSCode"; static CLIENT_PATH: &str = "../client"; static BUILDER_PATH: &str = "../builder"; @@ -125,7 +125,7 @@ static TEST_UTILS_PATH: &str = "../test_utils"; static NAPI_TARGET: &str = "NAPI_TARGET"; // Code -// ---- +// ----------------------------------------------------------------------------- // // ### Utilities // @@ -242,8 +242,8 @@ fn quick_copy_dir>(src: P, dest: P, files: Option

) -> io::Resu .status()? .code() .expect("Copy process terminated by signal"); - // Per [these - // docs](https://learn.microsoft.com/en-us/troubleshoot/windows-server/backup-and-storage/return-codes-used-robocopy-utility), + // 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 { Err(io::Error::other(format!( @@ -288,7 +288,7 @@ fn search_and_replace_file< } // Core routines -// ------------- +// ----------------------------------------------------------------------------- // // These functions simplify common build-focused development tasks and support // CI builds. @@ -312,8 +312,17 @@ fn patch_file(patch: &str, before_patch: &str, file_path: &str) -> io::Result<() } /// After updating files in the client's Node files, perform some fix-ups. fn patch_client_libs() -> io::Result<()> { - // In [older - // releases](https://www.tiny.cloud/docs/tinymce/5/6.0-upcoming-changes/#options), + // Apply a the fixes described in + // [issue 27](https://github.com/bjones1/CodeChat_Editor/issues/27). + patch_file( + " + selectionNotFocus = this.view.state.facet(editable) ? focused : hasSelection(this.dom, this.view.observer.selectionRange)", + " let selectionNotFocus = !focused && !(this.view.state.facet(editable) || this.dom.tabIndex > -1) && + hasSelection(this.dom, this.view.observer.selectionRange) && !(activeElt && this.dom.contains(activeElt));", + &format!("{CLIENT_PATH}/node_modules/@codemirror/view/dist/index.js") + )?; + // In + // [older releases](https://www.tiny.cloud/docs/tinymce/5/6.0-upcoming-changes/#options), // TinyMCE allowed users to change `whitespace_elements`; the whitespace // inside these isn't removed by TinyMCE. However, this was removed in v6.0. // Therefore, manually patch TinyMCE instead. @@ -376,8 +385,8 @@ fn run_install(dev: bool) -> io::Result<()> { #[cfg(not(windows))] // The original command had `'=https'`, but single quotes confused // `cmd_lib` and aren't needed to quote this. Note that `//` in the URL - // is a comment in Rust, so it must be [enclosed in - // quotes](https://github.com/rust-shell-script/rust_cmd_lib/issues/88). + // is a comment in Rust, so it must be + // [enclosed in quotes](https://github.com/rust-shell-script/rust_cmd_lib/issues/88). run_cmd! { curl -L --proto =https --tlsv1.2 -sSf "https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh" | bash; }?; @@ -551,7 +560,7 @@ fn run_client_build( true, )?; - // The PDF viewer for use with VSCode. Built it separately, // since it's loaded apart from the rest of the Client. run_script( &esbuild, @@ -634,7 +643,8 @@ fn run_extensions_build( if dist { napi_args.push("--release"); } - // See if this is a cross-platform build -- if so, add in the specified target. + // See if this is a cross-platform build -- if so, add in the specified + // target. let target; if let Ok(tmp) = env::var(NAPI_TARGET) { target = tmp; @@ -730,7 +740,9 @@ fn run_postrelease(target: &str) -> io::Result<()> { "aarch64-apple-darwin" => "darwin-arm64", _ => panic!("Unsupported platform {target}."), }; - // `vsce` will invoke this program's `ext_build`; however, it doesn't provide a way to pass the target when cross-compiling. Use an environment variable instead. + // `vsce` will invoke this program's `ext_build`; however, it doesn't + // provide a way to pass the target when cross-compiling. Use an environment + // variable instead. unsafe { env::set_var(NAPI_TARGET, target); } @@ -754,7 +766,7 @@ fn run_postrelease(target: &str) -> io::Result<()> { } // CLI implementation -// ------------------ +// ----------------------------------------------------------------------------- // // The following code implements the command-line interface for the CodeChat // Editor. diff --git a/client/package.json5 b/client/package.json5 index b526dbe8..90b23828 100644 --- a/client/package.json5 +++ b/client/package.json5 @@ -43,9 +43,9 @@ url: 'https://github.com/bjones1/CodeChat_editor', }, type: 'module', - version: '0.1.46', + version: '0.1.47', dependencies: { - '@codemirror/commands': '^6.10.0', + '@codemirror/commands': '^6.10.1', '@codemirror/lang-cpp': '^6.0.3', '@codemirror/lang-css': '^6.3.1', '@codemirror/lang-go': '^6.0.1', @@ -61,15 +61,15 @@ '@codemirror/lang-xml': '^6.1.0', '@codemirror/lang-yaml': '^6.1.2', '@codemirror/state': '^6.5.2', - '@codemirror/view': '^6.39.4', + '@codemirror/view': '6.38.8', '@hpcc-js/wasm-graphviz': '^1.17.0', - '@mathjax/mathjax-newcm-font': '4.1.0', + '@mathjax/mathjax-newcm-font': '^4.1.0', codemirror: '^6.0.2', - mathjax: '4.0.0', + mathjax: '^4.1.0', mermaid: '^11.12.2', 'npm-check-updates': '^19.2.0', 'pdfjs-dist': '^5.4.449', - tinymce: '^8.3.0', + tinymce: '^8.3.1', 'toastify-js': '^1.12.0', }, devDependencies: { @@ -83,7 +83,7 @@ '@typescript-eslint/eslint-plugin': '^8.50.0', '@typescript-eslint/parser': '^8.50.0', chai: '^6.2.1', - esbuild: '^0.27.1', + esbuild: '^0.27.2', eslint: '^9.39.2', 'eslint-config-prettier': '^10.1.8', 'eslint-plugin-import': '^2.32.0', diff --git a/client/pnpm-lock.yaml b/client/pnpm-lock.yaml index bd981845..a447b9ce 100644 --- a/client/pnpm-lock.yaml +++ b/client/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@codemirror/commands': - specifier: ^6.10.0 - version: 6.10.0 + specifier: ^6.10.1 + version: 6.10.1 '@codemirror/lang-cpp': specifier: ^6.0.3 version: 6.0.3 @@ -57,20 +57,20 @@ importers: specifier: ^6.5.2 version: 6.5.2 '@codemirror/view': - specifier: ^6.39.4 - version: 6.39.4 + specifier: 6.38.8 + version: 6.38.8 '@hpcc-js/wasm-graphviz': specifier: ^1.17.0 version: 1.17.0 '@mathjax/mathjax-newcm-font': - specifier: 4.1.0 + specifier: ^4.1.0 version: 4.1.0 codemirror: specifier: ^6.0.2 version: 6.0.2 mathjax: - specifier: 4.0.0 - version: 4.0.0 + specifier: ^4.1.0 + version: 4.1.0 mermaid: specifier: ^11.12.2 version: 11.12.2 @@ -81,8 +81,8 @@ importers: specifier: ^5.4.449 version: 5.4.449 tinymce: - specifier: ^8.3.0 - version: 8.3.0 + specifier: ^8.3.1 + version: 8.3.1 toastify-js: specifier: ^1.12.0 version: 1.12.0 @@ -118,8 +118,8 @@ importers: specifier: ^6.2.1 version: 6.2.1 esbuild: - specifier: ^0.27.1 - version: 0.27.1 + specifier: ^0.27.2 + version: 0.27.2 eslint: specifier: ^9.39.2 version: 9.39.2 @@ -171,8 +171,8 @@ packages: '@codemirror/autocomplete@6.20.0': resolution: {integrity: sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==} - '@codemirror/commands@6.10.0': - resolution: {integrity: sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w==} + '@codemirror/commands@6.10.1': + resolution: {integrity: sha512-uWDWFypNdQmz2y1LaNJzK7fL7TYKLeUAU0npEC685OKTF3KcQ2Vu3klIM78D7I6wGhktme0lh3CuQLv0ZCrD9Q==} '@codemirror/lang-cpp@6.0.3': resolution: {integrity: sha512-URM26M3vunFFn9/sm6rzqrBzDgfWuDixp85uTY49wKudToc2jTHUrKIGGKs+QWND+YLofNNZpxcNGRynFJfvgA==} @@ -228,161 +228,161 @@ packages: '@codemirror/state@6.5.2': resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==} - '@codemirror/view@6.39.4': - resolution: {integrity: sha512-xMF6OfEAUVY5Waega4juo1QGACfNkNF+aJLqpd8oUJz96ms2zbfQ9Gh35/tI3y8akEV31FruKfj7hBnIU/nkqA==} + '@codemirror/view@6.38.8': + resolution: {integrity: sha512-XcE9fcnkHCbWkjeKyi0lllwXmBLtyYb5dt89dJyx23I9+LSh5vZDIuk7OLG4VM1lgrXZQcY6cxyZyk5WVPRv/A==} - '@esbuild/aix-ppc64@0.27.1': - resolution: {integrity: sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==} + '@esbuild/aix-ppc64@0.27.2': + resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.27.1': - resolution: {integrity: sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==} + '@esbuild/android-arm64@0.27.2': + resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.27.1': - resolution: {integrity: sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==} + '@esbuild/android-arm@0.27.2': + resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.27.1': - resolution: {integrity: sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==} + '@esbuild/android-x64@0.27.2': + resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.27.1': - resolution: {integrity: sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ==} + '@esbuild/darwin-arm64@0.27.2': + resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.27.1': - resolution: {integrity: sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==} + '@esbuild/darwin-x64@0.27.2': + resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.27.1': - resolution: {integrity: sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==} + '@esbuild/freebsd-arm64@0.27.2': + resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.27.1': - resolution: {integrity: sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==} + '@esbuild/freebsd-x64@0.27.2': + resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.27.1': - resolution: {integrity: sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==} + '@esbuild/linux-arm64@0.27.2': + resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.27.1': - resolution: {integrity: sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==} + '@esbuild/linux-arm@0.27.2': + resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.27.1': - resolution: {integrity: sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==} + '@esbuild/linux-ia32@0.27.2': + resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.27.1': - resolution: {integrity: sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==} + '@esbuild/linux-loong64@0.27.2': + resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.27.1': - resolution: {integrity: sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==} + '@esbuild/linux-mips64el@0.27.2': + resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.27.1': - resolution: {integrity: sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==} + '@esbuild/linux-ppc64@0.27.2': + resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.27.1': - resolution: {integrity: sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==} + '@esbuild/linux-riscv64@0.27.2': + resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.27.1': - resolution: {integrity: sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==} + '@esbuild/linux-s390x@0.27.2': + resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.27.1': - resolution: {integrity: sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==} + '@esbuild/linux-x64@0.27.2': + resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.27.1': - resolution: {integrity: sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==} + '@esbuild/netbsd-arm64@0.27.2': + resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.27.1': - resolution: {integrity: sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==} + '@esbuild/netbsd-x64@0.27.2': + resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.27.1': - resolution: {integrity: sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==} + '@esbuild/openbsd-arm64@0.27.2': + resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.27.1': - resolution: {integrity: sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==} + '@esbuild/openbsd-x64@0.27.2': + resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.27.1': - resolution: {integrity: sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==} + '@esbuild/openharmony-arm64@0.27.2': + resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.27.1': - resolution: {integrity: sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==} + '@esbuild/sunos-x64@0.27.2': + resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.27.1': - resolution: {integrity: sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==} + '@esbuild/win32-arm64@0.27.2': + resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.27.1': - resolution: {integrity: sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==} + '@esbuild/win32-ia32@0.27.2': + resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.27.1': - resolution: {integrity: sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==} + '@esbuild/win32-x64@0.27.2': + resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -1193,8 +1193,8 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} - esbuild@0.27.1: - resolution: {integrity: sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==} + esbuild@0.27.2: + resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} engines: {node: '>=18'} hasBin: true @@ -1634,6 +1634,9 @@ packages: lodash-es@4.17.21: resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + lodash-es@4.17.22: + resolution: {integrity: sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==} + lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} @@ -1653,8 +1656,8 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} - mathjax@4.0.0: - resolution: {integrity: sha512-ThMPHiPl9ibZBInAmfoTCNq9MgCdH7ChIQ9YhKFc325noJ4DMzy9/Q14qdcuPzVJjEmC3kyXhwnERZWX3hbWzQ==} + mathjax@4.1.0: + resolution: {integrity: sha512-53eDXzxk40pS2sdI6KDCPoreY95ADaGygbi41ExKmn3FYQ+QIdpquIU90eppecelzQjf74kpScyeplVPccnIJw==} mermaid@11.12.2: resolution: {integrity: sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w==} @@ -1981,8 +1984,8 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} - tinymce@8.3.0: - resolution: {integrity: sha512-9IjrEo8HD5mg9QP6/rKcPSIcyRNVSf5eiYTqapb/q1zAIoISRJgI2DJUs4CJgZvio0hmEH394xSHUJuoGf4Msw==} + tinymce@8.3.1: + resolution: {integrity: sha512-mdQdTAA90aEIyhEteIwy+QQ6UnxPCd3qQ5MlGvvByOvnjyOSdBzBcmnXeqWuhGz3fIs3XBJjIw7JyIMiHjebqw==} toastify-js@1.12.0: resolution: {integrity: sha512-HeMHCO9yLPvP9k0apGSdPUWrUbLnxUKNFzgUoZp1PHCLploIX/4DSQ7V8H25ef+h4iO9n0he7ImfcndnN6nDrQ==} @@ -2158,14 +2161,14 @@ snapshots: dependencies: '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.4 + '@codemirror/view': 6.38.8 '@lezer/common': 1.4.0 - '@codemirror/commands@6.10.0': + '@codemirror/commands@6.10.1': dependencies: '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.4 + '@codemirror/view': 6.38.8 '@lezer/common': 1.4.0 '@codemirror/lang-cpp@6.0.3': @@ -2196,7 +2199,7 @@ snapshots: '@codemirror/lang-javascript': 6.2.4 '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.4 + '@codemirror/view': 6.38.8 '@lezer/common': 1.4.0 '@lezer/css': 1.3.0 '@lezer/html': 1.3.12 @@ -2212,7 +2215,7 @@ snapshots: '@codemirror/language': 6.11.3 '@codemirror/lint': 6.9.2 '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.4 + '@codemirror/view': 6.38.8 '@lezer/common': 1.4.0 '@lezer/javascript': 1.5.4 @@ -2227,7 +2230,7 @@ snapshots: '@codemirror/lang-html': 6.4.11 '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.4 + '@codemirror/view': 6.38.8 '@lezer/common': 1.4.0 '@lezer/markdown': 1.6.1 @@ -2266,7 +2269,7 @@ snapshots: '@codemirror/autocomplete': 6.20.0 '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.4 + '@codemirror/view': 6.38.8 '@lezer/common': 1.4.0 '@lezer/xml': 1.0.6 @@ -2283,7 +2286,7 @@ snapshots: '@codemirror/language@6.11.3': dependencies: '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.4 + '@codemirror/view': 6.38.8 '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 '@lezer/lr': 1.4.5 @@ -2292,102 +2295,102 @@ snapshots: '@codemirror/lint@6.9.2': dependencies: '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.4 + '@codemirror/view': 6.38.8 crelt: 1.0.6 '@codemirror/search@6.5.11': dependencies: '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.4 + '@codemirror/view': 6.38.8 crelt: 1.0.6 '@codemirror/state@6.5.2': dependencies: '@marijn/find-cluster-break': 1.0.2 - '@codemirror/view@6.39.4': + '@codemirror/view@6.38.8': dependencies: '@codemirror/state': 6.5.2 crelt: 1.0.6 style-mod: 4.1.3 w3c-keyname: 2.2.8 - '@esbuild/aix-ppc64@0.27.1': + '@esbuild/aix-ppc64@0.27.2': optional: true - '@esbuild/android-arm64@0.27.1': + '@esbuild/android-arm64@0.27.2': optional: true - '@esbuild/android-arm@0.27.1': + '@esbuild/android-arm@0.27.2': optional: true - '@esbuild/android-x64@0.27.1': + '@esbuild/android-x64@0.27.2': optional: true - '@esbuild/darwin-arm64@0.27.1': + '@esbuild/darwin-arm64@0.27.2': optional: true - '@esbuild/darwin-x64@0.27.1': + '@esbuild/darwin-x64@0.27.2': optional: true - '@esbuild/freebsd-arm64@0.27.1': + '@esbuild/freebsd-arm64@0.27.2': optional: true - '@esbuild/freebsd-x64@0.27.1': + '@esbuild/freebsd-x64@0.27.2': optional: true - '@esbuild/linux-arm64@0.27.1': + '@esbuild/linux-arm64@0.27.2': optional: true - '@esbuild/linux-arm@0.27.1': + '@esbuild/linux-arm@0.27.2': optional: true - '@esbuild/linux-ia32@0.27.1': + '@esbuild/linux-ia32@0.27.2': optional: true - '@esbuild/linux-loong64@0.27.1': + '@esbuild/linux-loong64@0.27.2': optional: true - '@esbuild/linux-mips64el@0.27.1': + '@esbuild/linux-mips64el@0.27.2': optional: true - '@esbuild/linux-ppc64@0.27.1': + '@esbuild/linux-ppc64@0.27.2': optional: true - '@esbuild/linux-riscv64@0.27.1': + '@esbuild/linux-riscv64@0.27.2': optional: true - '@esbuild/linux-s390x@0.27.1': + '@esbuild/linux-s390x@0.27.2': optional: true - '@esbuild/linux-x64@0.27.1': + '@esbuild/linux-x64@0.27.2': optional: true - '@esbuild/netbsd-arm64@0.27.1': + '@esbuild/netbsd-arm64@0.27.2': optional: true - '@esbuild/netbsd-x64@0.27.1': + '@esbuild/netbsd-x64@0.27.2': optional: true - '@esbuild/openbsd-arm64@0.27.1': + '@esbuild/openbsd-arm64@0.27.2': optional: true - '@esbuild/openbsd-x64@0.27.1': + '@esbuild/openbsd-x64@0.27.2': optional: true - '@esbuild/openharmony-arm64@0.27.1': + '@esbuild/openharmony-arm64@0.27.2': optional: true - '@esbuild/sunos-x64@0.27.1': + '@esbuild/sunos-x64@0.27.2': optional: true - '@esbuild/win32-arm64@0.27.1': + '@esbuild/win32-arm64@0.27.2': optional: true - '@esbuild/win32-ia32@0.27.1': + '@esbuild/win32-ia32@0.27.2': optional: true - '@esbuild/win32-x64@0.27.1': + '@esbuild/win32-x64@0.27.2': optional: true '@eslint-community/eslint-utils@4.9.0(eslint@9.39.2)': @@ -2977,7 +2980,7 @@ snapshots: chevrotain-allstar@0.3.1(chevrotain@11.0.3): dependencies: chevrotain: 11.0.3 - lodash-es: 4.17.21 + lodash-es: 4.17.22 chevrotain@11.0.3: dependencies: @@ -3001,12 +3004,12 @@ snapshots: codemirror@6.0.2: dependencies: '@codemirror/autocomplete': 6.20.0 - '@codemirror/commands': 6.10.0 + '@codemirror/commands': 6.10.1 '@codemirror/language': 6.11.3 '@codemirror/lint': 6.9.2 '@codemirror/search': 6.5.11 '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.4 + '@codemirror/view': 6.38.8 color-convert@2.0.1: dependencies: @@ -3220,7 +3223,7 @@ snapshots: dagre-d3-es@7.0.13: dependencies: d3: 7.9.0 - lodash-es: 4.17.21 + lodash-es: 4.17.22 data-view-buffer@1.0.2: dependencies: @@ -3376,34 +3379,34 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 - esbuild@0.27.1: + esbuild@0.27.2: optionalDependencies: - '@esbuild/aix-ppc64': 0.27.1 - '@esbuild/android-arm': 0.27.1 - '@esbuild/android-arm64': 0.27.1 - '@esbuild/android-x64': 0.27.1 - '@esbuild/darwin-arm64': 0.27.1 - '@esbuild/darwin-x64': 0.27.1 - '@esbuild/freebsd-arm64': 0.27.1 - '@esbuild/freebsd-x64': 0.27.1 - '@esbuild/linux-arm': 0.27.1 - '@esbuild/linux-arm64': 0.27.1 - '@esbuild/linux-ia32': 0.27.1 - '@esbuild/linux-loong64': 0.27.1 - '@esbuild/linux-mips64el': 0.27.1 - '@esbuild/linux-ppc64': 0.27.1 - '@esbuild/linux-riscv64': 0.27.1 - '@esbuild/linux-s390x': 0.27.1 - '@esbuild/linux-x64': 0.27.1 - '@esbuild/netbsd-arm64': 0.27.1 - '@esbuild/netbsd-x64': 0.27.1 - '@esbuild/openbsd-arm64': 0.27.1 - '@esbuild/openbsd-x64': 0.27.1 - '@esbuild/openharmony-arm64': 0.27.1 - '@esbuild/sunos-x64': 0.27.1 - '@esbuild/win32-arm64': 0.27.1 - '@esbuild/win32-ia32': 0.27.1 - '@esbuild/win32-x64': 0.27.1 + '@esbuild/aix-ppc64': 0.27.2 + '@esbuild/android-arm': 0.27.2 + '@esbuild/android-arm64': 0.27.2 + '@esbuild/android-x64': 0.27.2 + '@esbuild/darwin-arm64': 0.27.2 + '@esbuild/darwin-x64': 0.27.2 + '@esbuild/freebsd-arm64': 0.27.2 + '@esbuild/freebsd-x64': 0.27.2 + '@esbuild/linux-arm': 0.27.2 + '@esbuild/linux-arm64': 0.27.2 + '@esbuild/linux-ia32': 0.27.2 + '@esbuild/linux-loong64': 0.27.2 + '@esbuild/linux-mips64el': 0.27.2 + '@esbuild/linux-ppc64': 0.27.2 + '@esbuild/linux-riscv64': 0.27.2 + '@esbuild/linux-s390x': 0.27.2 + '@esbuild/linux-x64': 0.27.2 + '@esbuild/netbsd-arm64': 0.27.2 + '@esbuild/netbsd-x64': 0.27.2 + '@esbuild/openbsd-arm64': 0.27.2 + '@esbuild/openbsd-x64': 0.27.2 + '@esbuild/openharmony-arm64': 0.27.2 + '@esbuild/sunos-x64': 0.27.2 + '@esbuild/win32-arm64': 0.27.2 + '@esbuild/win32-ia32': 0.27.2 + '@esbuild/win32-x64': 0.27.2 escalade@3.2.0: {} @@ -3859,6 +3862,8 @@ snapshots: lodash-es@4.17.21: {} + lodash-es@4.17.22: {} + lodash.merge@4.6.2: {} log-symbols@4.1.0: @@ -3872,7 +3877,7 @@ snapshots: math-intrinsics@1.1.0: {} - mathjax@4.0.0: + mathjax@4.1.0: dependencies: '@mathjax/mathjax-newcm-font': 4.1.0 @@ -3892,7 +3897,7 @@ snapshots: dompurify: 3.3.1 katex: 0.16.27 khroma: 2.1.0 - lodash-es: 4.17.21 + lodash-es: 4.17.22 marked: 16.4.2 roughjs: 4.6.6 stylis: 4.3.6 @@ -4273,7 +4278,7 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 - tinymce@8.3.0: {} + tinymce@8.3.1: {} toastify-js@1.12.0: {} diff --git a/client/readme.md b/client/readme.md new file mode 100644 index 00000000..7a30e8b6 --- /dev/null +++ b/client/readme.md @@ -0,0 +1,10 @@ +`readme.md` - overview of the Client +================================================================================ + +Inside the client: + +* The Framework exchanges messages with the Server and loads the appropriate + Client (simple view, PDF view, editor, document-only editor). +* The editor provides basic Client services and handles document-only mode. +* The CodeMirror integration module embeds TinyMCE into CodeMirror, providing + the primary editing environment. diff --git a/client/src/CodeChatEditor-test.mts b/client/src/CodeChatEditor-test.mts index 3f7346ca..2ae94fbf 100644 --- a/client/src/CodeChatEditor-test.mts +++ b/client/src/CodeChatEditor-test.mts @@ -15,13 +15,13 @@ // [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"; @@ -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 6d031c04..eaa52ff2 100644 --- a/client/src/CodeChatEditor.mts +++ b/client/src/CodeChatEditor.mts @@ -62,7 +62,8 @@ import "./graphviz-webcomponent-setup.mts"; // This must be imported *after* the previous setup import, so it's placed here, // instead of in the third-party category above. import "./third-party/graphviz-webcomponent/graph.js"; -import { tinymce, init, Editor } from "./tinymce-config.mjs"; +import { init, tinymce } from "./tinymce-config.mjs"; +import { Editor, EditorEvent, Events } from "tinymce"; import { CodeChatForWeb, CodeMirrorDiffable, @@ -247,11 +248,19 @@ const _open_lp = async ( // [handling editor events](https://www.tiny.cloud/docs/tinymce/6/events/#handling-editor-events), // this is how to create a TinyMCE event handler. setup: (editor: Editor) => { - editor.on("dirty", () => { - tinymce.activeEditor!.setDirty(false); - is_dirty = true; - startAutosaveTimer(); - }); + editor.on( + "dirty", + ( + event: EditorEvent, + ) => { + // Sometimes, `tinymce.activeEditor` is null + // (perhaps when it's not focused). Use the `event` + // data instead. + event.target.setDirty(false); + is_dirty = true; + startAutosaveTimer(); + }, + ); }, }); tinymce.activeEditor!.focus(); @@ -313,7 +322,8 @@ const save_lp = (is_dirty: boolean) => { ) as HTMLDivElement; mathJaxUnTypeset(codechat_body); // To save a document only, simply get the HTML from the only Tiny - // MCE div. Update the `doc_contents` to stay in sync with the Server. + // MCE div. Update the `doc_contents` to stay in sync with the + // Server. doc_content = tinymce.activeEditor!.save(); ( code_mirror_diffable as { @@ -331,8 +341,8 @@ const save_lp = (is_dirty: boolean) => { } update.contents = { metadata: current_metadata, - source: code_mirror_diffable, version: rand(), + source: code_mirror_diffable, }; } @@ -361,7 +371,9 @@ export const saveSelection = () => { (!(current_node as Element).classList.contains( "CodeChat-doc-contents", ) && - // Sometimes, the parent of a custom node (`wc-mermaid`) skips the TinyMCE div and returns the overall div. I don't know why. + // Sometimes, the parent of a custom node (`wc-mermaid`) skips + // the TinyMCE div and returns the overall div. I don't know + // why. !(current_node as Element).classList.contains("CodeChat-doc")); current_node = current_node.parentNode!, is_first = false ) { @@ -373,7 +385,8 @@ export const saveSelection = () => { // `childNodes` change based on whether text nodes (such as a // newline) are included are not after tinyMCE parses the content. const p = current_node.parentNode; - // In case we go off the rails, give up if there are no more parents. + // In case we go off the rails, give up if there are no more + // parents. if (p === null) { return { selection_path: [], diff --git a/client/src/CodeChatEditorFramework.mts b/client/src/CodeChatEditorFramework.mts index 69b7634e..ee30ddc7 100644 --- a/client/src/CodeChatEditorFramework.mts +++ b/client/src/CodeChatEditorFramework.mts @@ -37,6 +37,7 @@ import { CodeChatForWeb, EditorMessage, EditorMessageContents, + KeysOfRustEnum, MessageResult, UpdateMessageContents, } from "./shared_types.mjs"; @@ -134,7 +135,7 @@ class WebSocketComm { assert(message !== undefined); const keys = Object.keys(message); assert(keys.length === 1); - const key = keys[0]; + const key = keys[0] as KeysOfRustEnum; const value = Object.values(message)[0]; // Process this message. @@ -175,7 +176,12 @@ class WebSocketComm { report_error( `Out of sync: Client version ${this.version} !== incoming version ${contents.source.Diff.version}.`, ); - this.send_result(id, "OutOfSync"); + this.send_result(id, { + OutOfSync: [ + this.version, + contents.source.Diff.version, + ], + }); return; } } @@ -282,7 +288,8 @@ class WebSocketComm { const result_contents = value as MessageResult; if ("Err" in result_contents) { report_error( - `Error in message ${id}: ${result_contents.Err}.`, + `Error in message ${id}: ${JSON.stringify(result_contents.Err)}.`, + result_contents.Err, ); } break; diff --git a/client/src/CodeMirror-integration.mts b/client/src/CodeMirror-integration.mts index 18eda907..703bcc39 100644 --- a/client/src/CodeMirror-integration.mts +++ b/client/src/CodeMirror-integration.mts @@ -82,8 +82,8 @@ 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 { Editor, init, tinymce } from "./tinymce-config.mjs"; -import { EditorEvent, Events } from "tinymce"; +import { tinymce, init } from "./tinymce-config.mjs"; +import { Editor, EditorEvent, Events } from "tinymce"; // ### Local import { @@ -792,8 +792,6 @@ export const DocBlockPlugin = ViewPlugin.fromClass( // div it will replace. target.insertBefore(tinymce_div, null); - // Setting the content makes TinyMCE consider it dirty - // -- ignore this "dirty" event. tinymce.activeEditor!.setContent( contents_div.innerHTML, ); @@ -919,7 +917,7 @@ export const CodeMirror_load = async ( case "sh": parser = cpp(); break; - case "c_cpp": + case "cpp": parser = cpp(); break; case "csharp": @@ -1035,10 +1033,10 @@ export const CodeMirror_load = async ( editor.on( "Dirty", (event: EditorEvent) => { - // Get the div TinyMCE stores edits in. TODO: find - // documentation for `event.target.bodyElement`. - tinymce.activeEditor!.setDirty(false); - const target_or_false = event.target?.bodyElement; + // Sometimes, `tinymce.activeEditor` is null (perhaps when it's not focused). Use the `event` data instead. + event.target.setDirty(false); + // Get the div TinyMCE stores edits in. + const target_or_false = event.target.bodyElement; if (target_or_false == null) { return; } diff --git a/client/src/HashReader.mts b/client/src/HashReader.mts index b62ecf5a..514dc3bf 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 diff --git a/client/src/assert.mts b/client/src/assert.mts index f0d23a71..e3b74a4e 100644 --- a/client/src/assert.mts +++ b/client/src/assert.mts @@ -1,5 +1,21 @@ +// Copyright (C) 2025 Bryan A. Jones. +// +// This file is part of the CodeChat Editor. The CodeChat Editor is free +// software: you can redistribute it and/or modify it under the terms of the GNU +// General Public License as published by the Free Software Foundation, either +// version 3 of the License, or (at your option) any later version. +// +// The CodeChat Editor is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with +// the CodeChat Editor. If not, see +// [http://www.gnu.org/licenses](http://www.gnu.org/licenses). +// // `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/CodeChatEditorProject.css b/client/src/css/CodeChatEditorProject.css index 9c886e53..377e2008 100644 --- a/client/src/css/CodeChatEditorProject.css +++ b/client/src/css/CodeChatEditorProject.css @@ -17,10 +17,10 @@ [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/). */ + This is used only to store a reused variable value. See the + [CSS docs](https://drafts.csswg.org/css-variables/). */ :root { --sidebar-width: 15rem; --body-padding: 0.2rem; diff --git a/client/src/graphviz-webcomponent-setup.mts b/client/src/graphviz-webcomponent-setup.mts index 32478e5a..1d2a5431 100644 --- a/client/src/graphviz-webcomponent-setup.mts +++ b/client/src/graphviz-webcomponent-setup.mts @@ -15,15 +15,15 @@ // [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 // [docs](https://github.com/prantlf/graphviz-webcomponent#configuration). // // Note that this must be in a separate module which is imported before the -// graphviz webcomponent; see the [ESBuild -// docs](https://esbuild.github.io/content-types/#real-esm-imports). +// graphviz webcomponent; see the +// [ESBuild docs](https://esbuild.github.io/content-types/#real-esm-imports). /*eslint-disable-next-line @typescript-eslint/no-explicit-any */ (window as any).graphvizWebComponent = { rendererUrl: "/static/bundled/renderer.js", diff --git a/client/src/shared_types.mts b/client/src/shared_types.mts index acb75b55..c0c7200c 100644 --- a/client/src/shared_types.mts +++ b/client/src/shared_types.mts @@ -14,12 +14,18 @@ // the CodeChat Editor. If not, see // [http://www.gnu.org/licenses](http://www.gnu.org/licenses). // -// `shared_types.mts` -- Shared type definitions -// ============================================= -// The time, in ms, to wait between the last user edit and sending updated data to the Server. +// `shared_types.mts` -- Shared type definitionsz +// ============================================================================= +// +// The time, in ms, to wait between the last user edit and sending updated data +// to the Server. export const autosave_timeout_ms = 300; -// Produce a whole random number. Fractional numbers aren't consistently converted to the same number. Note that the mantissa of a JavaScript `Number` is 53 bits per the [docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number#number_encoding). To be certain, also round the result. +// Produce a whole random number. Fractional numbers aren't consistently +// converted to the same number. Note that the mantissa of a JavaScript `Number` +// is 53 bits per the +// [docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number#number_encoding). +// To be certain, also round the result. export const rand = () => Math.round(Math.random() * 2 ** 53); // ### Message types @@ -41,14 +47,19 @@ import { ResultErrTypes } from "./rust-types/ResultErrTypes.js"; // Manually define this, since `ts-rs` can't export `webserver.MessageResult`. type MessageResult = { Ok: ResultOkTypes } | { Err: ResultErrTypes }; +// Modified from [SO](https://stackoverflow.com/a/79050131). If the value is a +// string, use it; otherwise, assume it's a dict and use its key. +type KeysOfRustEnum = T extends T ? (T extends string ? T : keyof T) : never; + export type { - EditorMessageContents, CodeMirror, + CodeMirrorDiffable, CodeMirrorDocBlockTuple, CodeChatForWeb, - StringDiff, - CodeMirrorDiffable, - UpdateMessageContents, EditorMessage, + EditorMessageContents, + KeysOfRustEnum, MessageResult, + StringDiff, + UpdateMessageContents, }; diff --git a/client/src/tinymce-config.mts b/client/src/tinymce-config.mts index 77578f72..2b64624b 100644 --- a/client/src/tinymce-config.mts +++ b/client/src/tinymce-config.mts @@ -25,9 +25,12 @@ import { RawEditorOptions, TinyMCE, } from "tinymce"; -// TODO: The type of tinymce is broken; I don't know why. Here's a workaround. +// This is taken from the +// [TinyMCE example code](https://github.com/tinymce/tinymce/blob/main/modules/tinymce/src/core/demo/ts/demo/TinyMceDemo.ts). +// However, esbuild doesn't accept it. +//export declare const tinymce: TinyMCE; +// Here's a workaround. export const tinymce = tinymce_ as unknown as TinyMCE; -export { Editor }; // Default icons are required for TinyMCE 5.3 or above. import "tinymce/icons/default/index.js"; diff --git a/dist-workspace.toml b/dist-workspace.toml index 3a9d2c60..0eb2a871 100644 --- a/dist-workspace.toml +++ b/dist-workspace.toml @@ -18,7 +18,7 @@ # # `dist-workspace.toml` - Configure # [cargo-dist](https://opensource.axo.dev/cargo-dist/) -# ==================================================== +# ============================================================================== [workspace] members = ["cargo:server/"] diff --git a/docs/design.md b/docs/design.md index fbe59577..798f2453 100644 --- a/docs/design.md +++ b/docs/design.md @@ -26,27 +26,20 @@ These form a set of high-level requirements to guide the project. [code blocks and doc blocks](index.md#code-blocks-and-doc-blocks). Doc blocks are lines of source which contain only correctly-formatted comments. - * Provide support for a [wide variety of programming languages](index.md#programming-language-support). - * Provide integration with a [wide variety of IDEs/text editors](index.md#ide-integration). - * Load a document from source code, allow edits in a GUI, then save it back to source code. - * Provide word processor GUI tools (insert hyperlink, images, headings, change font, etc.) for doc blocks. * Provide text editor/IDE tools (syntax highlighting, line numbers, show linter feedback) for code blocks. * Zero build: eliminate the traditional project build process -- make it almost instantaneous. - * Doc block markup should be readable and well-known: markdown. - * Support both a single-file mode and a project mode. - * A project is a specific directory tree, identified by the presence of a TOC. A TOC is just a plain Markdown file with a specific name. A better term: not a TOC, but a navigation pane, since the TOC can contain anything (see @@ -59,17 +52,20 @@ These form a set of high-level requirements to guide the project. * Provide [authoring support](index.md#authoring-support), which allows authors to easily create book/project-like features. In particular: - * Counters for numbering figures, tables, equations, etc. All counters are page-local (no global counters). + * Auto-titled links: the link text is automatically derived from the link's destination (the heading text at the link's destination; a figure/table caption, etc.). + * Auto-generated back links: anchors support auto-generated links back to all their referents, which can be used for footnotes, endnotes, citations, and indices. To enable this, all forward links must include an anchor and optionally the text to display at the target. + * TOC support which: + * Given some file(s), expands to a nested list of headings in the file(s). Authors may specify the depth of headings to include. * Show the filesystem, optionally not including files that are linked in th @@ -82,6 +78,7 @@ These form a set of high-level requirements to guide the project. the currently viewed file, and tracks headings within the current file. * A gathering element: given an anchor, it shows the context of all hyperlinks to this anchor. + * If the hyperlink is a heading, the context extends to the next same-level heading; * If the hyperlink is a start of context, the context ends at the end of @@ -89,34 +86,45 @@ These form a set of high-level requirements to guide the project. * Otherwise, the context extends to the following code block. * A report view: an extended gathering element that operates more like a query, producing nested, hierarchical results from the codebase. + * Headings can be collapsed, which code code and doc blocks until the next same-level heading. + * A sequencing/path element: given a starting hyperlink, it produces prev/next icons to show a startup/shutdown sequence, etc. + * A graph view: shows the entire document as a directed graph of hyperlinks. + * An inlined output mode, like Jupyter: includes graphs and console output produced by executing the code. + * Graphical code views: + * Present a case statement as a state machine. * Present if/else statements as a truth table. * Visualize data structures. * More? * Interactive learning support: multiple choice, fill-in-th-blank, short/long answer, coding problem, etc. from Runestone or similar. + * Autogenerated anchors for all anchors (headings, hyperlinks, etc.) + * Hyperlinks to identifiers in code (use [ctags](https://github.com/universal-ctags/ctags)); perhaps auto-generate headings for these identifiers? + * An API view; show only parts of the code that's exported/publicly-accessible. + * Substitutions. + * Files/anchors can be freely moved without breaking links. This requires all anchors to be globally unique. HTML allows upper/lowercase ASCII plus the hyphen and underscore for IDs, meaning that a 5-character string provides - >250 million unique anchors. + + > 250 million unique anchors. * Make picking a file/anchor easy: provide a searchable, expanded TOC listing every anchor. - * Provide edit and view options. (Rely on an IDE to edit raw source.) ### Nice to have features @@ -152,17 +160,50 @@ whitespace and optionally succeeded by whitespace. At least one whitespace character must separate the opening comment delimiter from the doc block text. Some examples in C: -

void foo(); // This is not a doc block, because these comments are preceded
void bar(); // by non-whitespace characters. Instead, they're a code block.
//This is not a doc block, because these inline comments lack
//whitespace after the opening comment delimiter //. They're also a code block.
/*This is not a doc block, because this block comment lacks
whitespace after the opening comment delimiter /*. It's also a code block. */
/* This is not a doc block, because non-whitespace
characters follow the closing comment delimiter.
It's also a code block. */ void food();

// This is a doc block. It has no whitespace preceding the inline
// comment delimiters and one character of whitespace following it.
// This is also a doc block. It has two characters of whitespace
// preceding the comment delimiters and one character of whitespace following it.
/* This is a doc block. Because it's based on
a block comment, a single comment can span multiple lines. */
/* This is also a doc block, even without the visual alignment
or a whitespace before the closing comment delimiter.*/
/* This is a doc block
as well. */
+```c +void foo(); // This is not a doc block, because these comments are preceded +void bar(); // by non-whitespace characters. Instead, they're a code block. +//This is not a doc block, because these inline comments lack +//whitespace after the opening comment delimiter //. They're also a code block. +/*This is not a doc block, because this block comment lacks + whitespace after the opening comment delimiter /*. It's also a code block. */ +/* This is not a doc block, because non-whitespace + characters follow the closing comment delimiter. + It's also a code block. */ void food(); + +// This is a doc block. It has no whitespace preceding the inline +// comment delimiters and one character of whitespace following it. + // This is also a doc block. It has two characters of whitespace + // preceding the comment delimiters and one character of whitespace following it. +/* This is a doc block. Because it's based on + a block comment, a single comment can span multiple lines. */ +/* This is also a doc block, even without the visual alignment +or a whitespace before the closing comment delimiter.*/ + /* This is a doc block + as well. */ +``` Doc blocks are differentiated by their indent: the whitespace characters preceding the opening comment delimiter. Adjacent doc blocks with identical indents are combined into a single, larger doc block. -
// This is all one doc block, since only the preceding
// whitespace (there is none) matters, not the amount of
// whitespace following the opening comment delimiters.
// This is the beginning of a different doc
// block, since the indent is different.
// Here's a third doc block; inline and block comments
/* combine as long as the whitespace preceding the comment
delimiters is identical. Whitespace inside the comment doesn't affect
the classification. */
// These are two separate doc blocks,
void foo();
// since they are separated by a code block.
- -### \[Programming language +```c +// This is all one doc block, since only the preceding +// whitespace (there is none) matters, not the amount of +// whitespace following the opening comment delimiters. + // This is the beginning of a different doc + // block, since the indent is different. + // Here's a third doc block; inline and block comments + /* combine as long as the whitespace preceding the comment +delimiters is identical. Whitespace inside the comment doesn't affect + the classification. */ +// These are two separate doc blocks, +void foo(); +// since they are separated by a code block. +``` + +### \[Programming language support\](index.md#programming-language-support) -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) diff --git a/docs/implementation.md b/docs/implementation.md index 8bd9b006..9f4f1bcb 100644 --- a/docs/implementation.md +++ b/docs/implementation.md @@ -21,35 +21,54 @@ Implementation ### System architecture -; - websocket_server [label = "Websocket\nserver"]; - web_server [label = "Web\nserver"]; + websocket_server [label = "Websocket\nserver"]; + web_server [label = "Web\nserver"]; } subgraph cluster_client_framework { - label = "CodeChat Editor Client framework" + label = "CodeChat Editor Client framework" subgraph cluster_client { - label = "CodeChat Editor Client\n(Editor/Viewer/Simple Viewer)" - rendered_code [label = "Rendered code", style = dashed]; + label = "CodeChat Editor Client" + rendered_code [label = "Rendered document", style = dashed]; } } - CodeChat_plugin -> websocket_server [label = "websocket", dir = both]; - websocket_server -> rendered_code [label = "websocket", dir = both, lhead = cluster_client_framework]; - web_server -> rendered_code [label = "HTTP", dir = both, lhead = cluster_client ]; - }"> + CodeChat_plugin -> websocket_server [label = "NAPI-RS", dir = both, lhead = cluster_server]; + websocket_server -> rendered_code [label = "websocket", dir = both, lhead = cluster_client_framework]; + web_server -> rendered_code [label = "HTTP", dir = both, lhead = cluster_client ]; +} +``` + +Inside the client: + +* The Framework exchanges messages with the Server and loads the appropriate + Client (simple view, PDF view, editor, document-only editor). +* The editor provides basic Client services and handles document-only mode. +* The CodeMirror integration module embeds TinyMCE into CodeMirror, providing + the primary editing environment. + +The entire VSCode interface is contained in the extension, with the NAPI-RS glue +in the corresponding library. + +Does this make more sense to place in the TOC? Or is it too wordy there? I think +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: + ### Client/server partitioning Doc blocks consist of Markdown augmented with custom HTML elements which provide @@ -246,14 +265,20 @@ So, this project contains Rust code to automate this process -- see the Misc topics -------------------------------------------------------------------------------- -### CodeChat Editor Client Simple Viewer +### CodeChat Editor Client Viewer Types + +The Client supports several classes of files: -When in project mode, the CodeChat Editor cannot edit some files -- -miscellaneous text files, unsupported languages, images, video, etc. The simple -viewer displays (without allowing editing) these files as raw text in the -browser, though wrapped in the appropriate project structure (with a TOC on the -left). For the Visual Studio Code extension, the simple viewer also provides -support for viewing PDFs (which VSCode's built-in web browser doesn't support). +* Source files, rendered as intermingled code/doc blocks. +* Document-only files -- these contain only Markdown and typically have a file + extension of `.md`. +* Unsupported text files -- the CodeChat Editor cannot edit some files, such as + miscellaneous text files, unsupported languages, images, video, etc. The + simple viewer displays (without allowing editing) these files as raw text in + the browser, though wrapped in the appropriate project structure (with a TOC + on the left). +* PDFs, where a plugin viewer for VSCode provides rendering, since the built-in + browser doesn't. ### Broken fences (Markdown challenges) @@ -284,12 +309,11 @@ Future work headings. Implement numbering using CSS variables, which makes it easy for a style sheet to include or exclude section numbers: - `:root {` `--section-counter-reset: s1 4 s2 5;` - `--section-counter-content: counter(s1, numeric) '-' counter(s2, numeric);` - `}` + `:root {` `--section-counter-reset: s1 4 s2 5;` `--section-counter-content: + counter(s1, numeric) '-' counter(s2, numeric);` `}` - `h1::before {` `counter-reset: var(--section-counter-reset);` - `content: var(--section-counter-content);` `}` + `h1::before {` `counter-reset: var(--section-counter-reset);` `content: + var(--section-counter-content);` `}` ### Cached state @@ -462,41 +486,43 @@ Organization As shown in the figure below, the CodeChat Editor Client starts with `client/package.json`, which tells [NPM](https://en.wikipedia.org/wiki/Npm_(software)) which JavaScript libraries -are used in this project. Running `npm update` copies these libraries and all +are used in this project. Running `npm update` copies these libraries and all their dependencies to the `client/node_modules` directory. The CodeChat Editor Client source code (see [CodeChatEditor.mts](../client/src/CodeChatEditor.mts)) imports these libraries. Next, [esbuild](https://esbuild.github.io/) analyzes the CodeChat Editor client -based by transforming any [TypeScript](https://www.typescriptlang.org/) into +based by transforming any [TypeScript](https://www.typescriptlang.org/) into JavaScript then packaging all dependencies (JavaScript, CSS, images, etc.) into a smaller set of files. At a user's request, the CodeChat Editor Server generates HTML which creates an editor around the user-requested file. This HTML loads the packaged dependencies to create the CodeChat Editor Client webpage. - JS_lib [label = "npm update"] JS_lib -> esbuild; - CCE_source [label = "CodeChat Editor\nClient source"] - JS_lib -> CCE_source [label = "imports"] + CCE_source [label = "CodeChat Editor\nClient source"] + JS_lib -> CCE_source [label = "imports"] CCE_source -> esbuild - esbuild -> "Bundled JavaScript" - esbuild -> "Bundle metadata" - "Bundle metadata" -> "HashReader.mts" - "HashReader.mts" -> server_HTML - CCE_webpage [label = "CodeChat Editor\nClient webpage"] - "Bundled JavaScript" -> CCE_webpage - server_HTML [label = "CodeChat Editor\nServer-generated\nHTML"] + esbuild -> "Bundled JavaScript" + esbuild -> "Bundle metadata" + "Bundle metadata" -> "HashReader.mts" + "HashReader.mts" -> server_HTML + CCE_webpage [label = "CodeChat Editor\nClient webpage"] + "Bundled JavaScript" -> CCE_webpage + server_HTML [label = "CodeChat Editor\nServer-generated\nHTML"] server_HTML -> CCE_webpage - }"> +} +``` Note: to edit these diagrams, use an [HTML entity encoder/decoder](https://mothereff.in/html-entities) and a Graphviz editor such as [Edotor](https://edotor.net/). -TODO: GUIs using TinyMCE. See -the [how-to guide](https://www.tiny.cloud/docs/tinymce/6/dialog-components/#panel-components). +TODO: GUIs using TinyMCE. See the +[how-to guide](https://www.tiny.cloud/docs/tinymce/6/dialog-components/#panel-components). Code style -------------------------------------------------------------------------------- diff --git a/extensions/VSCode/Cargo.lock b/extensions/VSCode/Cargo.lock index e67fccf7..29c67833 100644 --- a/extensions/VSCode/Cargo.lock +++ b/extensions/VSCode/Cargo.lock @@ -362,9 +362,9 @@ dependencies = [ [[package]] name = "assertables" -version = "9.8.2" +version = "9.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59051ec02907378a67b0ba1b8631121f5388c8dbbb3cec8c749d8f93c2c3c211" +checksum = "cbada39b42413d4db3d9460f6e791702490c40f72924378a1b6fc1a4181188fd" [[package]] name = "async-trait" @@ -443,9 +443,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" dependencies = [ "allocator-api2", ] @@ -476,9 +476,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.49" +version = "1.2.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" dependencies = [ "find-msvc-tools", "jobserver", @@ -553,7 +553,7 @@ checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "codechat-editor-server" -version = "0.1.46" +version = "0.1.47" dependencies = [ "actix-files", "actix-http", @@ -605,7 +605,7 @@ dependencies = [ [[package]] name = "codechat-editor-vscode-extension" -version = "0.1.46" +version = "0.1.47" dependencies = [ "codechat-editor-server", "log", @@ -1165,7 +1165,7 @@ dependencies = [ [[package]] name = "htmd" version = "0.5.0" -source = "git+https://github.com/bjones1/htmd.git?branch=dom-interface#a8a3c34143ad5641baa784e43bc73668dbbebc2f" +source = "git+https://github.com/bjones1/htmd.git?branch=dom-interface#433e7166825967e6a9e14a328946135e971f0f39" dependencies = [ "html5ever", "markup5ever_rcdom", @@ -1520,13 +1520,13 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" dependencies = [ "bitflags 2.10.0", "libc", - "redox_syscall", + "redox_syscall 0.6.0", ] [[package]] @@ -1708,9 +1708,9 @@ checksum = "dce6dd36094cac388f119d2e9dc82dc730ef91c32a6222170d630e5414b956e6" [[package]] name = "napi" -version = "3.7.0" +version = "3.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27a163b545fd2184d2efdccf3d3df56acdb63465f2fcfebcaee0463c1e91783" +checksum = "b2e120bab0f106264eec9f55c3d339a0fad90cc46e0905983b1b53be19e42be6" dependencies = [ "bitflags 2.10.0", "ctor", @@ -1730,9 +1730,9 @@ checksum = "d376940fd5b723c6893cd1ee3f33abbfd86acb1cd1ec079f3ab04a2a3bc4d3b1" [[package]] name = "napi-derive" -version = "3.4.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47cffa09ea668c4cc5d7b1198780882e28780ed1804a903b80680725426223d9" +checksum = "6e9161f3ef917aa415ee0bf123656ccee609db7752582b213d59d9e69f37586a" dependencies = [ "convert_case", "ctor", @@ -1744,9 +1744,9 @@ dependencies = [ [[package]] name = "napi-derive-backend" -version = "4.0.0" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e186227ec22f4675267a176d98dffecb27e6cc88926cbb7efb5427268565c0f" +checksum = "724788c8ae2e79ba98905ced435d414c01e1444c91bf43abab455d62eb565b1c" dependencies = [ "convert_case", "proc-macro2", @@ -1904,7 +1904,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.18", "smallvec", "windows-link", ] @@ -2213,6 +2213,15 @@ dependencies = [ "bitflags 2.10.0", ] +[[package]] +name = "redox_syscall" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec96166dafa0886eb81fe1c0a388bece180fbef2135f97c1e2cf8302e74b43b5" +dependencies = [ + "bitflags 2.10.0", +] + [[package]] name = "regex" version = "1.12.2" @@ -2778,9 +2787,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -2801,9 +2810,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", ] diff --git a/extensions/VSCode/Cargo.toml b/extensions/VSCode/Cargo.toml index 44269383..1871df83 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,17 +32,11 @@ license = "GPL-3.0-only" name = "codechat-editor-vscode-extension" readme = "../README.md" repository = "https://github.com/bjones1/CodeChat_Editor" -version = "0.1.46" +version = "0.1.47" [lib] crate-type = ["cdylib"] -# To make VSCode happy, create a unused `int_test` feature, then enable this -# feature in the VSCode Rust language server for a better experience. See -# `server/Cargo.toml` for its actual use. -[features] -int_tests = [] - [dependencies] codechat-editor-server = { path = "../../server" } log = "0.4.28" diff --git a/extensions/VSCode/package.json b/extensions/VSCode/package.json index 6f536bee..fc22eef1 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.46", + "version": "0.1.47", "activationEvents": [ "onCommand:extension.codeChatEditorActivate", "onCommand:extension.codeChatEditorDeactivate" @@ -92,7 +92,7 @@ "@typescript-eslint/parser": "^8.50.0", "@vscode/vsce": "^3.7.1", "chalk": "^5.6.2", - "esbuild": "^0.27.1", + "esbuild": "^0.27.2", "eslint": "^9.39.2", "eslint-config-prettier": "^10.1.8", "eslint-plugin-import": "^2.32.0", @@ -105,7 +105,7 @@ "typescript-eslint": "^8.50.0" }, "optionalDependencies": { - "bufferutil": "^4.0.9" + "bufferutil": "^4.1.0" }, "scripts": { "compile": "cargo run --manifest-path=../../builder/Cargo.toml ext-build", diff --git a/extensions/VSCode/pnpm-lock.yaml b/extensions/VSCode/pnpm-lock.yaml index 0a501946..cf32b113 100644 --- a/extensions/VSCode/pnpm-lock.yaml +++ b/extensions/VSCode/pnpm-lock.yaml @@ -49,8 +49,8 @@ importers: specifier: ^5.6.2 version: 5.6.2 esbuild: - specifier: ^0.27.1 - version: 0.27.1 + specifier: ^0.27.2 + version: 0.27.2 eslint: specifier: ^9.39.2 version: 9.39.2 @@ -83,8 +83,8 @@ importers: version: 8.50.0(eslint@9.39.2)(typescript@5.9.3) optionalDependencies: bufferutil: - specifier: ^4.0.9 - version: 4.0.9 + specifier: ^4.1.0 + version: 4.1.0 packages: @@ -155,158 +155,158 @@ packages: '@emnapi/wasi-threads@1.1.0': resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} - '@esbuild/aix-ppc64@0.27.1': - resolution: {integrity: sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==} + '@esbuild/aix-ppc64@0.27.2': + resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.27.1': - resolution: {integrity: sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==} + '@esbuild/android-arm64@0.27.2': + resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.27.1': - resolution: {integrity: sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==} + '@esbuild/android-arm@0.27.2': + resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.27.1': - resolution: {integrity: sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==} + '@esbuild/android-x64@0.27.2': + resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.27.1': - resolution: {integrity: sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ==} + '@esbuild/darwin-arm64@0.27.2': + resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.27.1': - resolution: {integrity: sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==} + '@esbuild/darwin-x64@0.27.2': + resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.27.1': - resolution: {integrity: sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==} + '@esbuild/freebsd-arm64@0.27.2': + resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.27.1': - resolution: {integrity: sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==} + '@esbuild/freebsd-x64@0.27.2': + resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.27.1': - resolution: {integrity: sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==} + '@esbuild/linux-arm64@0.27.2': + resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.27.1': - resolution: {integrity: sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==} + '@esbuild/linux-arm@0.27.2': + resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.27.1': - resolution: {integrity: sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==} + '@esbuild/linux-ia32@0.27.2': + resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.27.1': - resolution: {integrity: sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==} + '@esbuild/linux-loong64@0.27.2': + resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.27.1': - resolution: {integrity: sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==} + '@esbuild/linux-mips64el@0.27.2': + resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.27.1': - resolution: {integrity: sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==} + '@esbuild/linux-ppc64@0.27.2': + resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.27.1': - resolution: {integrity: sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==} + '@esbuild/linux-riscv64@0.27.2': + resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.27.1': - resolution: {integrity: sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==} + '@esbuild/linux-s390x@0.27.2': + resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.27.1': - resolution: {integrity: sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==} + '@esbuild/linux-x64@0.27.2': + resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.27.1': - resolution: {integrity: sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==} + '@esbuild/netbsd-arm64@0.27.2': + resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.27.1': - resolution: {integrity: sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==} + '@esbuild/netbsd-x64@0.27.2': + resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.27.1': - resolution: {integrity: sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==} + '@esbuild/openbsd-arm64@0.27.2': + resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.27.1': - resolution: {integrity: sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==} + '@esbuild/openbsd-x64@0.27.2': + resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.27.1': - resolution: {integrity: sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==} + '@esbuild/openharmony-arm64@0.27.2': + resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.27.1': - resolution: {integrity: sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==} + '@esbuild/sunos-x64@0.27.2': + resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.27.1': - resolution: {integrity: sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==} + '@esbuild/win32-arm64@0.27.2': + resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.27.1': - resolution: {integrity: sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==} + '@esbuild/win32-ia32@0.27.2': + resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.27.1': - resolution: {integrity: sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==} + '@esbuild/win32-x64@0.27.2': + resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -1338,8 +1338,8 @@ packages: buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - bufferutil@4.0.9: - resolution: {integrity: sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==} + bufferutil@4.1.0: + resolution: {integrity: sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==} engines: {node: '>=6.14.2'} bundle-name@4.1.0: @@ -1600,8 +1600,8 @@ packages: es-toolkit@1.43.0: resolution: {integrity: sha512-SKCT8AsWvYzBBuUqMk4NPwFlSdqLpJwmy6AP322ERn8W2YLIB6JBXnwMI2Qsh2gfphT3q7EKAxKb23cvFHFwKA==} - esbuild@0.27.1: - resolution: {integrity: sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==} + esbuild@0.27.2: + resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} engines: {node: '>=18'} hasBin: true @@ -1812,8 +1812,8 @@ packages: fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - fs-extra@11.3.2: - resolution: {integrity: sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==} + fs-extra@11.3.3: + resolution: {integrity: sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==} engines: {node: '>=14.14'} function-bind@1.1.2: @@ -3080,82 +3080,82 @@ snapshots: dependencies: tslib: 2.8.1 - '@esbuild/aix-ppc64@0.27.1': + '@esbuild/aix-ppc64@0.27.2': optional: true - '@esbuild/android-arm64@0.27.1': + '@esbuild/android-arm64@0.27.2': optional: true - '@esbuild/android-arm@0.27.1': + '@esbuild/android-arm@0.27.2': optional: true - '@esbuild/android-x64@0.27.1': + '@esbuild/android-x64@0.27.2': optional: true - '@esbuild/darwin-arm64@0.27.1': + '@esbuild/darwin-arm64@0.27.2': optional: true - '@esbuild/darwin-x64@0.27.1': + '@esbuild/darwin-x64@0.27.2': optional: true - '@esbuild/freebsd-arm64@0.27.1': + '@esbuild/freebsd-arm64@0.27.2': optional: true - '@esbuild/freebsd-x64@0.27.1': + '@esbuild/freebsd-x64@0.27.2': optional: true - '@esbuild/linux-arm64@0.27.1': + '@esbuild/linux-arm64@0.27.2': optional: true - '@esbuild/linux-arm@0.27.1': + '@esbuild/linux-arm@0.27.2': optional: true - '@esbuild/linux-ia32@0.27.1': + '@esbuild/linux-ia32@0.27.2': optional: true - '@esbuild/linux-loong64@0.27.1': + '@esbuild/linux-loong64@0.27.2': optional: true - '@esbuild/linux-mips64el@0.27.1': + '@esbuild/linux-mips64el@0.27.2': optional: true - '@esbuild/linux-ppc64@0.27.1': + '@esbuild/linux-ppc64@0.27.2': optional: true - '@esbuild/linux-riscv64@0.27.1': + '@esbuild/linux-riscv64@0.27.2': optional: true - '@esbuild/linux-s390x@0.27.1': + '@esbuild/linux-s390x@0.27.2': optional: true - '@esbuild/linux-x64@0.27.1': + '@esbuild/linux-x64@0.27.2': optional: true - '@esbuild/netbsd-arm64@0.27.1': + '@esbuild/netbsd-arm64@0.27.2': optional: true - '@esbuild/netbsd-x64@0.27.1': + '@esbuild/netbsd-x64@0.27.2': optional: true - '@esbuild/openbsd-arm64@0.27.1': + '@esbuild/openbsd-arm64@0.27.2': optional: true - '@esbuild/openbsd-x64@0.27.1': + '@esbuild/openbsd-x64@0.27.2': optional: true - '@esbuild/openharmony-arm64@0.27.1': + '@esbuild/openharmony-arm64@0.27.2': optional: true - '@esbuild/sunos-x64@0.27.1': + '@esbuild/sunos-x64@0.27.2': optional: true - '@esbuild/win32-arm64@0.27.1': + '@esbuild/win32-arm64@0.27.2': optional: true - '@esbuild/win32-ia32@0.27.1': + '@esbuild/win32-ia32@0.27.2': optional: true - '@esbuild/win32-x64@0.27.1': + '@esbuild/win32-x64@0.27.2': optional: true '@eslint-community/eslint-utils@4.9.0(eslint@9.39.2)': @@ -4189,7 +4189,7 @@ snapshots: ieee754: 1.2.1 optional: true - bufferutil@4.0.9: + bufferutil@4.1.0: dependencies: node-gyp-build: 4.8.4 optional: true @@ -4504,34 +4504,34 @@ snapshots: es-toolkit@1.43.0: {} - esbuild@0.27.1: + esbuild@0.27.2: optionalDependencies: - '@esbuild/aix-ppc64': 0.27.1 - '@esbuild/android-arm': 0.27.1 - '@esbuild/android-arm64': 0.27.1 - '@esbuild/android-x64': 0.27.1 - '@esbuild/darwin-arm64': 0.27.1 - '@esbuild/darwin-x64': 0.27.1 - '@esbuild/freebsd-arm64': 0.27.1 - '@esbuild/freebsd-x64': 0.27.1 - '@esbuild/linux-arm': 0.27.1 - '@esbuild/linux-arm64': 0.27.1 - '@esbuild/linux-ia32': 0.27.1 - '@esbuild/linux-loong64': 0.27.1 - '@esbuild/linux-mips64el': 0.27.1 - '@esbuild/linux-ppc64': 0.27.1 - '@esbuild/linux-riscv64': 0.27.1 - '@esbuild/linux-s390x': 0.27.1 - '@esbuild/linux-x64': 0.27.1 - '@esbuild/netbsd-arm64': 0.27.1 - '@esbuild/netbsd-x64': 0.27.1 - '@esbuild/openbsd-arm64': 0.27.1 - '@esbuild/openbsd-x64': 0.27.1 - '@esbuild/openharmony-arm64': 0.27.1 - '@esbuild/sunos-x64': 0.27.1 - '@esbuild/win32-arm64': 0.27.1 - '@esbuild/win32-ia32': 0.27.1 - '@esbuild/win32-x64': 0.27.1 + '@esbuild/aix-ppc64': 0.27.2 + '@esbuild/android-arm': 0.27.2 + '@esbuild/android-arm64': 0.27.2 + '@esbuild/android-x64': 0.27.2 + '@esbuild/darwin-arm64': 0.27.2 + '@esbuild/darwin-x64': 0.27.2 + '@esbuild/freebsd-arm64': 0.27.2 + '@esbuild/freebsd-x64': 0.27.2 + '@esbuild/linux-arm': 0.27.2 + '@esbuild/linux-arm64': 0.27.2 + '@esbuild/linux-ia32': 0.27.2 + '@esbuild/linux-loong64': 0.27.2 + '@esbuild/linux-mips64el': 0.27.2 + '@esbuild/linux-ppc64': 0.27.2 + '@esbuild/linux-riscv64': 0.27.2 + '@esbuild/linux-s390x': 0.27.2 + '@esbuild/linux-x64': 0.27.2 + '@esbuild/netbsd-arm64': 0.27.2 + '@esbuild/netbsd-x64': 0.27.2 + '@esbuild/openbsd-arm64': 0.27.2 + '@esbuild/openbsd-x64': 0.27.2 + '@esbuild/openharmony-arm64': 0.27.2 + '@esbuild/sunos-x64': 0.27.2 + '@esbuild/win32-arm64': 0.27.2 + '@esbuild/win32-ia32': 0.27.2 + '@esbuild/win32-x64': 0.27.2 escape-html@1.0.3: {} @@ -4762,7 +4762,7 @@ snapshots: fs-constants@1.0.0: optional: true - fs-extra@11.3.2: + fs-extra@11.3.3: dependencies: graceful-fs: 4.2.11 jsonfile: 6.2.0 @@ -5264,7 +5264,7 @@ snapshots: node-sarif-builder@3.3.1: dependencies: '@types/sarif': 2.1.7 - fs-extra: 11.3.2 + fs-extra: 11.3.3 normalize-package-data@6.0.2: dependencies: diff --git a/extensions/VSCode/src/extension.ts b/extensions/VSCode/src/extension.ts index 938fd836..02ff419d 100644 --- a/extensions/VSCode/src/extension.ts +++ b/extensions/VSCode/src/extension.ts @@ -41,6 +41,8 @@ import { CodeChatEditorServer, initServer } from "./index.js"; import { autosave_timeout_ms, EditorMessage, + EditorMessageContents, + KeysOfRustEnum, MessageResult, rand, UpdateMessageContents, @@ -162,7 +164,8 @@ export const activate = (context: vscode.ExtensionContext) => { ignore_active_editor_change = false; return; } - // Skip an update if we've already sent a `CurrentFile` for this editor. + // Skip an update if we've already sent a + // `CurrentFile` for this editor. if ( current_editor === vscode.window.activeTextEditor @@ -314,7 +317,8 @@ export const activate = (context: vscode.ExtensionContext) => { } const keys = Object.keys(message); assert(keys.length === 1); - const key = keys[0]; + const key = + keys[0] as KeysOfRustEnum; const value = Object.values(message)[0]; // Process this message. @@ -360,7 +364,12 @@ export const activate = (context: vscode.ExtensionContext) => { // If this diff was not made against the // text we currently have, reject it. if (source.Diff.version !== version) { - await sendResult(id, "OutOfSync"); + await sendResult(id, { + OutOfSync: [ + version, + source.Diff.version, + ], + }); // Send an `Update` with the full text to // re-sync the Client. console_log( @@ -513,27 +522,49 @@ export const activate = (context: vscode.ExtensionContext) => { // Report if this was an error. const result_contents = value as MessageResult; if ("Err" in result_contents) { - show_error( - `Error in message ${id}: ${result_contents.Err}`, - ); + const err = result_contents[ + "Err" + ] as ResultErrTypes; + if ( + err instanceof Object && + "OutOfSync" in err + ) { + // Send an update to re-sync the Client. + console.warn( + "Client is out of sync; resyncing.", + ); + 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)}`, + ); + } } break; } case "LoadFile": { - const load_file = value as string; + const [load_file, is_current] = value as [ + string, + boolean, + ]; // Look through all open documents to see if we have // the requested file. const doc = get_document(load_file); + // If we have this file and the request is for the + // current file to edit/view in the Client, assign a + // version. + if (doc !== undefined && is_current) { + version = rand(); + } const load_file_result: null | [string, number] = doc === undefined ? null - : [ - doc.getText(), - (version = Math.random()), - ]; + : [doc.getText(), version]; console_log( - `CodeChat Editor extension: Result(LoadFile(${format_struct(load_file_result)}))`, + `CodeChat Editor extension: Result(LoadFile(id = ${id}, ${format_struct(load_file_result)}))`, ); await codeChatEditorServer.sendResultLoadfile( id, @@ -584,7 +615,7 @@ const format_struct = (complex_data_structure: any): string => DEBUG_ENABLED ? JSON.stringify( // If the struct is `undefined`, print an empty string. - complex_data_structure ?? "", + complex_data_structure ?? "null/undefined", ).substring(0, MAX_MESSAGE_LENGTH) : ""; diff --git a/extensions/VSCode/src/lib.rs b/extensions/VSCode/src/lib.rs index a0108ddf..bef47679 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( diff --git a/extensions/readme.md b/extensions/readme.md new file mode 100644 index 00000000..43f52547 --- /dev/null +++ b/extensions/readme.md @@ -0,0 +1,14 @@ +`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 +implement most of the program's functionality in the Server and Client, and to +provide generic interfaces as a part of the server to IDEs (see ide.rs). + +Currently, the system supports two IDEs: + +* Visual Studio Code. This is the primary platform for the CodeChat Editor. +* Universal -- the file watcher extension allows use with any IDE by looking for + changes made to the current file and automatically reloading it when changes + are made. diff --git a/extensions/universal.md b/extensions/universal.md deleted file mode 100644 index 2dc4a90b..00000000 --- a/extensions/universal.md +++ /dev/null @@ -1,8 +0,0 @@ -**NOTE**: This file was copied from a previous project and needs significant -editing. - -# Universal extensions/plug-ins - -The CodeChat Server offers a modes that will work with almost any text editor or IDE: - -- A `file change watcher ` which renders any file as soon as it's saved. diff --git a/server/Cargo.lock b/server/Cargo.lock index 9ea76c0f..14feccf3 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -455,9 +455,9 @@ dependencies = [ [[package]] name = "assertables" -version = "9.8.2" +version = "9.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59051ec02907378a67b0ba1b8631121f5388c8dbbb3cec8c749d8f93c2c3c211" +checksum = "cbada39b42413d4db3d9460f6e791702490c40f72924378a1b6fc1a4181188fd" [[package]] name = "async-trait" @@ -589,9 +589,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" dependencies = [ "allocator-api2", ] @@ -651,9 +651,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.49" +version = "1.2.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" dependencies = [ "find-msvc-tools", "jobserver", @@ -746,7 +746,7 @@ checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "codechat-editor-server" -version = "0.1.46" +version = "0.1.47" dependencies = [ "actix-files", "actix-http", @@ -1582,7 +1582,7 @@ dependencies = [ [[package]] name = "htmd" version = "0.5.0" -source = "git+https://github.com/bjones1/htmd.git?branch=dom-interface#a8a3c34143ad5641baa784e43bc73668dbbebc2f" +source = "git+https://github.com/bjones1/htmd.git?branch=dom-interface#433e7166825967e6a9e14a328946135e971f0f39" dependencies = [ "html5ever", "markup5ever_rcdom", @@ -2105,13 +2105,13 @@ checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libredox" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" dependencies = [ "bitflags 2.10.0", "libc", - "redox_syscall", + "redox_syscall 0.6.0", ] [[package]] @@ -2485,7 +2485,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.18", "smallvec", "windows-link", ] @@ -2923,6 +2923,15 @@ dependencies = [ "bitflags 2.10.0", ] +[[package]] +name = "redox_syscall" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec96166dafa0886eb81fe1c0a388bece180fbef2135f97c1e2cf8302e74b43b5" +dependencies = [ + "bitflags 2.10.0", +] + [[package]] name = "redox_users" version = "0.5.2" @@ -3073,9 +3082,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" +checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" dependencies = [ "web-time", "zeroize", @@ -3258,9 +3267,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" dependencies = [ "serde_core", ] @@ -3813,9 +3822,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.8" +version = "0.9.10+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" dependencies = [ "indexmap 2.12.1", "serde_core", @@ -3828,27 +3837,27 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "tower" @@ -3897,9 +3906,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -3920,9 +3929,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", ] diff --git a/server/Cargo.toml b/server/Cargo.toml index d8fe63e5..37a08670 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.46" +version = "0.1.47" # This library allows other packages to use core CodeChat Editor features. [lib] diff --git a/server/dist.toml b/server/dist.toml index 21b7d0b9..2af96cc6 100644 --- a/server/dist.toml +++ b/server/dist.toml @@ -17,7 +17,7 @@ # [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/). # # `dist.toml` - Configure [cargo-dist](https://opensource.axo.dev/cargo-dist/) -# ============================================================================ +# ============================================================================== # # Config for 'dist' [dist] diff --git a/server/readme.md b/server/readme.md new file mode 100644 index 00000000..bd455f1f --- /dev/null +++ b/server/readme.md @@ -0,0 +1,16 @@ +`readme.md` - Overview of the Server +================================================================================ + +Overall: + +* The webserver module runs the overall Server. +* The translation module translates messages sent between the IDE and the + Client. + * The ide module provides an API interface between the Server and an IDE that + uses language-neutral data structures. Its submodules provide IDE-specific + code (for VSCode and for a file watcher). +* The processing module handles conversion of source code to the Client's format + and vice versa. + * The lexer divides source code into code and doc blocks. +* A main module provides a basic CLI interface for running the server outside an + IDE using the file watcher. diff --git a/server/src/ide/filewatcher.rs b/server/src/ide/filewatcher.rs index 2ff42faf..ecd3b271 100644 --- a/server/src/ide/filewatcher.rs +++ b/server/src/ide/filewatcher.rs @@ -634,7 +634,7 @@ async fn processing_task( break; } - EditorMessageContents::LoadFile(_) => { + EditorMessageContents::LoadFile(..) => { // We never have the requested file loaded in this // "IDE". Intead, it's always on disk. send_response(&from_ide_tx, m.id, Ok(ResultOkTypes::LoadFile(None))).await; @@ -783,7 +783,8 @@ mod tests { m } _ = sleep(Duration::from_secs(3)) => { - // The backtrace shows what message the code was waiting for; otherwise, it's an unhelpful error message. + // The backtrace shows what message the code was waiting for; + // otherwise, it's an unhelpful error message. panic!("Timeout waiting for message:\n{}", Backtrace::force_capture()); } } diff --git a/server/src/ide/vscode/tests.rs b/server/src/ide/vscode/tests.rs index 7a69f2fa..9676747a 100644 --- a/server/src/ide/vscode/tests.rs +++ b/server/src/ide/vscode/tests.rs @@ -353,10 +353,11 @@ async fn test_vscode_ide_websocket3() { // // Message ids: IDE - 0, Server - 1->2, Client - 0. let em = read_message(&mut ws_ide).await; - let msg = cast!(em.message, EditorMessageContents::LoadFile); + let (msg, is_current) = cast!(em.message, EditorMessageContents::LoadFile, a, b); // Compare these as strings -- we want to ensure the path separator is // correct for the current platform. assert_eq!(file_path.to_string_lossy(), msg.to_string_lossy()); + assert_eq!(is_current, false); assert_eq!(em.id, INITIAL_MESSAGE_ID + MESSAGE_ID_INCREMENT); // Reply to the `LoadFile` message -- the file isn't present. @@ -415,7 +416,7 @@ async fn test_vscode_ide_websocket3a() { // // Message ids: IDE - 0, Server - 0->2, Client - 0. let em = read_message(&mut ws_ide).await; - cast!(em.message, EditorMessageContents::LoadFile); + cast!(em.message, EditorMessageContents::LoadFile, a, b); // Skip comparing the file names, due to the backslash encoding. assert_eq!(em.id, INITIAL_MESSAGE_ID + MESSAGE_ID_INCREMENT); @@ -510,11 +511,12 @@ async fn test_vscode_ide_websocket8() { // // Message ids: IDE - 1, Server - 1->2, Client - 0. let em = read_message(&mut ws_ide).await; - let msg = cast!(em.message, EditorMessageContents::LoadFile); + let (msg, is_current) = cast!(em.message, EditorMessageContents::LoadFile, a, b); assert_eq!( path::absolute(Path::new(&msg)).unwrap(), path::absolute(&file_path).unwrap() ); + assert_eq!(is_current, true); assert_eq!(em.id, INITIAL_MESSAGE_ID + MESSAGE_ID_INCREMENT); // Reply to the `LoadFile` message with the file's contents. @@ -943,8 +945,9 @@ async fn test_vscode_ide_websocket4() { // // Message ids: IDE - 0, Server - 1->2, Client - 1. let em = read_message(&mut ws_ide).await; - let msg = cast!(em.message, EditorMessageContents::LoadFile); + let (msg, is_current) = cast!(em.message, EditorMessageContents::LoadFile, a, b); assert_eq!(fs::canonicalize(&msg).unwrap(), file_path_temp); + assert_eq!(is_current, true); assert_eq!(em.id, INITIAL_MESSAGE_ID + MESSAGE_ID_INCREMENT); // Reply to the `LoadFile` message: the IDE doesn't have the file. @@ -1030,11 +1033,12 @@ async fn test_vscode_ide_websocket4() { // // Message ids: IDE - 0, Server - 3->4, Client - 0. let em = read_message(&mut ws_ide).await; - let msg = cast!(em.message, EditorMessageContents::LoadFile); + let (msg, is_current) = cast!(em.message, EditorMessageContents::LoadFile, a, b); assert_eq!( fs::canonicalize(&msg).unwrap(), fs::canonicalize(test_dir.join("toc.md")).unwrap() ); + assert_eq!(is_current, false); assert_eq!(em.id, INITIAL_MESSAGE_ID + 3.0 * MESSAGE_ID_INCREMENT); // Reply to the `LoadFile` message: the IDE doesn't have the file. @@ -1049,7 +1053,7 @@ async fn test_vscode_ide_websocket4() { join_handle.join().unwrap(); // What makes sense here? If the IDE didn't load the file, either the Client shouldn't edit it or the Client should switch to using a filewatcher for edits. - /* + /*** // Send an update from the Client, which should produce a diff. // // Message ids: IDE - 0, Server - 4, Client - 0->1. @@ -1210,8 +1214,9 @@ async fn test_vscode_ide_websocket4a() { // // Message ids: IDE - 0, Server - 1->2, Client - 1. let em = read_message(&mut ws_ide).await; - let msg = cast!(em.message, EditorMessageContents::LoadFile); + let (msg, is_current) = cast!(em.message, EditorMessageContents::LoadFile, a, b); assert_eq!(fs::canonicalize(&msg).unwrap(), file_path_temp); + assert_eq!(is_current, true); assert_eq!(em.id, INITIAL_MESSAGE_ID + MESSAGE_ID_INCREMENT); // Reply to the `LoadFile` message: the IDE doesn't have the file. @@ -1324,8 +1329,9 @@ async fn test_vscode_ide_websocket4b() { // // Message ids: IDE - 0, Server - 1->2, Client - 1. let em = read_message(&mut ws_ide).await; - let msg = cast!(em.message, EditorMessageContents::LoadFile); + let (msg, is_current) = cast!(em.message, EditorMessageContents::LoadFile, a, b); assert_eq!(fs::canonicalize(&msg).unwrap(), file_path_temp); + assert_eq!(is_current, true); assert_eq!(em.id, INITIAL_MESSAGE_ID + MESSAGE_ID_INCREMENT); // Reply to the `LoadFile` message: the IDE doesn't have the file. diff --git a/server/src/lexer.rs b/server/src/lexer.rs index c06395fc..e86396bb 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,38 +36,34 @@ use regex::Regex; use supported_languages::get_language_lexer_vec; /// Data structures -/// --------------- +/// ---------------------------------------------------------------------------- /// /// ### Language definition /// /// These data structures define everything the lexer needs in order to analyze /// a programming language: /// -/// * It defines block and inline comment delimiters; these (when correctly -/// formatted) become doc blocks. -/// * It defines strings: what is the escape character? Are newlines allowed? -/// If 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 defines block and inline comment delimiters; these (when correctly +/// formatted) become doc blocks. +/// * It defines strings: what is the escape character? Are newlines allowed? If +/// 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. /// /// This lexer ignores line continuation characters; in C/C++/Python, it's a `\` -/// character followed immediately by a newline ([C -/// reference](https://www.open-std.org/jtc1/sc22/WG14/www/docs/n1256.pdf#page22), -/// [Python -/// reference](https://docs.python.org/3/reference/lexical_analysis.html#explicit-line-joining)). +/// character followed immediately by a newline +/// ([C reference](https://www.open-std.org/jtc1/sc22/WG14/www/docs/n1256.pdf#page22), +/// [Python reference](https://docs.python.org/3/reference/lexical_analysis.html#explicit-line-joining)). /// From a lexer perspective, supporting these adds little value: /// -/// 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 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 -/// newlines in strings, a separate case, are handled correctly). +/// 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 +/// 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 +/// newlines in strings, a separate case, are handled correctly). /// /// This struct defines the delimiters for a block comment. #[derive(Clone)] @@ -129,15 +125,14 @@ struct HeredocDelim { enum SpecialCase { /// There are no special cases for this language. None, - /// [Template - /// literal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) + /// [Template literal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) /// support (for languages such as JavaScript, TypeScript, etc.). TemplateLiteral, - /// C#'s verbatim string literal -- see [6.4.5.6 String - /// literals](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/lexical-structure#6456-string-literals). + /// C#'s verbatim string literal -- see + /// [6.4.5.6 String literals](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/lexical-structure#6456-string-literals). CSharpVerbatimStringLiteral, - /// MATLAB [block - /// comments](https://www.mathworks.com/help/matlab/matlab_prog/comments.html) + /// MATLAB + /// [block comments](https://www.mathworks.com/help/matlab/matlab_prog/comments.html) /// must start and end on a blank line. Matlab, } @@ -265,10 +260,10 @@ 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). +// Create constant regexes needed by the lexer, following the +// [Regex docs recommendation](https://docs.rs/regex/1.6.0/regex/index.html#example-avoid-compiling-the-same-regex-in-a-loop). lazy_static! { static ref WHITESPACE_ONLY_REGEX: Regex = Regex::new("^[[:space:]]*$").unwrap(); /// TODO: This regex should also allow termination on an unescaped `${` @@ -276,8 +271,7 @@ lazy_static! { /// expression. static ref TEMPLATE_LITERAL_CLOSING_REGEX: Regex = Regex::new( // Allow `.` to match *any* character, including a newline. See the - // [regex - // docs](https://docs.rs/regex/1.6.0/regex/index.html#grouping-and-flags). + // [regex docs](https://docs.rs/regex/1.6.0/regex/index.html#grouping-and-flags). &("(?s)".to_string() + // Start at the beginning of the string, and require a match of every // character. Allowing the regex to start matching in the middle means @@ -412,8 +406,8 @@ fn build_lexer_regex( // Escaped newlines or terminators should be included in the string. (true, NewlineSupport::Escaped) => Regex::new( // Allow `.` to match *any* character, including a newline. See - // the [regex - // docs](https://docs.rs/regex/1.6.0/regex/index.html#grouping-and-flags). + // the + // [regex docs](https://docs.rs/regex/1.6.0/regex/index.html#grouping-and-flags). &("(?s)".to_string() + // Start at the beginning of the string, and require a match of // every character. Allowing the regex to start matching in the @@ -462,8 +456,8 @@ fn build_lexer_regex( // Even simpler: look for an unescaped string delimiter. (true, NewlineSupport::Unescaped) => Regex::new( // Allow `.` to match *any* character, including a newline. See - // the [regex - // docs](https://docs.rs/regex/1.6.0/regex/index.html#grouping-and-flags). + // the + // [regex docs](https://docs.rs/regex/1.6.0/regex/index.html#grouping-and-flags). &("(?s)".to_string() + // Start at the beginning of the string, and require a match of // every character. Allowing the regex to start matching in the @@ -547,8 +541,8 @@ fn build_lexer_regex( // To match on a line which consists only of leading and // trailing whitespace plus the opening comment delimiter, put // these inside a `(?m:exp)` block, so that `^` and `$` will - // match on any newline in the string; see the [regex - // docs](https://docs.rs/regex/latest/regex/#grouping-and-flags). + // match on any newline in the string; see the + // [regex docs](https://docs.rs/regex/latest/regex/#grouping-and-flags). // This also functions as a non-capturing group, to avoid // whitespace capture as discussed earlier. "(?m:" + @@ -602,7 +596,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(), @@ -640,7 +634,7 @@ pub fn compile_lexers(language_lexer_arr: Vec) -> LanguageLexersC } /// Source lexer -/// ------------ +/// ---------------------------------------------------------------------------- /// /// This lexer categorizes source code into code blocks or doc blocks. /// @@ -657,16 +651,15 @@ pub fn source_lexer( // to categorize all the source code into code blocks or doc blocks. To do // it, it only needs to: // - // * Recognize where comments can't be—inside strings or string-like - // syntax, such as [here - // text](https://en.wikipedia.org/wiki/Here_document) or [template - // literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals). - // These are always part of a code block and can never contain a comment - // or (by implication) a doc block. - // * Outside of these special cases, look for inline or block comments, - // categorizing everything else as plain code. - // * After finding either an inline or block comment, determine if this is - // a doc block. + // * Recognize where comments can't be—inside strings or string-like syntax, + // such as [here text](https://en.wikipedia.org/wiki/Here_document) or + // [template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals). + // These are always part of a code block and can never contain a comment + // or (by implication) a doc block. + // * Outside of these special cases, look for inline or block comments, + // categorizing everything else as plain code. + // * After finding either an inline or block comment, determine if this is a + // doc block. // // ### Lexer operation // @@ -676,18 +669,18 @@ pub fn source_lexer( // `language_lexer_compiled.map`. These divides source code into two // categories: plain code and special cases. The special cases consist of: // - // * String-like code (strings, here text, template literals). In this - // case, the lexer must find the end of the string-like element before - // it can return to plain code. - // * Comments (inline or block). In this case, the lexer must find the end - // of the comment before it can return to plain code. + // * String-like code (strings, here text, template literals). In this case, + // the lexer must find the end of the string-like element before it can + // return to plain code. + // * Comments (inline or block). In this case, the lexer must find the end + // of the comment before it can return to plain code. // // This regex assumes the string it analyzes was preceded by plain code; its // purpose is to identify the start of the next special case. **This code // makes heavy use of regexes -- read the previous link thoroughly.** // - // To better explain the operation of the lexer, see the [lexer - // walkthrough](lexer/lexer-walkthrough.md). + // To better explain the operation of the lexer, see the + // [lexer walkthrough](lexer/lexer-walkthrough.md). // // ### Helper function // @@ -897,13 +890,13 @@ pub fn source_lexer( // **Next**, determine if this comment is a doc block. // Criteria for doc blocks for an inline comment: // - // 1. All characters preceding the comment on the line - // containing the comment must be whitespace. - // 2. Either: - // 1. The inline comment delimiter is immediately - // followed by a space, or - // 2. the inline comment delimiter is followed by a - // newline or the end of the file. + // 1. All characters preceding the comment on the line + // containing the comment must be whitespace. + // 2. Either: + // 1. The inline comment delimiter is immediately + // followed by a space, or + // 2. the inline comment delimiter is followed by a + // newline or the end of the file. // // With this last line located, apply the doc block // criteria. @@ -988,23 +981,22 @@ pub fn source_lexer( // as a potential doc block; everything else is treated // as code. The rationale: // - // 1. Typically, nested comments are used to comment - // out a block of code, which may already contain - // "real" comments (as opposed to commented-out - // code). Therefore, we assume that only these - // innermost comments are true comments, while - // everything else is code. I can't think of any - // reason to nest true comments. Assuming a - // legitimate use for nested comments, what criteria - // would distinguish a nested comment from a - // commented-out code block? - // 2. The CodeChat Editor data structures don't support - // nested doc blocks. So, while we might be able to - // correctly parse nested comments as doc blocks, - // the code that transforms these back to code would - // remove the nesting. - // 3. We lack criteria that would distinguish a nested - // doc block from commented-out code. + // 1. Typically, nested comments are used to comment out + // a block of code, which may already contain "real" + // comments (as opposed to commented-out code). + // Therefore, we assume that only these innermost + // comments are true comments, while everything else + // is code. I can't think of any reason to nest true + // comments. Assuming a legitimate use for nested + // comments, what criteria would distinguish a nested + // comment from a commented-out code block? + // 2. The CodeChat Editor data structures don't support + // nested doc blocks. So, while we might be able to + // correctly parse nested comments as doc blocks, the + // code that transforms these back to code would + // remove the nesting. + // 3. We lack criteria that would distinguish a nested + // doc block from commented-out code. // // With these assumptions, we need to know if the // current comment is the innermost or not. If the last @@ -1211,20 +1203,20 @@ pub fn source_lexer( // Next, determine if this is a doc block. // Criteria for doc blocks for a block comment: // - // 1. Must have a space or newline after the - // opening delimiter. - // 2. Must not have anything besides whitespace - // before the opening comment delimiter on - // the same line. This whitespace becomes - // the indent. - // 3. Must not have anything besides whitespace - // after the closing comment delimiter on - // the same line. This whitespace is - // included, as if it were inside the block - // comment. Rationale: this avoids deleting - // text (or, in this case, whitespace); - // moving that whitespace around seems like - // a better alternative than deleting it. + // 1. Must have a space or newline after the + // opening delimiter. + // 2. Must not have anything besides whitespace + // before the opening comment delimiter on + // the same line. This whitespace becomes the + // indent. + // 3. Must not have anything besides whitespace + // after the closing comment delimiter on the + // same line. This whitespace is included, as + // if it were inside the block comment. + // Rationale: this avoids deleting text (or, + // in this case, whitespace); moving that + // whitespace around seems like a better + // alternative than deleting it. if (comment_body.starts_with(' ') || comment_body.starts_with('\n')) && WHITESPACE_ONLY_REGEX.is_match(comment_line_prefix) && WHITESPACE_ONLY_REGEX.is_match(post_closing_delimiter_line) @@ -1267,47 +1259,49 @@ pub fn source_lexer( // // There are several cases: // - // * A single line: `/* comment */`. No - // special handling needed. - // * Multiple lines, in two styles. - // * Each line of the comment is not - // consistently whitespace-indented. No - // special handling needed. For example: + // * A single line: `/* comment */`. No + // special handling needed. + // * Multiple lines, in two styles. + // * Each line of the comment is not + // consistently whitespace-indented. No + // special handling needed. For example: // - // ```C - // /* This is - // not - // consistently indented. */ - // ``` + // ```C + // /* This is + // not + // consistently indented. */ + // ``` // - // * Each line of the comment is consistently - // whitespace-indented; for example: + // * Each line of the comment is + // consistently whitespace-indented; for + // example: // - // ```C - // /* This is - // consistently indented. */ - // ``` + // ```C + // /* This is + // consistently indented. */ + // ``` // - // Consistently indented means the first - // non-whitespace character on a line - // aligns with, but never comes before, the - // comment's start. Another example: + // Consistently indented means the first + // non-whitespace character on a line + // aligns with, but never comes before, + // the comment's start. Another example: // - // ```C - // /* This is - // correct + // ```C + // /* This is + // correct // - // indentation. - // */ - // ``` + // indentation. + // */ + // ``` // - // Note that the third (blank) line doesn't - // have an indent; since that line consists - // only of whitespace, this is OK. - // Likewise, the last line (containing the - // closing comment delimiter of `*/`) - // consists only of whitespace after the - // comment delimiters are removed. + // Note that the third (blank) line + // doesn't have an indent; since that + // line consists only of whitespace, + // this is OK. Likewise, the last line + // (containing the closing comment + // delimiter of `*/`) consists only of + // whitespace after the comment + // delimiters are removed. // // Determine if this comment is indented. // @@ -1447,10 +1441,10 @@ pub fn source_lexer( } // Tests -// ----- +// ----------------------------------------------------------------------------- // -// Rust [almost -// mandates](https://doc.rust-lang.org/book/ch11-03-test-organization.html) +// Rust +// [almost mandates](https://doc.rust-lang.org/book/ch11-03-test-organization.html) // putting tests in the same file as the source, which I dislike. Here's a way // to place them in a separate file. #[cfg(test)] diff --git a/server/src/lexer/pest/c.pest b/server/src/lexer/pest/c.pest index f2ddc708..295648ef 100644 --- a/server/src/lexer/pest/c.pest +++ b/server/src/lexer/pest/c.pest @@ -15,22 +15,22 @@ // [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 [C standard, section -// 6.4.3](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3220.pdf#page=65), +// Per the +// [C standard, section 6.4.3](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3220.pdf#page=65), // "white-space consists of: (space, horizontal tab, new-line, vertical tab, and // form-feed)." Omit newlines, since the rest of this parser uses these. vertical_tab = { "\x0B" } form_feed = { "\x0C" } white_space = { (" " | "\t" | vertical_tab | form_feed)* } -// The [C standard, section -// 6.4.9](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3220.pdf#page=65), +// The +// [C standard, section 6.4.9](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3220.pdf#page=65), // defines inline and block comments. // // ### Inline comments @@ -50,110 +50,106 @@ 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), +// Per the +// [C standard, section 5.1.1.2](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3220.pdf#page=24), // if a line of code ends with a backslash, it continues on the next line. This // is a logical line; treat it as a single line. Therefore, consider a // backslash-newline (or anything that's not a newline) a part of the current // logical line. Note that this parser doesn't apply this rule to comments // (which, per the spec, it should) for several reasons: // -// 1. Comments continued onto another line don't look like a comment; this -// would confuse most developers. -// -// 2. The backslash-newline in a comment creates a [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:  -// -// ```C -// // This is a hard line break\ -// followed by a comment which must not include the // inline comment -// // delimiter on the line after the line break, but which must -// include them on following lines. -// ``` -// -// 3. The CodeChat Editor web-to-code function produces incorrect results in -// this case, adding a comment delimiter when it shouldn't. To fix this, it -// would have to look for a backslash newline only in C/C++-like languages. +// 1. Comments continued onto another line don't look like a comment; this would +// confuse most developers. +// +// 2. The backslash-newline in a comment creates a +// [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:  +// +// ```C +// // This is a hard line break\ +// followed by a comment which must not include the // inline comment +// // delimiter on the line after the line break, but which must +// include them on following lines. +// ``` +// +// 3. The CodeChat Editor web-to-code function produces incorrect results in +// this case, adding a comment delimiter when it shouldn't. To fix this, it +// would have to look for a backslash newline only in C/C++-like languages. 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: // -// * A single line: `/* comment */`. No special handling needed. -// * Multiple lines, in two styles. -// * Each line of the comment is not consistently whitespace-indented. No -// special handling needed. For example: -// -// ```C -// /* This is -// not -// consistently indented. */ -// ``` -// -// * Each line of the comment is consistently whitespace-indented; for -// example: -// -// ```C -// /* This is -// consistently indented. */ -// ``` -// -// Consistently indented means the first non-whitespace character on a -// line aligns with, but never comes before, the comment's start. -// Another example: -// -// ```C -// /* This is -// correct -// -// indentation. -// */ -// ``` -// -// Note that the third (blank) line doesn't have an indent; since that -// line consists only of whitespace, this is OK. Likewise, the last line -// (containing the closing comment delimiter of `*/`) consists only of -// whitespace after the comment delimiters are removed. -// -// * Each line of the comment is consistently asterisk-indented; for -// example: -// -// ```C -// /* This is -// * correct -// * -// * indentation. -// */ -// ``` -// -// Note that in this case, no whitespace-only lines are allowed. -// Instead, the special case is lines which have a newline immediately -// after the `*`. +// * A single line: `/* comment */`. No special handling needed. +// * Multiple lines, in two styles. +// * Each line of the comment is not consistently whitespace-indented. No +// special handling needed. For example: +// +// ```C +// /* This is +// not +// consistently indented. */ +// ``` +// +// * Each line of the comment is consistently whitespace-indented; for +// example: +// +// ```C +// /* This is +// consistently indented. */ +// ``` +// +// Consistently indented means the first non-whitespace character on a line +// aligns with, but never comes before, the comment's start. Another +// example: +// +// ```C +// /* This is +// correct +// +// indentation. +// */ +// ``` +// +// Note that the third (blank) line doesn't have an indent; since that line +// consists only of whitespace, this is OK. Likewise, the last line +// (containing the closing comment delimiter of `*/`) consists only of +// whitespace after the comment delimiters are removed. +// +// * Each line of the comment is consistently asterisk-indented; for example: +// +// ```C +// /* This is +// * correct +// * +// * indentation. +// */ +// ``` +// +// Note that in this case, no whitespace-only lines are allowed. Instead, +// the special case is lines which have a newline immediately after the `*`. // // To implement this dedenting, we must have two paths to accepting the contents // of a block comment. Otherwise, this parser rejects the block (it cannot be // dedented). The approach: // -// 1. The space-indented path. This requires: -// 1. The first line ends with a newline. (`valid_first_line`) -// 2. Non-first lines with contents must be properly indented. If a -// non-first line ends in a newline, it must not be the last line. -// (`dedented_line`) -// 3. A whitespace-only line must not be the last line, unless it has -// exactly the indent needed to align the closing comment delimiter -// (`last_line`). -// 2. The asterisk-indented path. The requirements are the same as the -// space-indented path, though the proper indent includes an asterisk in the -// correct location. +// 1. The space-indented path. This requires: +// 1. The first line ends with a newline. (`valid_first_line`) +// 2. Non-first lines with contents must be properly indented. If a non-first +// line ends in a newline, it must not be the last line. (`dedented_line`) +// 3. A whitespace-only line must not be the last line, unless it has exactly +// the indent needed to align the closing comment delimiter (`last_line`). +// 2. The asterisk-indented path. The requirements are the same as the +// space-indented path, though the proper indent includes an asterisk in the +// correct location. dedenter = { SOI ~ indent ~ valid_first_line ~ (valid_space_line+ | valid_star_line+) ~ DROP ~ EOI } @@ -170,4 +166,4 @@ vis_newline = { NEWLINE } not_newline_eoi = _{ !(NEWLINE ~ EOI) } star_dedent = _{ PEEK ~ " * " } -/// CodeChat Editor lexer: c_cpp. +/// CodeChat Editor lexer: cpp. diff --git a/server/src/lexer/pest/parser_design.md b/server/src/lexer/pest/parser_design.md index 25802881..acea239f 100644 --- a/server/src/lexer/pest/parser_design.md +++ b/server/src/lexer/pest/parser_design.md @@ -1,5 +1,5 @@ Parser design -============= +================================================================================ The CodeChat Editor uses the [Pest parser](https://pest.rs/), a Rust implementation of a parsing expression grammar (or PEG). The purpose of the @@ -7,48 +7,47 @@ parser from a CodeChat Editor perspective is to classify a source file into code blocks and doc blocks. To accomplish this goal, grammar files (`.pest`) are divided into: -* A shared grammar ([shared.pest](shared.pest)), which contains basic - definitions applicable to all languages; -* A language-specific grammar, which builds on these shared definitions by - providing necessary language-specific customizations. +* A shared grammar ([shared.pest](shared.pest)), which contains basic + definitions applicable to all languages; +* A language-specific grammar, which builds on these shared definitions by + providing necessary language-specific customizations. In particular, a language-specific grammar must provide: -* The definition of a `doc_block`; for most languages, `doc_block = _{ - inline_comment | block_comment }`. However, languages which lack an inline - comment (such as CSS) or a block comment (such as Python) would contain only - the appropriate comment type. +* The definition of a `doc_block`; for most languages, `doc_block = _{ + inline_comment | block_comment }`. However, languages which lack an inline + comment (such as CSS) or a block comment (such as Python) would contain only + the appropriate comment type. -* Inline comment definitions: +* Inline comment definitions:x - * Opening inline delimiter(s) supported by the language. Three inline - comment delimiters must be defined for a language. For C, this is: + * Opening inline delimiter(s) supported by the language. Three inline comment + delimiters must be defined for a language. For C, this is: - ``` - inline_comment_delims = _{ inline_comment_delim_0 } - inline_comment_delim_0 = { "//" } - inline_comment_delim_1 = { unused } - inline_comment_delim_2 = { unused } - ``` - - * A token which defines characters in the body of on an inline comment. - For Python, this is: - - ``` - inline_comment_char = { not_newline } - ``` + ``` + inline_comment_delims = _{ inline_comment_delim_0 } + inline_comment_delim_0 = { "//" } + inline_comment_delim_1 = { unused } + inline_comment_delim_2 = { unused } + ``` -* Block comment definitions: provide opening and closing delimiter - definitions. For C, this is: + * A token which defines characters in the body of on an inline comment. For + Python, this is: ``` - block_comment = { block_comment_0 } - block_comment_opening_delim_0 = { "/*" } - block_comment_opening_delim_1 = { unused } - block_comment_opening_delim_2 = { unused } - block_comment_closing_delim_0 = { "*/" } - block_comment_closing_delim_1 = { unused } - block_comment_closing_delim_2 = { unused } + inline_comment_char = { not_newline } ``` - -* `code_line_token`, a token used to recognize tokens in a code line. \ No newline at end of file +* Block comment definitions: provide opening and closing delimiter definitions. + For C, this is: + + ``` + block_comment = { block_comment_0 } + block_comment_opening_delim_0 = { "/*" } + block_comment_opening_delim_1 = { unused } + block_comment_opening_delim_2 = { unused } + block_comment_closing_delim_0 = { "*/" } + block_comment_closing_delim_1 = { unused } + block_comment_closing_delim_2 = { unused } + ``` + +* `code_line_token`, a token used to recognize tokens in a code line. diff --git a/server/src/lexer/pest/python.pest b/server/src/lexer/pest/python.pest index c0a45a1c..e07812aa 100644 --- a/server/src/lexer/pest/python.pest +++ b/server/src/lexer/pest/python.pest @@ -15,29 +15,29 @@ // [http://www.gnu.org/licenses](http://www.gnu.org/licenses). // // `python.pest` - Pest parser definition for Python -// ================================================= +// ============================================================================= doc_block = _{ inline_comment } -// Per the [Python language -// reference](https://docs.python.org/3/reference/lexical_analysis.html#indentation), +// Per the +// [Python language reference](https://docs.python.org/3/reference/lexical_analysis.html#indentation), // leading whitespace used to determine the indentation level consists of spaces // and tabs. white_space = { (" " | "\t")* } // Inline comments -// --------------- +// ----------------------------------------------------------------------------- inline_comment_delims = _{ inline_comment_delim_0 } inline_comment_delim_0 = { "#" } inline_comment_delim_1 = { unused } inline_comment_delim_2 = { unused } -// Per the [Python language reference, section -// 2.1.3](https://docs.python.org/3/reference/lexical_analysis.html#comments), +// Per the +// [Python language reference, section 2.1.3](https://docs.python.org/3/reference/lexical_analysis.html#comments), // comments end at the end of a physical line. There's no C-like backslash that // can join physical lines into logical lines for comments. 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: c_cpp. +/// CodeChat Editor lexer: cpp. diff --git a/server/src/lexer/pest/shared.pest b/server/src/lexer/pest/shared.pest index f3034cd8..6abcfa76 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. @@ -41,12 +41,12 @@ inline_comment_line = { (" " ~ inline_comment_body) | newline_eoi } // // becomes: // -// * inline\_comment\_body > logical\_line: `a\n` -// * inline\_comment\_body: `\n` +// * inline\_comment\_body > logical\_line: `a\n` +// * inline\_comment\_body: `\n` inline_comment_body = { inline_comment_char* ~ newline_eoi } // Block comments -// -------------- +// ----------------------------------------------------------------------------- // // Support multiple opening and closing delimiters using some repetition. block_comment_0 = _{ @@ -72,15 +72,15 @@ 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. unused = { "unused" } -/// CodeChat Editor lexer: c_cpp. +/// CodeChat Editor lexer: cpp. diff --git a/server/src/lexer/supported_languages.rs b/server/src/lexer/supported_languages.rs index 3949a6b1..cd856b74 100644 --- a/server/src/lexer/supported_languages.rs +++ b/server/src/lexer/supported_languages.rs @@ -147,7 +147,7 @@ pub fn get_language_lexer_vec() -> Vec { ), // ### C/C++ make_language_lexer( - "c_cpp", + "cpp", // Unusual extensions: // // * The `.ino` extension is for Arduino source files. diff --git a/server/src/lexer/tests.rs b/server/src/lexer/tests.rs index a27c600d..b72eedec 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 { @@ -464,7 +464,7 @@ fn test_js() { #[test] fn test_cpp() { let llc = compile_lexers(get_language_lexer_vec()); - let cpp = llc.map_mode_to_lexer.get(&stringit("c_cpp")).unwrap(); + let cpp = llc.map_mode_to_lexer.get(&stringit("cpp")).unwrap(); // Try out a C++ heredoc. assert_eq!( @@ -479,7 +479,7 @@ fn test_cpp() { #[test] fn test_c() { let llc = compile_lexers(get_language_lexer_vec()); - let c = llc.map_mode_to_lexer.get(&stringit("c_cpp")).unwrap(); + let c = llc.map_mode_to_lexer.get(&stringit("cpp")).unwrap(); // Test logical lines. let s = indoc!( @@ -731,10 +731,7 @@ fn test_compiler() { let c_ext_lexer_arr = llc.map_ext_to_lexer_vec.get(&stringit("c")).unwrap(); assert_eq!(c_ext_lexer_arr.len(), 1); - assert_eq!( - c_ext_lexer_arr[0].language_lexer.lexer_name.as_str(), - "c_cpp" - ); + assert_eq!(c_ext_lexer_arr[0].language_lexer.lexer_name.as_str(), "cpp"); assert_eq!( llc.map_mode_to_lexer .get(&stringit("verilog")) diff --git a/server/src/processing.rs b/server/src/processing.rs index 057e875d..a85f26c9 100644 --- a/server/src/processing.rs +++ b/server/src/processing.rs @@ -97,9 +97,9 @@ use crate::lexer::{ #[ts(export)] pub struct CodeChatForWeb { pub metadata: SourceFileMetadata, - pub source: CodeMirrorDiffable, /// The version number after accepting this update. pub version: f64, + pub source: CodeMirrorDiffable, } /// Provide two options for sending CodeMirror data -- as the full contents @@ -136,11 +136,11 @@ pub struct CodeMirror { /// A diff of the `CodeMirror` struct. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, TS)] pub struct CodeMirrorDiff { + /// The version number from which this diff was produced. + pub version: f64, /// A diff of the document being edited. pub doc: Vec, pub doc_blocks: Vec, - /// The version number from which this diff was produced. - pub version: f64, } /// A transaction produced by the diff of the `CodeMirror` struct. @@ -576,7 +576,8 @@ impl HtmlToMarkdownWrapped { self.word_wrap_config.line_width = line_width as u32; } - /// Convert one item in a stream of HTML to markdown. The HTML must be start at the root, not continue a previous incomplete section of the DOM. + /// Convert one item in a stream of HTML to markdown. The HTML must be start + /// at the root, not continue a previous incomplete section of the DOM. fn next(&self, tree: &Rc) -> Result { let converted = self.html_to_markdown.tree_to_markdown(tree)?; Ok( @@ -806,7 +807,8 @@ fn code_doc_block_vec_to_source( pub enum SourceToCodeChatForWebError { #[error("unknown lexer {0}")] UnknownLexer(String), - // Since we want `PartialEq`, we can't use `#[from] io::Error`; instead, convert the IO error to a string. + // Since we want `PartialEq`, we can't use `#[from] io::Error`; instead, + // convert the IO error to a string. #[error("unable to parse HTML {0}")] ParseFailed(String), } @@ -1050,16 +1052,17 @@ fn html_to_tree(html: &str) -> io::Result> { .read_from(&mut html.as_bytes())?; // TODO: should we report parse errors? If so, how and where? - /* - if let Some(err) = dom.errors.borrow().first() { - //return Err(io::Error::other(err.to_string())); - } + /*** + if let Some(err) = dom.errors.borrow().first() { + //return Err(io::Error::other(err.to_string())); + } */ Ok(dom.document) } -// A framework to transform HTML by parsing it to a DOM tree, walking the tree, then serializing the tree back to an HTML string. +// A framework to transform HTML by parsing it to a DOM tree, walking the tree, +// then serializing the tree back to an HTML string. fn transform_html)>(html: &str, transform: T) -> io::Result { let tree = html_to_tree(html)?; transform(tree.clone()); @@ -1072,6 +1075,7 @@ fn transform_html)>(html: &str, transform: T) -> io::Result <-- element 0 // ... @@ -1087,11 +1091,10 @@ fn transform_html)>(html: &str, transform: T) -> io::Result io::Result { transform_html(html, hydrating_walk_node) } @@ -1126,7 +1129,8 @@ fn hydrating_walk_node(node: Rc) { && let Some(text_child) = text_children.iter().next() && let NodeData::Text { .. } = &text_child.data { - // Make the parent node a `element_name` node, with the child's text. + // Make the parent node a `element_name` node, with the child's + // text. let wc_mermaid = Node::new(NodeData::Element { name: QualName::new(None, Namespace::from(""), LocalName::from(*element_name)), attrs: RefCell::new(vec![]), @@ -1136,7 +1140,8 @@ fn hydrating_walk_node(node: Rc) { wc_mermaid.children.borrow_mut().push(text_child.clone()); Some(wc_mermaid) } else { - // See if this is a math node to replace; if not, this returns `None`. + // See if this is a math node to replace; if not, this returns + // `None`. replace_math_node(child, true) }; @@ -1151,7 +1156,9 @@ fn hydrating_walk_node(node: Rc) { } fn replace_math_node(child: &Rc, is_hydrate: bool) -> Option> { - // Look for math produced by pulldown-cmark: `...`; add `\(...\)` inside the span. Perform a similar + // transformation for display math. if get_node_tag_name(child) == Some("span") && let NodeData::Element { attrs: ref_child_attrs, .. @@ -1167,15 +1174,28 @@ fn replace_math_node(child: &Rc, is_hydrate: bool) -> Option> { && let Some(text_child) = text_children.iter().next() && let NodeData::Text { contents } = &text_child.data { - // Final test: if the class is correct, this is math; otherwise, perform no transformation. + // Final test: if the class is correct, this is math; otherwise, perform + // no transformation. + // + // Add/remove the `mceNonEditable` class to prevent accidental edits of + // this span. let attr_value_str: &str = attr_value; - let delim = match attr_value_str { - "math math-inline" => Some(("\\(", "\\)")), - "math math-display" => Some(("$$", "$$")), - _ => None, + let delim = if is_hydrate { + match attr_value_str { + "math math-inline" => Some(("\\(", "\\)", "math math-inline mceNonEditable")), + "math math-display" => Some(("$$", "$$", "math math-display mceNonEditable")), + _ => None, + } + } else { + match attr_value_str { + "math math-inline mceNonEditable" => Some(("\\(", "\\)", "math math-inline")), + "math math-display mceNonEditable" => Some(("$$", "$$", "math math-display")), + _ => None, + } }; - // Since we've already borrowed `child`, we can't `borrow_mut` to modify it. Instead, create a new `span` with delimited text and return that. + // Since we've already borrowed `child`, we can't `borrow_mut` to modify + // it. Instead, create a new `span` with delimited text and return that. if let Some(delim) = delim { let contents_str = &*contents.borrow(); let delimited_text_str = if is_hydrate { @@ -1185,7 +1205,8 @@ fn replace_math_node(child: &Rc, is_hydrate: bool) -> Option> { if !contents_str.starts_with(delim.0) || !contents_str.ends_with(delim.1) { return None; } - // Return the contents without the beginning and ending delimiters. + // Return the contents without the beginning and ending + // delimiters. contents_str[delim.0.len()..contents_str.len() - delim.1.len()].to_string() }; let delimited_text_node = Node::new(NodeData::Text { @@ -1195,7 +1216,7 @@ fn replace_math_node(child: &Rc, is_hydrate: bool) -> Option> { name: QualName::new(None, Namespace::from(""), LocalName::from("span")), attrs: RefCell::new(vec![Attribute { name: QualName::new(None, Namespace::from(""), LocalName::from("class")), - value: attr_value.clone(), + value: delim.2.into(), }]), template_contents: RefCell::new(None), mathml_annotation_xml_integration_point: false, @@ -1227,7 +1248,8 @@ fn dehydrating_walk_node(node: &Rc) { && let Some(text_child) = text_children.iter().next() && let NodeData::Text { .. } = &text_child.data { - // Create `
text_child contents
`. + // Create `
text_child
+            // contents
`. let pre = Node::new(NodeData::Element { name: QualName::new(None, Namespace::from(""), LocalName::from("pre")), attrs: RefCell::new(vec![]), @@ -1268,7 +1290,8 @@ fn get_node_tag_name(node: &Rc) -> Option<&str> { } } -// Translate from Markdown class names for code blocks to the appropriate HTML custom element. +// Translate from Markdown class names for code blocks to the appropriate HTML +// custom element. static CODE_BLOCK_LANGUAGE_TO_CUSTOM_ELEMENT: phf::Map<&'static str, &'static str> = phf_map! { "language-mermaid" => "wc-mermaid", "language-graphviz" => "graphviz-graph", diff --git a/server/src/processing/tests.rs b/server/src/processing/tests.rs index 10abb5de..f47c1a85 100644 --- a/server/src/processing/tests.rs +++ b/server/src/processing/tests.rs @@ -245,7 +245,7 @@ fn test_codemirror_to_code_doc_blocks_cpp() { // Pass an inline comment. assert_eq!( run_test( - "c_cpp", + "cpp", "\n", vec![build_codemirror_doc_block(0, 1, "", "//", "Test")] ), @@ -255,7 +255,7 @@ fn test_codemirror_to_code_doc_blocks_cpp() { // Pass a block comment. assert_eq!( run_test( - "c_cpp", + "cpp", "\n", vec![build_codemirror_doc_block(0, 1, "", "/*", "Test")] ), @@ -265,7 +265,7 @@ fn test_codemirror_to_code_doc_blocks_cpp() { // Two back-to-back doc blocks. assert_eq!( run_test( - "c_cpp", + "cpp", "\n\n", vec![ build_codemirror_doc_block(0, 1, "", "//", "Test 1"), @@ -622,7 +622,7 @@ fn test_source_to_codechat_for_web_1() { assert_eq!( source_to_codechat_for_web("//\n\n//\n\n//", &"cpp".to_string(), 0.0, false, false), Ok(TranslationResults::CodeChat(build_codechat_for_web( - "c_cpp", + "cpp", "\n\n\n\n", vec![ build_codemirror_doc_block(0, 1, "", "//", ""), @@ -634,7 +634,7 @@ fn test_source_to_codechat_for_web_1() { assert_eq!( source_to_codechat_for_web("// ~~~\n\n//\n\n//", &"cpp".to_string(), 0.0, false, false), Ok(TranslationResults::CodeChat(build_codechat_for_web( - "c_cpp", + "cpp", "\n\n\n\n", vec![ build_codemirror_doc_block(0, 1, "", "//", "
\n
\n"), @@ -648,7 +648,7 @@ fn test_source_to_codechat_for_web_1() { assert_eq!( source_to_codechat_for_web("; // σ\n//", &"cpp".to_string(), 0.0, false, false), Ok(TranslationResults::CodeChat(build_codechat_for_web( - "c_cpp", + "cpp", "; // σ\n", vec![build_codemirror_doc_block(7, 8, "", "//", ""),] ))) @@ -658,7 +658,7 @@ fn test_source_to_codechat_for_web_1() { assert_eq!( source_to_codechat_for_web("\"σ\";\n//", &"cpp".to_string(), 0.0, false, false), Ok(TranslationResults::CodeChat(build_codechat_for_web( - "c_cpp", + "cpp", "\"σ\";\n", vec![build_codemirror_doc_block(5, 6, "", "//", ""),] ))) @@ -675,7 +675,7 @@ fn test_source_to_codechat_for_web_1() { false ), Ok(TranslationResults::CodeChat(build_codechat_for_web( - "c_cpp", + "cpp", "\n\n\n", vec![ build_codemirror_doc_block( @@ -699,7 +699,7 @@ fn test_source_to_codechat_for_web_1() { false ), Ok(TranslationResults::CodeChat(build_codechat_for_web( - "c_cpp", + "cpp", "\n\n\n", vec![ build_codemirror_doc_block( @@ -717,7 +717,7 @@ fn test_source_to_codechat_for_web_1() { assert_eq!( source_to_codechat_for_web("// ```\n // ~~~", &"cpp".to_string(), 0.0, false, false), Ok(TranslationResults::CodeChat(build_codechat_for_web( - "c_cpp", + "cpp", "\n\n", vec![ build_codemirror_doc_block(0, 1, "", "//", "
\n
\n"), @@ -730,7 +730,7 @@ fn test_source_to_codechat_for_web_1() { assert_eq!( source_to_codechat_for_web("// \n // Test", &"cpp".to_string(), 0.0, false, false), Ok(TranslationResults::CodeChat(build_codechat_for_web( - "c_cpp", + "cpp", "\n\n", vec![ build_codemirror_doc_block(0, 1, "", "//", "\n"), @@ -750,7 +750,7 @@ fn test_source_to_codechat_for_web_1() { false ), Ok(TranslationResults::CodeChat(build_codechat_for_web( - "c_cpp", + "cpp", "\n\n", vec![ build_codemirror_doc_block(0, 1, "", "//", "
\n"),
@@ -1293,12 +1293,12 @@ fn test_hydrate_html_1() {
         .unwrap(),
         indoc!(
             r#"
-            

\({a}_1, b_{2}\) - \(a*1, b*2\) - \([a](b)\) - \(3 <a> b\) - \(a \; b\)

-

$${a}_1, b_{2}, a*1, b*2, [a](b), 3 <a> b, a \; b$$

+

\({a}_1, b_{2}\) + \(a*1, b*2\) + \([a](b)\) + \(3 <a> b\) + \(a \; b\)

+

$${a}_1, b_{2}, a*1, b*2, [a](b), 3 <a> b, a \; b$$

"# ) ); @@ -1380,12 +1380,12 @@ fn test_dehydrate_html_1() { .convert( &dehydrate_html(indoc!( r#" -

\({a}_1, b_{2}\) - \(a*1, b*2\) - \([a](b)\) - \(3 <a> b\) - \(a \; b\)

-

$${a}_1, b_{2}, a*1, b*2, [a](b), 3 <a> b, a \; b$$

+

\({a}_1, b_{2}\) + \(a*1, b*2\) + \([a](b)\) + \(3 <a> b\) + \(a \; b\)

+

$${a}_1, b_{2}, a*1, b*2, [a](b), 3 <a> b, a \; b$$

"# )) .unwrap() diff --git a/server/src/translation.rs b/server/src/translation.rs index 3d8e9c35..3583c440 100644 --- a/server/src/translation.rs +++ b/server/src/translation.rs @@ -33,22 +33,23 @@ /// core processing needed to translate source code between a CodeChat Editor /// Client and an IDE client. The following diagram illustrates this approach: /// -/// digraph { -/// ccc -> client_task [ label = "websocket" dir = "both" ] -/// ccc -> http_task [ label = "HTTP\nrequest/response" dir = "both"] -/// client_task -> from_client -/// http_task -> http_to_client -/// http_to_client -> processing -/// processing -> http_from_client -/// http_from_client -> http_task -/// from_client -> processing -/// processing -> to_client -/// to_client -> client_task -/// ide -> ide_task [ dir = "both" ] -/// ide_task -> from_ide -/// from_ide -> processing -/// processing -> to_ide -/// to_ide -> ide_task +/// ```graphviz +/// digraph { +/// ccc -> client_task [ label = "websocket" dir = "both" ] +/// ccc -> http_task [ label = "HTTP\nrequest/response" dir = "both"] +/// client_task -> from_client +/// http_task -> http_to_client +/// http_to_client -> processing +/// processing -> http_from_client +/// http_from_client -> http_task +/// from_client -> processing +/// processing -> to_client +/// to_client -> client_task +/// ide -> ide_task [ dir = "both" ] +/// ide_task -> from_ide +/// from_ide -> processing +/// processing -> to_ide +/// to_ide -> ide_task /// { rank = same; client_task; http_task } /// { rank = same; to_client; from_client; http_from_client; http_to_client } /// { rank = same; to_ide; from_ide } @@ -65,7 +66,8 @@ /// ide_task [ label = "IDE task" ] /// from_ide [ label = "queue from IDE" shape="rectangle" ] /// to_ide [ label = "queue to IDE" shape="rectangle" ] -/// } +/// } +/// ``` /// /// The queues use multiple-sender, single receiver (mpsc) types. The exception /// to this pattern is the HTTP endpoint. This endpoint is invoked with each @@ -80,100 +82,100 @@ /// The following diagrams formally define the forwarding and translation /// protocol which this module implements. /// -/// * The startup phase loads the Client framework into a browser: +/// * The startup phase loads the Client framework into a browser: /// -/// -/// sequenceDiagram -/// participant IDE -/// participant Server -/// participant Client -/// note over IDE, Client: Startup -/// IDE ->> Server: Opened(IdeType) -/// Server ->> IDE: Result(String: OK) -/// Server ->> IDE: ClientHtml(String: HTML or URL) -/// IDE ->> Server: Result(String: OK) -/// note over IDE, Client: Open browser (Client framework HTML or URL) -/// loop -/// Client -> Server: HTTP request(/static URL) -/// Server -> Client: HTTP response(/static data) -/// end -/// +/// ```mermaid +/// sequenceDiagram +/// participant IDE +/// participant Server +/// participant Client +/// note over IDE, Client: Startup +/// IDE ->> Server: Opened(IdeType) +/// Server ->> IDE: Result(String: OK) +/// Server ->> IDE: ClientHtml(String: HTML or URL) +/// IDE ->> Server: Result(String: OK) +/// note over IDE, Client: Open browser (Client framework HTML or URL) +/// loop +/// Client -> Server: HTTP request(/static URL) +/// Server -> Client: HTTP response(/static data) +/// end +/// ``` /// -/// * If the current file in the IDE changes (including the initial startup, -/// when the change is from no file to the current file), or a link is -/// followed in the Client's iframe: +/// * If the current file in the IDE changes (including the initial startup, +/// when the change is from no file to the current file), or a link is +/// followed in the Client's iframe: /// -/// -/// sequenceDiagram -/// participant IDE -/// participant Server -/// participant Client -/// alt IDE loads file -/// IDE ->> Client: CurrentFile(String: Path of main.py) +/// ```mermaid +/// sequenceDiagram +/// participant IDE +/// participant Server +/// participant Client +/// alt IDE loads file +/// IDE ->> Client: CurrentFile(String: Path of main.py) /// opt If Client document is dirty -/// Client ->> IDE: Update(String: contents of main.py) -/// IDE ->> Client: Response(OK) -/// end -/// Client ->> IDE: Response(OK) -/// else Client loads file -/// Client ->> IDE: CurrentFile(String: URL of main.py) -/// IDE ->> Client: Response(OK) +/// Client ->> IDE: Update(String: contents of main.py) +/// IDE ->> Client: Response(OK) /// end -/// Client ->> Server: HTTP request(URL of main.py) -/// Server ->> IDE: LoadFile(String: path to main.py) -/// IDE ->> Server: Response(LoadFile(String: file contents of main.py)) -/// alt main.py is editable -/// Server ->> Client: HTTP response(contents of Client) -/// Server ->> Client: Update(String: contents of main.py) -/// Client ->> Server: Response(OK) +/// Client ->> IDE: Response(OK) +/// else Client loads file +/// Client ->> IDE: CurrentFile(String: URL of main.py) +/// IDE ->> Client: Response(OK) +/// end +/// Client ->> Server: HTTP request(URL of main.py) +/// Server ->> IDE: LoadFile(String: path to main.py) +/// IDE ->> Server: Response(LoadFile(String: file contents of main.py)) +/// alt main.py is editable +/// Server ->> Client: HTTP response(contents of Client) +/// Server ->> Client: Update(String: contents of main.py) +/// Client ->> Server: Response(OK) /// loop -/// Client ->> Server: HTTP request(URL of supporting file in main.py) -/// Server ->> IDE: LoadFile(String: path of supporting file) -/// alt Supporting file in IDE -/// IDE ->> Server: Response(LoadFile(contents of supporting file) -/// Server ->> Client: HTTP response(contents of supporting file) -/// else Supporting file not in IDE -/// IDE ->> Server: Response(LoadFile(None)) -/// Server ->> Client: HTTP response(contents of supporting file from /// filesystem) -/// end -/// end -/// else main.py not editable and not a project -/// Server ->> Client: HTTP response(contents of main.py) -/// else main.py not editable and is a project -/// Server ->> Client: HTTP response(contents of Client Simple Viewer) -/// Client ->> Server: HTTP request (URL?raw of main.py) -/// Server ->> Client: HTTP response(contents of main.py) +/// Client ->> Server: HTTP request(URL of supporting file in main.py) +/// Server ->> IDE: LoadFile(String: path of supporting file) +/// alt Supporting file in IDE +/// IDE ->> Server: Response(LoadFile(contents of supporting file) +/// Server ->> Client: HTTP response(contents of supporting file) +/// else Supporting file not in IDE +/// IDE ->> Server: Response(LoadFile(None)) +/// Server ->> Client: HTTP response(contents of supporting file from /// filesystem) +/// end /// end -/// +/// else main.py not editable and not a project +/// Server ->> Client: HTTP response(contents of main.py) +/// else main.py not editable and is a project +/// Server ->> Client: HTTP response(contents of Client Simple Viewer) +/// Client ->> Server: HTTP request (URL?raw of main.py) +/// Server ->> Client: HTTP response(contents of main.py) +/// end +/// ``` /// -/// * If the current file's contents in the IDE are edited: +/// * If the current file's contents in the IDE are edited: /// -/// -/// sequenceDiagram -/// participant IDE -/// participant Server -/// participant Client -/// IDE ->> Server: Update(String: new text contents) -/// alt Main file is editable -/// Server ->> Client: Update(String: new Client contents) -/// else Main file is not editable -/// Server ->> Client: Update(String: new text contents) -/// end -/// Client ->> IDE: Response(String: OK)
-///
+/// ```mermaid +/// sequenceDiagram +/// participant IDE +/// participant Server +/// participant Client +/// IDE ->> Server: Update(String: new text contents) +/// alt Main file is editable +/// Server ->> Client: Update(String: new Client contents) +/// else Main file is not editable +/// Server ->> Client: Update(String: new text contents) +/// end +/// Client ->> IDE: Response(String: OK) +/// ``` /// -/// * If the current file's contents in the Client are edited, the Client -/// sends the IDE an `Update` with the revised contents. +/// * If the current file's contents in the Client are edited, the Client sends +/// the IDE an `Update` with the revised contents. /// -/// * When the PC goes to sleep then wakes up, the IDE client and the Editor -/// client both reconnect to the websocket URL containing their assigned ID. +/// * When the PC goes to sleep then wakes up, the IDE client and the Editor +/// client both reconnect to the websocket URL containing their assigned ID. /// -/// * If the Editor client or the IDE client are closed, they close their -/// websocket, which sends a `Close` message to the other websocket, causes -/// it to also close and ending the session. +/// * If the Editor client or the IDE client are closed, they close their +/// websocket, which sends a `Close` message to the other websocket, causes it +/// to also close and ending the session. /// -/// * If the server is stopped (or crashes), both clients shut down after -/// several reconnect retries. +/// * If the server is stopped (or crashes), both clients shut down after +/// several reconnect retries. /// /// ### Editor-overlay filesystem /// @@ -229,10 +231,10 @@ use crate::{ queue_send, queue_send_func, webserver::{ EditorMessage, EditorMessageContents, INITIAL_MESSAGE_ID, MESSAGE_ID_INCREMENT, - ProcessingTaskHttpRequest, ResultErrTypes, ResultOkTypes, SimpleHttpResponse, - SimpleHttpResponseError, UpdateMessageContents, WebAppState, WebsocketQueues, - file_to_response, path_to_url, send_response, try_canonicalize, try_read_as_text, - url_to_path, + ProcessingTaskHttpRequest, ProcessingTaskHttpRequestFlags, ResultErrTypes, ResultOkTypes, + SimpleHttpResponse, SimpleHttpResponseError, UpdateMessageContents, WebAppState, + WebsocketQueues, file_to_response, path_to_url, send_response, try_canonicalize, + try_read_as_text, url_to_path, }, }; @@ -240,7 +242,7 @@ use crate::{ // ----------------------------------------------------------------------------- // // The max length of a message to show in the console. -const MAX_MESSAGE_LENGTH: usize = 3000; +const MAX_MESSAGE_LENGTH: usize = 500; lazy_static! { /// A regex to determine the type of the first EOL. See 'PROCESSINGS\`. @@ -494,7 +496,7 @@ pub async fn translation_task( // Handle messages that the IDE must not send. EditorMessageContents::Opened(_) | EditorMessageContents::OpenUrl(_) | - EditorMessageContents::LoadFile(_) | + EditorMessageContents::LoadFile(..) | EditorMessageContents::ClientHtml(_) => { let err = ResultErrTypes::IdeIllegalMessage; error!("{err:?}"); @@ -543,7 +545,16 @@ pub async fn translation_task( // Convert the request into a `LoadFile` message. queue_send!(tt.to_ide_tx.send(EditorMessage { id: tt.id, - message: EditorMessageContents::LoadFile(http_request.file_path.clone()) + message: EditorMessageContents::LoadFile + (http_request.file_path.clone(), + // Assign a version to this `LoadFile` request only + // if it's the current file and loaded as the file + // to edit, not as the sidebar TOC. We can us a + // simple comparison, since both file names have + // already been canonicalized. + http_request.file_path == tt.current_file && + http_request.flags == ProcessingTaskHttpRequestFlags::None + ) })); // Store the ID and request, which are needed to send a // response when the `LoadFile` result is received. @@ -557,7 +568,7 @@ pub async fn translation_task( match client_message.message { // Handle messages that the client must not send. EditorMessageContents::Opened(_) | - EditorMessageContents::LoadFile(_) | + EditorMessageContents::LoadFile(..) | EditorMessageContents::RequestClose | EditorMessageContents::ClientHtml(_) => { let err = ResultErrTypes::ClientIllegalMessage; @@ -575,7 +586,7 @@ pub async fn translation_task( debug!("Forwarding it to the IDE."); // If the Client can't read our diff, send the full // text next time. - if matches!(result, Err(ResultErrTypes::OutOfSync)) { + if matches!(result, Err(ResultErrTypes::OutOfSync(..))) { tt.sent_full = false; } queue_send!(tt.to_ide_tx.send(client_message)) @@ -706,7 +717,7 @@ impl TranslationTask { if !is_loadfile { debug!("Forwarding it to the Client."); // If the Server can't read our diff, send the full text next time. - if matches!(result, Err(ResultErrTypes::OutOfSync)) { + if matches!(result, Err(ResultErrTypes::OutOfSync(..))) { self.sent_full = false; } queue_send_func!(self.to_client_tx.send(ide_message)); @@ -745,7 +756,13 @@ impl TranslationTask { let use_pdf_js = http_request.file_path.extension() == Some(OsStr::new("pdf")); let ((simple_http_response, option_update), file_contents) = match file_contents_option { Some((file_contents, new_version)) => { - self.version = new_version; + // Only pay attention to the version if this is an editable + // Client file. + if http_request.file_path == self.current_file + && http_request.flags == ProcessingTaskHttpRequestFlags::None + { + self.version = new_version; + } // The IDE just sent the full contents; we're sending full // contents to the Client. self.sent_full = true; @@ -806,7 +823,7 @@ impl TranslationTask { self.code_mirror_doc = plain.doc.clone(); self.code_mirror_doc_blocks = Some(plain.doc_blocks.clone()); - debug!("Sending Update to Client, id = {}.", self.id); + debug!("Sending Update from LoadFile to Client, id = {}.", self.id); queue_send_func!(self.to_client_tx.send(EditorMessage { id: self.id, message: EditorMessageContents::Update(update) @@ -1083,7 +1100,10 @@ impl TranslationTask { cfw_version, &code_mirror_translated, ); - debug!("Sending re-translation update back to the Client."); + debug!( + "Sending re-translation update id = {} back to the Client.", + self.id + ); queue_send_func!(self.to_client_tx.send(EditorMessage { id: self.id, message: EditorMessageContents::Update( @@ -1099,7 +1119,8 @@ impl TranslationTask { ) })); self.id += MESSAGE_ID_INCREMENT; - // Update with what was just sent to the client. + // Update with what was just sent to the + // client. self.code_mirror_doc = code_mirror_translated.doc; self.code_mirror_doc_blocks = Some(code_mirror_translated.doc_blocks); @@ -1144,6 +1165,7 @@ impl TranslationTask { } }, }; + debug!("Sending update id = {}", client_message.id); queue_send_func!(self.to_ide_tx.send(EditorMessage { id: client_message.id, message: EditorMessageContents::Update(UpdateMessageContents { diff --git a/server/src/webserver.rs b/server/src/webserver.rs index 32d05a35..724de16f 100644 --- a/server/src/webserver.rs +++ b/server/src/webserver.rs @@ -116,7 +116,7 @@ pub struct ProcessingTaskHttpRequest { /// The path of the file requested. pub file_path: PathBuf, /// Flags for this file: none, TOC, raw. - flags: ProcessingTaskHttpRequestFlags, + pub flags: ProcessingTaskHttpRequestFlags, /// True if test mode is enabled. is_test_mode: bool, /// A queue to send the response back to the HTTP task. @@ -124,7 +124,7 @@ pub struct ProcessingTaskHttpRequest { } #[derive(Debug, PartialEq)] -enum ProcessingTaskHttpRequestFlags { +pub enum ProcessingTaskHttpRequestFlags { // No flags provided. None, // This file is a TOC. @@ -214,11 +214,18 @@ pub enum EditorMessageContents { OpenUrl(String), // #### These messages may only be sent by the Server. - /// Ask the IDE if the provided file is loaded. If so, the IDE should - /// respond by sending a `LoadFile` with the requested file. If not, the - /// returned `Result` should indicate the error "not loaded". Valid - /// destinations: IDE. - LoadFile(PathBuf), + /// Ask the IDE if the provided file is already loaded. The IDE should + /// always respond to this with a `ResultOkTypes::LoadFile` message. If the + /// file was loaded, the IDE responds with `Some((` contents of file, + /// version of file `))`; if the was isn't loaded, it responds with `None`. + /// The boolean value accompanying this message Valid destinations: IDE. + LoadFile( + // Path to the file to load. + PathBuf, + // `is_current` - true if this is the current file being edited/viewed + // by the Client. + bool, + ), /// This may only be used to respond to an `Opened` message; it contains the /// HTML for the CodeChat Editor Client to display in its built-in browser. /// Valid destinations: IDE. @@ -249,13 +256,21 @@ pub enum ResultOkTypes { Void, /// The `LoadFile` message provides file contents and a revision number, if /// available. This message may only be sent from the IDE to the Server. - LoadFile(Option<(String, f64)>), + LoadFile( + Option<( + // The text of the file. + String, + // The version of the file; ignored if the corresponding `LoadFile` + // request's `is_current` value was false. + f64, + )>, + ), } #[derive(Debug, Serialize, Deserialize, TS, PartialEq, thiserror::Error)] pub enum ResultErrTypes { - #[error("File out of sync; update rejected")] - OutOfSync, + #[error("File out of sync; update rejected. Expected version {0} but saw version {1}")] + OutOfSync(f64, f64), #[error("IDE must not send this message")] IdeIllegalMessage, #[error("Client not allowed to send this message")] @@ -426,19 +441,6 @@ pub const INITIAL_IDE_MESSAGE_ID: f64 = INITIAL_CLIENT_MESSAGE_ID + 1.0; /// assuming an average of 1 message/second.) pub const MESSAGE_ID_INCREMENT: f64 = 3.0; -/// Synchronization state between the Client, Server, and IDE. -#[derive(PartialEq)] -pub enum SyncState { - /// Indicates the Client, IDE, and server's documents are identical. - InSync, - /// An Update message is in flight; the documents are out of sync until the - /// response to the Update is received. - Pending(f64), - /// A CurrentFile message was sent, guaranteeing that documents are out of - /// sync. - OutOfSync, -} - const MATHJAX_TAGS: &str = concatdoc!( r#"