diff --git a/builder/Cargo.lock b/builder/Cargo.lock index ee92f543..aa505621 100644 --- a/builder/Cargo.lock +++ b/builder/Cargo.lock @@ -74,14 +74,15 @@ dependencies = [ "clap", "cmd_lib", "current_platform", + "path-slash", "regex", ] [[package]] name = "clap" -version = "4.5.32" +version = "4.5.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83" +checksum = "e958897981290da2a852763fe9cdb89cd36977a5d729023127095fa94d95e2ff" dependencies = [ "clap_builder", "clap_derive", @@ -89,9 +90,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.32" +version = "4.5.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8" +checksum = "83b0f35019843db2160b5bb19ae09b4e6411ac33fc6a712003c33e03090e2489" dependencies = [ "anstream", "anstyle", @@ -228,9 +229,9 @@ checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "log" -version = "0.4.26" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" @@ -240,9 +241,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "once_cell" -version = "1.21.1" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "os_pipe" @@ -254,6 +255,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "path-slash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42" + [[package]] name = "proc-macro-error-attr2" version = "2.0.0" diff --git a/builder/Cargo.toml b/builder/Cargo.toml index 6acdc3c6..1b0af726 100644 --- a/builder/Cargo.toml +++ b/builder/Cargo.toml @@ -27,4 +27,5 @@ edition = "2024" clap = { version = "4.5.19", features = ["derive"] } cmd_lib = "1.9.5" current_platform = "0.2.0" +path-slash = "0.2.1" regex = "1.11.1" diff --git a/builder/src/main.rs b/builder/src/main.rs index ea5bae09..d90db708 100644 --- a/builder/src/main.rs +++ b/builder/src/main.rs @@ -31,12 +31,18 @@ // ------- // // ### Standard library -use std::{ffi::OsStr, fs, io, path::Path, process::Command}; +use std::{ + ffi::OsStr, + fs, io, + path::{Path, PathBuf}, + process::Command, +}; // ### Third-party use clap::{Parser, Subcommand}; use cmd_lib::run_cmd; use current_platform::CURRENT_PLATFORM; +use path_slash::PathBufExt; use regex::Regex; // ### Local @@ -68,6 +74,12 @@ enum Commands { Test, /// Build everything. Build, + /// Build the Client. + ClientBuild { + /// True to build for distribution, instead of development. + #[arg(short, long, default_value_t = false)] + dist: bool, + }, /// Change the version for the client, server, and extensions. ChangeVersion { /// The new version number, such as "0.1.1". @@ -98,17 +110,18 @@ enum Commands { // These functions are called by the build support functions. /// On Windows, scripts must be run from a shell; on Linux and OS X, scripts are /// directly executable. This function runs a script regardless of OS. -fn run_script, P: AsRef + std::fmt::Display>( +fn run_script, A: AsRef, P: AsRef + std::fmt::Display>( // The script to run. script: T, // Arguments to pass. - args: &[T], + args: &[A], // The directory to run the script in. dir: P, // True to report errors based on the process' exit code; false to ignore // the code. check_exit_code: bool, ) -> io::Result<()> { + let script = OsStr::new(script.as_ref()); let mut process; if cfg!(windows) { process = Command::new("cmd"); @@ -136,9 +149,11 @@ fn run_script, P: AsRef + std::fmt::Display>( /// programs (`robocopy`/`rsync`) to accomplish this. Very important: the `src` /// **must** end with a `/`, otherwise the Windows and Linux copies aren't /// identical. -fn quick_copy_dir>(src: P, dest: P, files: Option

) -> io::Result<()> { +fn quick_copy_dir>(src: P, dest: P, files: Option

) -> io::Result<()> { assert!(src.as_ref().to_string_lossy().ends_with('/')); let mut copy_process; + let src = OsStr::new(src.as_ref()); + let dest = OsStr::new(dest.as_ref()); #[cfg(windows)] { // From `robocopy /?`: @@ -165,31 +180,26 @@ fn quick_copy_dir>(src: P, dest: P, files: Option

) -> io::Res .args([ "/MIR", "/MT", "/NFL", "/NDL", "/NJH", "/NJS", "/NP", "/NS", "/NC", ]) - .arg(&src) - .arg(&dest); + .arg(src) + .arg(dest); // Robocopy expects the files to copy after the dest. if let Some(files_) = &files { - copy_process.arg(files_); + copy_process.arg(OsStr::new(files_.as_ref())); } } #[cfg(not(windows))] { // Create the dest directory, since old CI OSes don't support `rsync // --mkpath`. - run_script( - "mkdir", - &["-p", dest.as_ref().to_str().unwrap()], - "./", - true, - )?; + run_script("mkdir", &["-p", dest.to_str().unwrap()], "./", true)?; let mut tmp; let src_combined = match files.as_ref() { Some(files_) => { - tmp = src.as_ref().to_os_string(); - tmp.push(files_); + tmp = src.to_os_string(); + tmp.push(OsStr::new(files_.as_ref())); tmp.as_os_str() } - None => src.as_ref(), + None => src, }; // Use bash to perform globbing, since rsync doesn't do this. @@ -199,7 +209,7 @@ fn quick_copy_dir>(src: P, dest: P, files: Option

) -> io::Res format!( "rsync --archive --delete {} {}", &src_combined.to_str().unwrap(), - &dest.as_ref().to_str().unwrap() + &dest.to_str().unwrap() ) .as_str(), ]); @@ -410,7 +420,7 @@ fn run_test() -> io::Result<()> { fn run_build() -> io::Result<()> { // Clean out all bundled files before the rebuild. remove_dir_all_if_exists("../client/static/bundled")?; - run_script("npm", &["run", "build"], "../client", true)?; + run_client_build(false)?; run_script("npm", &["run", "compile"], "../extensions/VSCode", true)?; run_cmd!( cargo build --manifest-path=../builder/Cargo.toml; @@ -419,6 +429,82 @@ fn run_build() -> io::Result<()> { Ok(()) } +// Build the NPM Client. +fn run_client_build( + // True to build for distribution, not development. + dist: bool, +) -> io::Result<()> { + let esbuild = PathBuf::from_slash("node_modules/.bin/esbuild"); + let distflag = if dist { "--minify" } else { "--sourcemap" }; + // This makes the program work from either the `server/` or `client/` directories. + let rel_path = "../client"; + + // The main build for the Client. + run_script( + &esbuild, + &[ + "src/CodeChatEditorFramework.mts", + "src/CodeChatEditor.mts", + "src/CodeChatEditor-test.mts", + "src/css/CodeChatEditorProject.css", + "src/css/CodeChatEditor.css", + "--bundle", + "--outdir=./static/bundled", + distflag, + "--format=esm", + "--splitting", + "--metafile=meta.json", + "--entry-names=[dir]/[name]-[hash]", + ], + rel_path, + true, + )?; + // io::Result<()> { let replacement_string = format!("${{1}}{new_version}${{2}}"); search_and_replace_file( @@ -490,6 +576,7 @@ impl Cli { Commands::Update => run_update(), Commands::Test => run_test(), Commands::Build => run_build(), + Commands::ClientBuild { dist } => run_client_build(*dist), Commands::ChangeVersion { new_version } => run_change_version(new_version), Commands::Prerelease => run_prerelease(), Commands::Postrelease { target, .. } => run_postrelease(target), diff --git a/client/package-lock.json b/client/package-lock.json index a055d453..dc0e5cfd 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "codechat-editor-client", - "version": "0.1.14", + "version": "0.1.15", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "codechat-editor-client", - "version": "0.1.14", + "version": "0.1.15", "license": "GPL-3.0-or-later", "dependencies": { "@codemirror/lang-cpp": "^6", @@ -28,6 +28,7 @@ "mathjax-modern-font": "4.0.0-beta.7", "mermaid": "^11", "npm-check-updates": "^17.1.15", + "pdfjs-dist": "^5", "tinymce": "^7" }, "devDependencies": { @@ -43,7 +44,6 @@ "eslint-config-prettier": "^10", "eslint-plugin-import": "^2", "eslint-plugin-prettier": "^5", - "run-script-os": "^1", "typescript": "^5" } }, @@ -306,9 +306,9 @@ } }, "node_modules/@codemirror/lint": { - "version": "6.8.4", - "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.4.tgz", - "integrity": "sha512-u4q7PnZlJUojeRe8FJa/njJcMctISGgPQ4PnWsd9268R4ZTtU+tfFYmwkBvgcrK2+QQ8tYFVALVb5fVJykKc5A==", + "version": "6.8.5", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz", + "integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==", "license": "MIT", "dependencies": { "@codemirror/state": "^6.0.0", @@ -337,9 +337,9 @@ } }, "node_modules/@codemirror/view": { - "version": "6.36.4", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.4.tgz", - "integrity": "sha512-ZQ0V5ovw/miKEXTvjgzRyjnrk9TwriUB1k4R5p7uNnHR9Hus+D1SXHGdJshijEzPFjU25xea/7nhIeSqYFKdbA==", + "version": "6.36.5", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.5.tgz", + "integrity": "sha512-cd+FZEUlu3GQCYnguYm3EkhJ8KJVisqqUsCOKedBoAt/d9c76JUUap6U0UrpElln5k6VyrEOYliMuDAKIeDQLg==", "license": "MIT", "dependencies": { "@codemirror/state": "^6.5.0", @@ -348,9 +348,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz", - "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", + "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", "cpu": [ "ppc64" ], @@ -365,9 +365,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz", - "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", + "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", "cpu": [ "arm" ], @@ -382,9 +382,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz", - "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", + "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", "cpu": [ "arm64" ], @@ -399,9 +399,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz", - "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", + "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", "cpu": [ "x64" ], @@ -416,9 +416,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz", - "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", + "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", "cpu": [ "arm64" ], @@ -433,9 +433,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz", - "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", + "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", "cpu": [ "x64" ], @@ -450,9 +450,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz", - "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", + "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", "cpu": [ "arm64" ], @@ -467,9 +467,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz", - "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", + "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", "cpu": [ "x64" ], @@ -484,9 +484,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz", - "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", + "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", "cpu": [ "arm" ], @@ -501,9 +501,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz", - "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", + "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", "cpu": [ "arm64" ], @@ -518,9 +518,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz", - "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", + "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", "cpu": [ "ia32" ], @@ -535,9 +535,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz", - "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", + "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", "cpu": [ "loong64" ], @@ -552,9 +552,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz", - "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", + "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", "cpu": [ "mips64el" ], @@ -569,9 +569,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz", - "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", + "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", "cpu": [ "ppc64" ], @@ -586,9 +586,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz", - "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", + "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", "cpu": [ "riscv64" ], @@ -603,9 +603,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz", - "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", + "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", "cpu": [ "s390x" ], @@ -620,9 +620,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz", - "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", + "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", "cpu": [ "x64" ], @@ -637,9 +637,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz", - "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", + "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", "cpu": [ "arm64" ], @@ -654,9 +654,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz", - "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", + "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", "cpu": [ "x64" ], @@ -671,9 +671,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz", - "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", + "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", "cpu": [ "arm64" ], @@ -688,9 +688,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz", - "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", + "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", "cpu": [ "x64" ], @@ -705,9 +705,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz", - "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", + "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", "cpu": [ "x64" ], @@ -722,9 +722,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz", - "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", + "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", "cpu": [ "arm64" ], @@ -739,9 +739,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz", - "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", + "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", "cpu": [ "ia32" ], @@ -756,9 +756,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz", - "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", + "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", "cpu": [ "x64" ], @@ -841,9 +841,9 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.1.0.tgz", - "integrity": "sha512-kLrdPDJE1ckPo94kmPPf9Hfd0DU0Jw6oKYrhe+pwSC0iTUInmTa+w6fw8sGgcfkFJGNdWOUeOaDM4quW4a7OkA==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.0.tgz", + "integrity": "sha512-yJLLmLexii32mGrhW29qvU3QBVTu0GUmEf/J4XsBtVhp4JkIUFN/BjWqTF63yRvGApIDpZm5fa97LtYtINmfeQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -864,9 +864,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.0.tgz", - "integrity": "sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, "license": "MIT", "dependencies": { @@ -912,9 +912,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.22.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.22.0.tgz", - "integrity": "sha512-vLFajx9o8d1/oL2ZkpMYbkLv8nDB6yaIwFNt7nI4+I80U/z03SxmfOMsLbvWr3p7C+Wnoh//aOu2pQW8cS0HCQ==", + "version": "9.23.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.23.0.tgz", + "integrity": "sha512-35MJ8vCPU0ZMxo7zfev2pypqTwWTofFZO6m4KAtdoFhRpLJUpHTZZ+KB3C7Hb1d7bULYwO4lJXGCi5Se+8OMbw==", "dev": true, "license": "MIT", "engines": { @@ -1052,9 +1052,9 @@ "license": "MIT" }, "node_modules/@lezer/cpp": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@lezer/cpp/-/cpp-1.1.2.tgz", - "integrity": "sha512-macwKtyeUO0EW86r3xWQCzOV9/CF8imJLpJlPv3sDY57cPGeUZ8gXWOWNlJr52TVByMV3PayFQCA5SHEERDmVQ==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@lezer/cpp/-/cpp-1.1.3.tgz", + "integrity": "sha512-ykYvuFQKGsRi6IcE+/hCSGUhb/I4WPjd3ELhEblm2wS2cOznDFzO+ubK2c+ioysOnlZ3EduV+MVQFCPzAIoY3w==", "license": "MIT", "dependencies": { "@lezer/common": "^1.2.0", @@ -1063,9 +1063,9 @@ } }, "node_modules/@lezer/css": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.1.10.tgz", - "integrity": "sha512-V5/89eDapjeAkWPBpWEfQjZ1Hag3aYUUJOL8213X0dFRuXJ4BXa5NKl9USzOnaLod4AOpmVCkduir2oKwZYZtg==", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.1.11.tgz", + "integrity": "sha512-FuAnusbLBl1SEAtfN8NdShxYJiESKw9LAFysfea1T96jD3ydBn12oYjaSG1a04BQRIUd93/0D8e5CV1cUMkmQg==", "license": "MIT", "dependencies": { "@lezer/common": "^1.2.0", @@ -1168,9 +1168,9 @@ } }, "node_modules/@lezer/python": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/@lezer/python/-/python-1.1.16.tgz", - "integrity": "sha512-ievIWylIZA5rNgAyHgA06/Y76vMUISKaYL9WrtjU8rCTTEzyZYo2jz9ER2YBdnN6dxCyS7eaK4HJCzamoAMKZw==", + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/@lezer/python/-/python-1.1.17.tgz", + "integrity": "sha512-Iz0doICPko9uv2chIfSsViNSugNa4PWhxs17jtFd0ZMt+OieDq3wxtFOdmj7wtst3FWDeJkB0CxWNot0BlYixw==", "license": "MIT", "dependencies": { "@lezer/common": "^1.2.0", @@ -1207,12 +1207,194 @@ "license": "MIT" }, "node_modules/@mermaid-js/parser": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.3.0.tgz", - "integrity": "sha512-HsvL6zgE5sUPGgkIDlmAWR1HTNHz2Iy11BAWPTa4Jjabkpguy4Ze2gzfLrg6pdRuBvFwgUYyxiaNqZwrEEXepA==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.4.0.tgz", + "integrity": "sha512-wla8XOWvQAwuqy+gxiZqY+c7FokraOTHRWMsbB4AgRx9Sy7zKslNyejy7E+a77qHfey5GXw/ik3IXv/NHMJgaA==", "license": "MIT", "dependencies": { - "langium": "3.0.0" + "langium": "3.3.1" + } + }, + "node_modules/@napi-rs/canvas": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.68.tgz", + "integrity": "sha512-LQESrePLEBLvhuFkXx9jjBXRC2ClYsO5mqQ1m/puth5z9SOuM3N/B3vDuqnC3RJFktDktyK9khGvo7dTkqO9uQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@napi-rs/canvas-android-arm64": "0.1.68", + "@napi-rs/canvas-darwin-arm64": "0.1.68", + "@napi-rs/canvas-darwin-x64": "0.1.68", + "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.68", + "@napi-rs/canvas-linux-arm64-gnu": "0.1.68", + "@napi-rs/canvas-linux-arm64-musl": "0.1.68", + "@napi-rs/canvas-linux-riscv64-gnu": "0.1.68", + "@napi-rs/canvas-linux-x64-gnu": "0.1.68", + "@napi-rs/canvas-linux-x64-musl": "0.1.68", + "@napi-rs/canvas-win32-x64-msvc": "0.1.68" + } + }, + "node_modules/@napi-rs/canvas-android-arm64": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.68.tgz", + "integrity": "sha512-h1KcSR4LKLfRfzeBH65xMxbWOGa1OtMFQbCMVlxPCkN1Zr+2gK+70pXO5ktojIYcUrP6KDcOwoc8clho5ccM/w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-darwin-arm64": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.68.tgz", + "integrity": "sha512-/VURlrAD4gDoxW1GT/b0nP3fRz/fhxmHI/xznTq2FTwkQLPOlLkDLCvTmQ7v6LtGKdc2Ed6rvYpRan+JXThInQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-darwin-x64": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.68.tgz", + "integrity": "sha512-tEpvGR6vCLTo1Tx9wmDnoOKROpw57wiCWwCpDOuVlj/7rqEJOUYr9ixW4aRJgmeGBrZHgevI0EURys2ER6whmg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.68.tgz", + "integrity": "sha512-U9xbJsumPOiAYeAFZMlHf62b9dGs2HJ6Q5xt7xTB0uEyPeurwhgYBWGgabdsEidyj38YuzI/c3LGBbSQB3vagw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-gnu": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.68.tgz", + "integrity": "sha512-KFkn8wEm3mPnWD4l8+OUUkxylSJuN5q9PnJRZJgv15RtCA1bgxIwTkBhI/+xuyVMcHqON9sXq7cDkEJtHm35dg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-musl": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.68.tgz", + "integrity": "sha512-IQzts91rCdOALXBWQxLZRCEDrfFTGDtNRJMNu+2SKZ1uT8cmPQkPwVk5rycvFpvgAcmiFiOSCp1aRrlfU8KPpQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.68.tgz", + "integrity": "sha512-e9AS5UttoIKqXSmBzKZdd3NErSVyOEYzJfNOCGtafGk1//gibTwQXGlSXmAKuErqMp09pyk9aqQRSYzm1AQfBw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-gnu": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.68.tgz", + "integrity": "sha512-Pa/I36VE3j57I3Obhrr+J48KGFfkZk2cJN/2NmW/vCgmoF7kCP6aTVq5n+cGdGWLd/cN9CJ9JvNwEoMRDghu0g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-musl": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.68.tgz", + "integrity": "sha512-9c6rkc5195wNxuUHJdf4/mmnq433OQey9TNvQ9LspJazvHbfSkTij8wtKjASVQsJyPDva4fkWOeV/OQ7cLw0GQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-win32-x64-msvc": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.68.tgz", + "integrity": "sha512-Fc5Dez23u0FoSATurT6/w1oMytiRnKWEinHivdMvXpge6nG4YvhrASrtqMk8dGJMVQpHr8QJYF45rOrx2YU2Aw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" } }, "node_modules/@nodelib/fs.scandir": { @@ -1254,9 +1436,9 @@ } }, "node_modules/@pkgr/core": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", - "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.0.tgz", + "integrity": "sha512-vsJDAkYR6qCPu+ioGScGiMYR7LvZYIXh/dlQeviqoTWNCVfKTLYD/LkNWH4Mxsv2a5vpIRc77FN5DnmK1eBggQ==", "dev": true, "license": "MIT", "engines": { @@ -1274,9 +1456,9 @@ "license": "MIT" }, "node_modules/@types/chai": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.0.tgz", - "integrity": "sha512-FWnQYdrG9FAC8KgPVhDFfrPL1FBsL3NtIt2WsxKvwu/61K6HiuDF3xAb7c7w/k9ML2QOUHcwTgU7dKLFPK6sBg==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.1.tgz", + "integrity": "sha512-iu1JLYmGmITRzUgNiLMZD3WCoFzpYtueuyAgHTXqgwSRAMIlFTnZqG6/xenkpUGRJEzSfklUTI4GNSzks/dc0w==", "dev": true, "license": "MIT", "dependencies": { @@ -1544,9 +1726,9 @@ "license": "MIT" }, "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", "dev": true, "license": "MIT" }, @@ -1585,9 +1767,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.13.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz", - "integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==", + "version": "22.13.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.14.tgz", + "integrity": "sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w==", "dev": true, "license": "MIT", "dependencies": { @@ -1602,17 +1784,17 @@ "optional": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.1.tgz", - "integrity": "sha512-2X3mwqsj9Bd3Ciz508ZUtoQQYpOhU/kWoUqIf49H8Z0+Vbh6UF/y0OEYp0Q0axOGzaBGs7QxRwq0knSQ8khQNA==", + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.28.0.tgz", + "integrity": "sha512-lvFK3TCGAHsItNdWZ/1FkvpzCxTHUVuFrdnOGLMa0GGCFIbCgQWVk3CzCGdA7kM3qGVc+dfW9tr0Z/sHnGDFyg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.26.1", - "@typescript-eslint/type-utils": "8.26.1", - "@typescript-eslint/utils": "8.26.1", - "@typescript-eslint/visitor-keys": "8.26.1", + "@typescript-eslint/scope-manager": "8.28.0", + "@typescript-eslint/type-utils": "8.28.0", + "@typescript-eslint/utils": "8.28.0", + "@typescript-eslint/visitor-keys": "8.28.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1632,16 +1814,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.26.1.tgz", - "integrity": "sha512-w6HZUV4NWxqd8BdeFf81t07d7/YV9s7TCWrQQbG5uhuvGUAW+fq1usZ1Hmz9UPNLniFnD8GLSsDpjP0hm1S4lQ==", + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.28.0.tgz", + "integrity": "sha512-LPcw1yHD3ToaDEoljFEfQ9j2xShY367h7FZ1sq5NJT9I3yj4LHer1Xd1yRSOdYy9BpsrxU7R+eoDokChYM53lQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.26.1", - "@typescript-eslint/types": "8.26.1", - "@typescript-eslint/typescript-estree": "8.26.1", - "@typescript-eslint/visitor-keys": "8.26.1", + "@typescript-eslint/scope-manager": "8.28.0", + "@typescript-eslint/types": "8.28.0", + "@typescript-eslint/typescript-estree": "8.28.0", + "@typescript-eslint/visitor-keys": "8.28.0", "debug": "^4.3.4" }, "engines": { @@ -1657,14 +1839,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.26.1.tgz", - "integrity": "sha512-6EIvbE5cNER8sqBu6V7+KeMZIC1664d2Yjt+B9EWUXrsyWpxx4lEZrmvxgSKRC6gX+efDL/UY9OpPZ267io3mg==", + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.28.0.tgz", + "integrity": "sha512-u2oITX3BJwzWCapoZ/pXw6BCOl8rJP4Ij/3wPoGvY8XwvXflOzd1kLrDUUUAIEdJSFh+ASwdTHqtan9xSg8buw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.26.1", - "@typescript-eslint/visitor-keys": "8.26.1" + "@typescript-eslint/types": "8.28.0", + "@typescript-eslint/visitor-keys": "8.28.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1675,14 +1857,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.26.1.tgz", - "integrity": "sha512-Kcj/TagJLwoY/5w9JGEFV0dclQdyqw9+VMndxOJKtoFSjfZhLXhYjzsQEeyza03rwHx2vFEGvrJWJBXKleRvZg==", + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.28.0.tgz", + "integrity": "sha512-oRoXu2v0Rsy/VoOGhtWrOKDiIehvI+YNrDk5Oqj40Mwm0Yt01FC/Q7nFqg088d3yAsR1ZcZFVfPCTTFCe/KPwg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.26.1", - "@typescript-eslint/utils": "8.26.1", + "@typescript-eslint/typescript-estree": "8.28.0", + "@typescript-eslint/utils": "8.28.0", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, @@ -1699,9 +1881,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.26.1.tgz", - "integrity": "sha512-n4THUQW27VmQMx+3P+B0Yptl7ydfceUj4ON/AQILAASwgYdZ/2dhfymRMh5egRUrvK5lSmaOm77Ry+lmXPOgBQ==", + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.28.0.tgz", + "integrity": "sha512-bn4WS1bkKEjx7HqiwG2JNB3YJdC1q6Ue7GyGlwPHyt0TnVq6TtD/hiOdTZt71sq0s7UzqBFXD8t8o2e63tXgwA==", "dev": true, "license": "MIT", "engines": { @@ -1713,14 +1895,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.1.tgz", - "integrity": "sha512-yUwPpUHDgdrv1QJ7YQal3cMVBGWfnuCdKbXw1yyjArax3353rEJP1ZA+4F8nOlQ3RfS2hUN/wze3nlY+ZOhvoA==", + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.28.0.tgz", + "integrity": "sha512-H74nHEeBGeklctAVUvmDkxB1mk+PAZ9FiOMPFncdqeRBXxk1lWSYraHw8V12b7aa6Sg9HOBNbGdSHobBPuQSuA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.26.1", - "@typescript-eslint/visitor-keys": "8.26.1", + "@typescript-eslint/types": "8.28.0", + "@typescript-eslint/visitor-keys": "8.28.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1740,16 +1922,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.26.1.tgz", - "integrity": "sha512-V4Urxa/XtSUroUrnI7q6yUTD3hDtfJ2jzVfeT3VK0ciizfK2q/zGC0iDh1lFMUZR8cImRrep6/q0xd/1ZGPQpg==", + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.28.0.tgz", + "integrity": "sha512-OELa9hbTYciYITqgurT1u/SzpQVtDLmQMFzy/N8pQE+tefOyCWT79jHsav294aTqV1q1u+VzqDGbuujvRYaeSQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.26.1", - "@typescript-eslint/types": "8.26.1", - "@typescript-eslint/typescript-estree": "8.26.1" + "@typescript-eslint/scope-manager": "8.28.0", + "@typescript-eslint/types": "8.28.0", + "@typescript-eslint/typescript-estree": "8.28.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1764,13 +1946,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.1.tgz", - "integrity": "sha512-AjOC3zfnxd6S4Eiy3jwktJPclqhFHNyd8L6Gycf9WUPoKZpgM5PjkxY1X7uSy61xVpiJDhhk7XT2NVsN3ALTWg==", + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.28.0.tgz", + "integrity": "sha512-hbn8SZ8w4u2pRwgQ1GlUrPKE+t2XvcCW5tTRF7j6SMYIuYG37XuzIW44JCZPa36evi0Oy2SnM664BlIaAuQcvg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.26.1", + "@typescript-eslint/types": "8.28.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -1904,18 +2086,19 @@ } }, "node_modules/array.prototype.findlastindex": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", - "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", + "es-abstract": "^1.23.9", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -3089,9 +3272,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz", - "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", + "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -3102,31 +3285,31 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.1", - "@esbuild/android-arm": "0.25.1", - "@esbuild/android-arm64": "0.25.1", - "@esbuild/android-x64": "0.25.1", - "@esbuild/darwin-arm64": "0.25.1", - "@esbuild/darwin-x64": "0.25.1", - "@esbuild/freebsd-arm64": "0.25.1", - "@esbuild/freebsd-x64": "0.25.1", - "@esbuild/linux-arm": "0.25.1", - "@esbuild/linux-arm64": "0.25.1", - "@esbuild/linux-ia32": "0.25.1", - "@esbuild/linux-loong64": "0.25.1", - "@esbuild/linux-mips64el": "0.25.1", - "@esbuild/linux-ppc64": "0.25.1", - "@esbuild/linux-riscv64": "0.25.1", - "@esbuild/linux-s390x": "0.25.1", - "@esbuild/linux-x64": "0.25.1", - "@esbuild/netbsd-arm64": "0.25.1", - "@esbuild/netbsd-x64": "0.25.1", - "@esbuild/openbsd-arm64": "0.25.1", - "@esbuild/openbsd-x64": "0.25.1", - "@esbuild/sunos-x64": "0.25.1", - "@esbuild/win32-arm64": "0.25.1", - "@esbuild/win32-ia32": "0.25.1", - "@esbuild/win32-x64": "0.25.1" + "@esbuild/aix-ppc64": "0.25.2", + "@esbuild/android-arm": "0.25.2", + "@esbuild/android-arm64": "0.25.2", + "@esbuild/android-x64": "0.25.2", + "@esbuild/darwin-arm64": "0.25.2", + "@esbuild/darwin-x64": "0.25.2", + "@esbuild/freebsd-arm64": "0.25.2", + "@esbuild/freebsd-x64": "0.25.2", + "@esbuild/linux-arm": "0.25.2", + "@esbuild/linux-arm64": "0.25.2", + "@esbuild/linux-ia32": "0.25.2", + "@esbuild/linux-loong64": "0.25.2", + "@esbuild/linux-mips64el": "0.25.2", + "@esbuild/linux-ppc64": "0.25.2", + "@esbuild/linux-riscv64": "0.25.2", + "@esbuild/linux-s390x": "0.25.2", + "@esbuild/linux-x64": "0.25.2", + "@esbuild/netbsd-arm64": "0.25.2", + "@esbuild/netbsd-x64": "0.25.2", + "@esbuild/openbsd-arm64": "0.25.2", + "@esbuild/openbsd-x64": "0.25.2", + "@esbuild/sunos-x64": "0.25.2", + "@esbuild/win32-arm64": "0.25.2", + "@esbuild/win32-ia32": "0.25.2", + "@esbuild/win32-x64": "0.25.2" } }, "node_modules/escape-string-regexp": { @@ -3143,19 +3326,19 @@ } }, "node_modules/eslint": { - "version": "9.22.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.22.0.tgz", - "integrity": "sha512-9V/QURhsRN40xuHXWjV64yvrzMjcz7ZyNoF2jJFmy9j/SLk0u1OLSZgXi28MrXjymnjEGSR80WCdab3RGMDveQ==", + "version": "9.23.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.23.0.tgz", + "integrity": "sha512-jV7AbNoFPAY1EkFYpLq5bslU9NLNO8xnEeQXwErNibVryjk67wHVmddTBilc5srIttJDBrB0eMHKZBFbSIABCw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.2", - "@eslint/config-helpers": "^0.1.0", + "@eslint/config-helpers": "^0.2.0", "@eslint/core": "^0.12.0", - "@eslint/eslintrc": "^3.3.0", - "@eslint/js": "9.22.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.23.0", "@eslint/plugin-kit": "^0.2.7", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -3345,14 +3528,14 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.3.tgz", - "integrity": "sha512-qJ+y0FfCp/mQYQ/vWQ3s7eUlFEL4PyKfAJxsnYTJ4YT73nsJBWqmEpFryxV9OeUiqmsTsYJ5Y+KDNaeP31wrRw==", + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.5.tgz", + "integrity": "sha512-IKKP8R87pJyMl7WWamLgPkloB16dagPIdd2FjBDbyRYPKo93wS/NbCOPh6gH+ieNLC+XZrhJt/kWj0PS/DFdmg==", "dev": true, "license": "MIT", "dependencies": { "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.9.1" + "synckit": "^0.10.2" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -3363,7 +3546,7 @@ "peerDependencies": { "@types/eslint": ">=8.0.0", "eslint": ">=8.0.0", - "eslint-config-prettier": "*", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", "prettier": ">=3.0.0" }, "peerDependenciesMeta": { @@ -4502,9 +4685,9 @@ "license": "MIT" }, "node_modules/langium": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/langium/-/langium-3.0.0.tgz", - "integrity": "sha512-+Ez9EoiByeoTu/2BXmEaZ06iPNXM6thWJp02KfBO/raSMyCJ4jw7AkWWa+zBCTm0+Tw1Fj9FOxdqSskyN5nAwg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/langium/-/langium-3.3.1.tgz", + "integrity": "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==", "license": "MIT", "dependencies": { "chevrotain": "~11.0.3", @@ -4640,14 +4823,14 @@ } }, "node_modules/mermaid": { - "version": "11.5.0", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.5.0.tgz", - "integrity": "sha512-IYhyukID3zzDj1EihKiN1lp+PXNImoJ3Iyz73qeDAgnus4BNGsJV1n471P4PyeGxPVONerZxignwGxGTSwZnlg==", + "version": "11.6.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.6.0.tgz", + "integrity": "sha512-PE8hGUy1LDlWIHWBP05SFdqUHGmRcCcK4IzpOKPE35eOw+G9zZgcnMpyunJVUEOgb//KBORPjysKndw8bFLuRg==", "license": "MIT", "dependencies": { "@braintree/sanitize-url": "^7.0.4", "@iconify/utils": "^2.1.33", - "@mermaid-js/parser": "^0.3.0", + "@mermaid-js/parser": "^0.4.0", "@types/d3": "^7.4.3", "cytoscape": "^3.29.3", "cytoscape-cose-bilkent": "^4.1.0", @@ -4750,9 +4933,9 @@ "license": "MIT" }, "node_modules/npm-check-updates": { - "version": "17.1.15", - "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-17.1.15.tgz", - "integrity": "sha512-miATvKu5rjec/1wxc5TGDjpsucgtCHwRVZorZpDkS6NzdWXfnUWlN4abZddWb7XSijAuBNzzYglIdTm9SbgMVg==", + "version": "17.1.16", + "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-17.1.16.tgz", + "integrity": "sha512-9nohkfjLRzLfsLVGbO34eXBejvrOOTuw5tvNammH73KEFG5XlFoi3G2TgjTExHtnrKWCbZ+mTT+dbNeSjASIPw==", "license": "Apache-2.0", "bin": { "ncu": "build/cli.js", @@ -4999,6 +5182,18 @@ "node": ">= 14.16" } }, + "node_modules/pdfjs-dist": { + "version": "5.1.91", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.1.91.tgz", + "integrity": "sha512-qSIADdagooJB4wWCBnrBJjRvASevmxL0BwafvOuKJG5uTQdYoFBrhrRYnucKNiSc9qS6JIk0hC5y1yktFljXkA==", + "license": "Apache-2.0", + "engines": { + "node": ">=20" + }, + "optionalDependencies": { + "@napi-rs/canvas": "^0.1.67" + } + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -5100,9 +5295,9 @@ } }, "node_modules/quansync": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.8.tgz", - "integrity": "sha512-4+saucphJMazjt7iOM27mbFCk+D9dd/zmgMDCzRZ8MEoBfYp7lAvoN38et/phRQF6wOPMy/OROBGgoWeSKyluA==", + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.10.tgz", + "integrity": "sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==", "funding": [ { "type": "individual", @@ -5264,17 +5459,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/run-script-os": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/run-script-os/-/run-script-os-1.1.6.tgz", - "integrity": "sha512-ql6P2LzhBTTDfzKts+Qo4H94VUKpxKDFz6QxxwaUZN0mwvi7L3lpOI7BqPCq7lgDh3XLl0dpeXwfcVIitlrYrw==", - "dev": true, - "license": "MIT", - "bin": { - "run-os": "index.js", - "run-script-os": "index.js" - } - }, "node_modules/rw": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", @@ -5624,14 +5808,14 @@ } }, "node_modules/synckit": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", - "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.10.3.tgz", + "integrity": "sha512-R1urvuyiTaWfeCggqEvpDJwAlDVdsT9NM+IP//Tk2x7qHCkSvBk/fwFgw/TLAHzZlrAnnazMcRw0ZD8HlYFTEQ==", "dev": true, "license": "MIT", "dependencies": { - "@pkgr/core": "^0.1.0", - "tslib": "^2.6.2" + "@pkgr/core": "^0.2.0", + "tslib": "^2.8.1" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -5647,9 +5831,9 @@ "license": "MIT" }, "node_modules/tinymce": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-7.7.1.tgz", - "integrity": "sha512-rMetqSgZtYbj4YPOX+gYgmlhy/sIjVlI/qlrSOul/Mpn9e0aIIG/fR0qvQSVYvxFv6OzRTge++NQyTbzLJK1NA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-7.7.2.tgz", + "integrity": "sha512-GX7Jd0ac9ph3QM2yei4uOoxytKX096CyG6VkkgQNikY39T6cDldoNgaqzHHlcm62WtdBMCd7Ch+PYaRnQo+NLA==", "license": "GPL-2.0-or-later" }, "node_modules/to-regex-range": { @@ -5666,9 +5850,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz", - "integrity": "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", "dev": true, "license": "MIT", "engines": { diff --git a/client/package.json b/client/package.json index 334339c0..d48b721d 100644 --- a/client/package.json +++ b/client/package.json @@ -1,17 +1,13 @@ { "name": "codechat-editor-client", - "version": "0.1.14", + "version": "0.1.15", "description": "The CodeChat Editor Client, part of a web-based literate programming editor (the CodeChat Editor).", "homepage": "https://github.com/bjones1/CodeChat_Editor", "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "build": "run-script-os", - "build:win32": "node_modules\\.bin\\esbuild src/HashReader.mts --outdir=. --platform=node --format=esm && node_modules\\.bin\\esbuild src/CodeChatEditorFramework.mts src/CodeChatEditor.mts src/CodeChatEditor-test.mts src/css/CodeChatEditorProject.css src/css/CodeChatEditor.css --bundle --outdir=./static/bundled --sourcemap --format=esm --splitting --metafile=meta.json --entry-names=[dir]/[name]-[hash] && node HashReader.js && tsc -noEmit", - "build:default": "node_modules/.bin/esbuild src/HashReader.mts --outdir=. --platform=node --format=esm && node_modules/.bin/esbuild src/CodeChatEditorFramework.mts src/CodeChatEditor.mts src/CodeChatEditor-test.mts src/css/CodeChatEditorProject.css src/css/CodeChatEditor.css --bundle --outdir=./static/bundled --sourcemap --format=esm --splitting --metafile=meta.json --entry-names=[dir]/[name]-[hash] && node HashReader.js && tsc -noEmit", - "dist": "run-script-os", - "dist:win32": "node_modules\\.bin\\esbuild src/HashReader.mts --outdir=. --platform=node --format=esm && node_modules\\.bin\\esbuild src/CodeChatEditorFramework.mts src/CodeChatEditor.mts src/CodeChatEditor-test.mts src/css/CodeChatEditorProject.css src/css/CodeChatEditor.css --bundle --outdir=./static/bundled --minify --format=esm --splitting --metafile=meta.json --entry-names=[dir]/[name]-[hash] && node HashReader.js && tsc -noEmit", - "dist:default": "node_modules/.bin/esbuild src/HashReader.mts --outdir=. --platform=node --format=esm && node_modules/.bin/esbuild src/CodeChatEditorFramework.mts src/CodeChatEditor.mts src/CodeChatEditor-test.mts src/css/CodeChatEditorProject.css src/css/CodeChatEditor.css --bundle --outdir=./static/bundled --minify --format=esm --splitting --metafile=meta.json --entry-names=[dir]/[name]-[hash] && node HashReader.js && tsc -noEmit" + "build": "cargo run --manifest-path=../builder/Cargo.toml client-build", + "dist": "cargo run --manifest-path=../builder/Cargo.toml client-build --dist" }, "keywords": [], "author": "Bryan A. Jones", @@ -29,7 +25,6 @@ "eslint-config-prettier": "^10", "eslint-plugin-import": "^2", "eslint-plugin-prettier": "^5", - "run-script-os": "^1", "typescript": "^5" }, "dependencies": { @@ -52,6 +47,7 @@ "mathjax-modern-font": "4.0.0-beta.7", "mermaid": "^11", "npm-check-updates": "^17.1.15", + "pdfjs-dist": "^5", "tinymce": "^7" }, "repository": { diff --git a/client/src/CodeChatEditor.mts b/client/src/CodeChatEditor.mts index 9c96b911..82b6c8b8 100644 --- a/client/src/CodeChatEditor.mts +++ b/client/src/CodeChatEditor.mts @@ -422,7 +422,7 @@ const on_navigate = (navigateEvent: NavigateEvent) => { return; } - // If the IDE initiated this navigation via a`CurrentFile` message, then + // If the IDE initiated this navigation via a `CurrentFile` message, then // allow it. if (window.CodeChatEditor.allow_navigation) { // We don't need to reset this flag, since this window will be reloaded. diff --git a/client/src/CodeChatEditorFramework.mts b/client/src/CodeChatEditorFramework.mts index 6c9ab7f0..06187bfe 100644 --- a/client/src/CodeChatEditorFramework.mts +++ b/client/src/CodeChatEditorFramework.mts @@ -50,7 +50,7 @@ type ResultType = { Ok: "Void" } | { Err: string }; interface EditorMessageContents { Update?: UpdateMessageContents; - CurrentFile?: string; + CurrentFile?: [string, boolean?]; RequestClose?: null; OpenUrl?: string, Result?: ResultType; @@ -160,7 +160,8 @@ class WebSocketComm { break; case "CurrentFile": - const current_file = value as string; + // Note that we can ignore `value[1]` (if the file is text or binary); the server only sends text files here. + const current_file = value[0] as string; // If the page is still loading, then don't save. Otherwise, // save the editor contents if necessary. let cce = get_client(); @@ -274,7 +275,7 @@ class WebSocketComm { current_file = (url: URL) => { // If this points to the Server, then tell the IDE to load a new file. if (url.host === window.location.host) { - this.send_message({ CurrentFile: url.toString() }, () => { + this.send_message({ CurrentFile: [url.toString(), undefined] }, () => { this.set_root_iframe_src(url.toString()); }); } else { @@ -308,7 +309,8 @@ const get_client = () => root_iframe?.contentWindow?.CodeChatEditor; const set_content = (contents: CodeChatForWeb) => { let client = get_client(); if (client === undefined) { - let cw = root_iframe!.contentWindow!; + // See if this is the [simple viewer](#Client-simple-viewer). Otherwise, it's just the bare document to replace. + const cw = (root_iframe!.contentDocument?.getElementById("CodeChat-contents") as HTMLIFrameElement | undefined)?.contentWindow ?? root_iframe!.contentWindow!; cw.document.open(); cw.document.write(contents.source.doc); cw.document.close(); diff --git a/client/src/css/CodeChatEditorProject.css b/client/src/css/CodeChatEditorProject.css index e964b521..6121d84d 100644 --- a/client/src/css/CodeChatEditorProject.css +++ b/client/src/css/CodeChatEditorProject.css @@ -17,12 +17,31 @@ [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/). */ :root { --sidebar-width: 15rem; + --body-padding: 0.2rem; +} + +/* See [box sizing](https://css-tricks.com/box-sizing/) for the following + technique to use `border-box` sizing. */ +html { + box-sizing: border-box; +} + +*, +*:before, +*:after { + box-sizing: inherit; } body { + /* For box model simplicity, switch the padding and margin. */ + padding: var(--body-padding); + margin: 0px; overflow: hidden; } diff --git a/client/src/pdf.js/README.md b/client/src/pdf.js/README.md new file mode 100644 index 00000000..520e1b38 --- /dev/null +++ b/client/src/pdf.js/README.md @@ -0,0 +1,11 @@ +## Overview + +Example to demonstrate PDF.js library usage with a viewer optimized for mobile usage. + +## Getting started + +Build PDF.js using `gulp dist-install` and run `gulp server` to start a web server. +You can then work with the mobile viewer at +http://localhost:8888/examples/mobile-viewer/viewer.html. + +Refer to `viewer.js` for the source code of the mobile viewer. diff --git a/client/src/pdf.js/images/div_line_left.png b/client/src/pdf.js/images/div_line_left.png new file mode 100644 index 00000000..e12eb153 Binary files /dev/null and b/client/src/pdf.js/images/div_line_left.png differ diff --git a/client/src/pdf.js/images/div_line_left@1.5x.png b/client/src/pdf.js/images/div_line_left@1.5x.png new file mode 100644 index 00000000..6459398b Binary files /dev/null and b/client/src/pdf.js/images/div_line_left@1.5x.png differ diff --git a/client/src/pdf.js/images/div_line_left@2x.png b/client/src/pdf.js/images/div_line_left@2x.png new file mode 100644 index 00000000..f53f1d64 Binary files /dev/null and b/client/src/pdf.js/images/div_line_left@2x.png differ diff --git a/client/src/pdf.js/images/div_line_right.png b/client/src/pdf.js/images/div_line_right.png new file mode 100644 index 00000000..e9bf322f Binary files /dev/null and b/client/src/pdf.js/images/div_line_right.png differ diff --git a/client/src/pdf.js/images/div_line_right@1.5x.png b/client/src/pdf.js/images/div_line_right@1.5x.png new file mode 100644 index 00000000..05d6343f Binary files /dev/null and b/client/src/pdf.js/images/div_line_right@1.5x.png differ diff --git a/client/src/pdf.js/images/div_line_right@2x.png b/client/src/pdf.js/images/div_line_right@2x.png new file mode 100644 index 00000000..f7a5ec7b Binary files /dev/null and b/client/src/pdf.js/images/div_line_right@2x.png differ diff --git a/client/src/pdf.js/images/document_bg.png b/client/src/pdf.js/images/document_bg.png new file mode 100644 index 00000000..21c29b2e Binary files /dev/null and b/client/src/pdf.js/images/document_bg.png differ diff --git a/client/src/pdf.js/images/icon_next_page.png b/client/src/pdf.js/images/icon_next_page.png new file mode 100644 index 00000000..76e42965 Binary files /dev/null and b/client/src/pdf.js/images/icon_next_page.png differ diff --git a/client/src/pdf.js/images/icon_next_page@1.5x.png b/client/src/pdf.js/images/icon_next_page@1.5x.png new file mode 100644 index 00000000..7317e4a9 Binary files /dev/null and b/client/src/pdf.js/images/icon_next_page@1.5x.png differ diff --git a/client/src/pdf.js/images/icon_previous_page.png b/client/src/pdf.js/images/icon_previous_page.png new file mode 100644 index 00000000..96f0d8e5 Binary files /dev/null and b/client/src/pdf.js/images/icon_previous_page.png differ diff --git a/client/src/pdf.js/images/icon_previous_page@1.5x.png b/client/src/pdf.js/images/icon_previous_page@1.5x.png new file mode 100644 index 00000000..0ed066c3 Binary files /dev/null and b/client/src/pdf.js/images/icon_previous_page@1.5x.png differ diff --git a/client/src/pdf.js/images/icon_zoom_in.png b/client/src/pdf.js/images/icon_zoom_in.png new file mode 100644 index 00000000..57b3a338 Binary files /dev/null and b/client/src/pdf.js/images/icon_zoom_in.png differ diff --git a/client/src/pdf.js/images/icon_zoom_in@1.5x.png b/client/src/pdf.js/images/icon_zoom_in@1.5x.png new file mode 100644 index 00000000..be828647 Binary files /dev/null and b/client/src/pdf.js/images/icon_zoom_in@1.5x.png differ diff --git a/client/src/pdf.js/images/icon_zoom_out.png b/client/src/pdf.js/images/icon_zoom_out.png new file mode 100644 index 00000000..8ddf34bc Binary files /dev/null and b/client/src/pdf.js/images/icon_zoom_out.png differ diff --git a/client/src/pdf.js/images/icon_zoom_out@1.5x.png b/client/src/pdf.js/images/icon_zoom_out@1.5x.png new file mode 100644 index 00000000..8e98b036 Binary files /dev/null and b/client/src/pdf.js/images/icon_zoom_out@1.5x.png differ diff --git a/client/src/pdf.js/images/spinner.png b/client/src/pdf.js/images/spinner.png new file mode 100644 index 00000000..32ef1077 Binary files /dev/null and b/client/src/pdf.js/images/spinner.png differ diff --git a/client/src/pdf.js/images/toolbar_background.png b/client/src/pdf.js/images/toolbar_background.png new file mode 100644 index 00000000..bc6dfb6c Binary files /dev/null and b/client/src/pdf.js/images/toolbar_background.png differ diff --git a/client/src/pdf.js/viewer.css b/client/src/pdf.js/viewer.css new file mode 100644 index 00000000..6c5a67a0 --- /dev/null +++ b/client/src/pdf.js/viewer.css @@ -0,0 +1,242 @@ +/* Copyright 2016 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +* { + padding: 0; + margin: 0; +} + +html { + height: 100%; + width: 100%; + overflow: hidden; + font-size: 10px; +} + +header { + background-color: rgb(244 244 244 / 1); +} + +header h1 { + border-bottom: 1px solid rgb(216 216 216 / 1); + color: rgb(133 133 133 / 1); + font-size: 23px; + font-style: italic; + font-weight: normal; + overflow: hidden; + padding: 10px; + text-align: center; + text-overflow: ellipsis; + white-space: nowrap; +} + +body { + background: url(./images/document_bg.png); + color: rgb(255 255 255 / 1); + font-family: sans-serif; + font-size: 10px; + height: 100%; + width: 100%; + overflow: hidden; + padding-bottom: 5rem; +} + +section { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + overflow: hidden; + font-size: 2rem; +} + +footer { + background-image: url(images/toolbar_background.png); + height: 4rem; + position: absolute; + bottom: 0; + left: 0; + right: 0; + z-index: 1; + box-shadow: 0 -0.2rem 0.5rem rgb(50 50 50 / 0.75); +} + +.toolbarButton { + display: block; + padding: 0; + margin: 0; + border-width: 0; + background-position: center center; + background-repeat: no-repeat; + background-color: rgb(0 0 0 / 0); +} + +.toolbarButton.pageUp { + position: absolute; + width: 18%; + height: 100%; + left: 0; + background-image: url(images/icon_previous_page.png); + background-size: 2rem; +} + +.toolbarButton.pageDown { + position: absolute; + width: 18%; + height: 100%; + left: 18%; + background-image: url(images/icon_next_page.png); + background-size: 2rem; +} + +#pageNumber { + -moz-appearance: textfield; /* hides the spinner in moz */ + position: absolute; + width: 28%; + height: 100%; + left: 36%; + text-align: center; + border: 0; + background-color: rgb(0 0 0 / 0); + font-size: 1.2rem; + color: rgb(255 255 255 / 1); + background-image: + url(images/div_line_left.png), url(images/div_line_right.png); + background-repeat: no-repeat; + background-position: left, right; + background-size: 0.2rem, 0.2rem; +} + +.toolbarButton.zoomOut { + position: absolute; + width: 18%; + height: 100%; + left: 64%; + background-image: url(images/icon_zoom_out.png); + background-size: 2.4rem; +} + +.toolbarButton.zoomIn { + position: absolute; + width: 18%; + height: 100%; + left: 82%; + background-image: url(images/icon_zoom_in.png); + background-size: 2.4rem; +} + +.toolbarButton[disabled] { + opacity: 0.3; +} + +.hidden { + display: none; +} +[hidden] { + display: none !important; +} + +#viewerContainer { + position: absolute; + overflow: auto; + width: 100%; + inset: 5rem 0 4rem; +} + +canvas { + margin: auto; + display: block; +} + +.pdfViewer .page .loadingIcon { + width: 2.9rem; + height: 2.9rem; + background: url("images/spinner.png") no-repeat left top / 38rem; + border: medium none; + animation: 1s steps(10, end) 0s normal none infinite moveDefault; + display: block; + position: absolute; + top: calc((100% - 2.9rem) / 2); + left: calc((100% - 2.9rem) / 2); +} + +@keyframes moveDefault { + from { + background-position: 0 top; + } + + to { + background-position: -39rem top; + } +} + +#loadingBar { + /* Define this variable here, and not in :root, to avoid reflowing the + entire viewer when updating progress (see issue 15958). */ + --progressBar-percent: 0%; + + position: relative; + height: 0.6rem; + background-color: rgb(51 51 51 / 1); + border-bottom: 1px solid rgb(51 51 51 / 1); +} + +#loadingBar .progress { + position: absolute; + left: 0; + width: 100%; + transform: scaleX(var(--progressBar-percent)); + transform-origin: 0 0; + height: 100%; + background-color: rgb(221 221 221 / 1); + overflow: hidden; + transition: transform 200ms; +} + +@keyframes progressIndeterminate { + 0% { + transform: translateX(0%); + } + 50% { + transform: translateX(100%); + } + 100% { + transform: translateX(100%); + } +} + +#loadingBar.indeterminate .progress { + transform: none; + background-color: rgb(153 153 153 / 1); + transition: none; +} + +#loadingBar.indeterminate .progress .glimmer { + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 5rem; + background-image: linear-gradient( + to right, + rgb(153 153 153 / 1) 0%, + rgb(255 255 255 / 1) 50%, + rgb(153 153 153 / 1) 100% + ); + background-size: 100% 100%; + background-repeat: no-repeat; + animation: progressIndeterminate 2s linear infinite; +} diff --git a/client/src/pdf.js/viewer.html b/client/src/pdf.js/viewer.html new file mode 100644 index 00000000..6bd8b540 --- /dev/null +++ b/client/src/pdf.js/viewer.html @@ -0,0 +1,57 @@ + + + + + + + + PDF.js viewer + + + + + + + + + +

+

+
+ +
+
+
+ +
+
+
+
+ +
+ + + + + + + +
+ + + + diff --git a/client/src/pdf.js/viewer.mjs b/client/src/pdf.js/viewer.mjs new file mode 100644 index 00000000..5c24fb72 --- /dev/null +++ b/client/src/pdf.js/viewer.mjs @@ -0,0 +1,391 @@ +// `viewer.mjs` - PDF viewer for VSCode +// ==================================== +// +// The PDF viewer for use with VSCode. See the
index. This is lightly modified from the [PDF.js mobile viewer example](https://github.com/mozilla/pdf.js/tree/master/examples/mobile-viewer) to fixes paths and accept the path to view from the URL's query parameter. + +/* Copyright 2016 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as pdfjsLib from "pdfjs-dist"; +import * as pdfjsViewer from "pdfjs-dist/web/pdf_viewer.mjs"; + +import "./viewer.css"; +import "pdfjs-dist/web/pdf_viewer.css"; + +if (!pdfjsLib.getDocument || !pdfjsViewer.PDFViewer) { + // eslint-disable-next-line no-alert + alert("Please build the pdfjs-dist library using\n `gulp dist-install`"); +} + +const MAX_CANVAS_PIXELS = 0; // CSS-only zooming. +const TEXT_LAYER_MODE = 0; // DISABLE +const MAX_IMAGE_SIZE = 1024 * 1024; +// Copied in the builder's `run_client_build` function. +const CMAP_URL = "/static/bundled/node_modules/pdfjs-dist/cmaps/"; +const CMAP_PACKED = true; + +pdfjsLib.GlobalWorkerOptions.workerSrc = + "/static/bundled/node_modules/pdfjs-dist/build/pdf.worker.js"; + +// The search param provides the location of the file to view. Add the `raw` +// query string to get the actual PDF contents, instead of a Client Simple Viewer. +const DEFAULT_URL = window.location.search.substring(1) + "?raw"; +const DEFAULT_SCALE_DELTA = 1.1; +const MIN_SCALE = 0.25; +const MAX_SCALE = 10.0; +const DEFAULT_SCALE_VALUE = "auto"; + +const PDFViewerApplication = { + pdfLoadingTask: null, + pdfDocument: null, + pdfViewer: null, + pdfHistory: null, + pdfLinkService: null, + eventBus: null, + + /** + * Opens PDF document specified by URL. + * @returns {Promise} - Returns the promise, which is resolved when document + * is opened. + */ + open(params) { + if (this.pdfLoadingTask) { + // We need to destroy already opened document + return this.close().then( + function () { + // ... and repeat the open() call. + return this.open(params); + }.bind(this), + ); + } + + const url = params.url; + const self = this; + this.setTitleUsingUrl(url); + + // Loading document. + const loadingTask = pdfjsLib.getDocument({ + url, + maxImageSize: MAX_IMAGE_SIZE, + cMapUrl: CMAP_URL, + cMapPacked: CMAP_PACKED, + }); + this.pdfLoadingTask = loadingTask; + + loadingTask.onProgress = function (progressData) { + self.progress(progressData.loaded / progressData.total); + }; + + return loadingTask.promise.then( + function (pdfDocument) { + // Document loaded, specifying document for the viewer. + self.pdfDocument = pdfDocument; + self.pdfViewer.setDocument(pdfDocument); + self.pdfLinkService.setDocument(pdfDocument); + self.pdfHistory.initialize({ + fingerprint: pdfDocument.fingerprints[0], + }); + + self.loadingBar.hide(); + self.setTitleUsingMetadata(pdfDocument); + }, + function (reason) { + let key = "pdfjs-loading-error"; + if (reason instanceof pdfjsLib.InvalidPDFException) { + key = "pdfjs-invalid-file-error"; + } else if (reason instanceof pdfjsLib.ResponseException) { + key = reason.missing + ? "pdfjs-missing-file-error" + : "pdfjs-unexpected-response-error"; + } + self.l10n.get(key).then((msg) => { + self.error(msg, { message: reason?.message }); + }); + self.loadingBar.hide(); + }, + ); + }, + + /** + * Closes opened PDF document. + * @returns {Promise} - Returns the promise, which is resolved when all + * destruction is completed. + */ + close() { + if (!this.pdfLoadingTask) { + return Promise.resolve(); + } + + const promise = this.pdfLoadingTask.destroy(); + this.pdfLoadingTask = null; + + if (this.pdfDocument) { + this.pdfDocument = null; + + this.pdfViewer.setDocument(null); + this.pdfLinkService.setDocument(null, null); + + if (this.pdfHistory) { + this.pdfHistory.reset(); + } + } + + return promise; + }, + + get loadingBar() { + const bar = document.getElementById("loadingBar"); + return pdfjsLib.shadow( + this, + "loadingBar", + new pdfjsViewer.ProgressBar(bar), + ); + }, + + setTitleUsingUrl: function pdfViewSetTitleUsingUrl(url) { + this.url = url; + let title = pdfjsLib.getFilenameFromUrl(url) || url; + try { + title = decodeURIComponent(title); + } catch { + // decodeURIComponent may throw URIError, + // fall back to using the unprocessed url in that case + } + this.setTitle(title); + }, + + setTitleUsingMetadata(pdfDocument) { + const self = this; + pdfDocument.getMetadata().then(function (data) { + const info = data.info, + metadata = data.metadata; + self.documentInfo = info; + self.metadata = metadata; + + // Provides some basic debug information + console.log( + "PDF " + + pdfDocument.fingerprints[0] + + " [" + + info.PDFFormatVersion + + " " + + (info.Producer || "-").trim() + + " / " + + (info.Creator || "-").trim() + + "]" + + " (PDF.js: " + + (pdfjsLib.version || "-") + + ")", + ); + + let pdfTitle; + if (metadata && metadata.has("dc:title")) { + const title = metadata.get("dc:title"); + // Ghostscript sometimes returns 'Untitled', so prevent setting the + // title to 'Untitled. + if (title !== "Untitled") { + pdfTitle = title; + } + } + + if (!pdfTitle && info && info.Title) { + pdfTitle = info.Title; + } + + if (pdfTitle) { + self.setTitle(pdfTitle + " - " + document.title); + } + }); + }, + + setTitle: function pdfViewSetTitle(title) { + document.title = title; + document.getElementById("title").textContent = title; + }, + + error: function pdfViewError(message, moreInfo) { + const moreInfoText = [ + `PDF.js v${pdfjsLib.version || "?"} (build: ${pdfjsLib.build || "?"})`, + ]; + if (moreInfo) { + moreInfoText.push(`Message: ${moreInfo.message}`); + + if (moreInfo.stack) { + moreInfoText.push(`Stack: ${moreInfo.stack}`); + } else { + if (moreInfo.filename) { + moreInfoText.push(`File: ${moreInfo.filename}`); + } + if (moreInfo.lineNumber) { + moreInfoText.push(`Line: ${moreInfo.lineNumber}`); + } + } + } + + console.error(`${message}\n\n${moreInfoText.join("\n")}`); + }, + + progress: function pdfViewProgress(level) { + const percent = Math.round(level * 100); + // Updating the bar if value increases. + if (percent > this.loadingBar.percent || isNaN(percent)) { + this.loadingBar.percent = percent; + } + }, + + get pagesCount() { + return this.pdfDocument.numPages; + }, + + get page() { + return this.pdfViewer.currentPageNumber; + }, + + set page(val) { + this.pdfViewer.currentPageNumber = val; + }, + + zoomIn: function pdfViewZoomIn(ticks) { + let newScale = this.pdfViewer.currentScale; + do { + newScale = (newScale * DEFAULT_SCALE_DELTA).toFixed(2); + newScale = Math.ceil(newScale * 10) / 10; + newScale = Math.min(MAX_SCALE, newScale); + } while (--ticks && newScale < MAX_SCALE); + this.pdfViewer.currentScaleValue = newScale; + }, + + zoomOut: function pdfViewZoomOut(ticks) { + let newScale = this.pdfViewer.currentScale; + do { + newScale = (newScale / DEFAULT_SCALE_DELTA).toFixed(2); + newScale = Math.floor(newScale * 10) / 10; + newScale = Math.max(MIN_SCALE, newScale); + } while (--ticks && newScale > MIN_SCALE); + this.pdfViewer.currentScaleValue = newScale; + }, + + initUI: function pdfViewInitUI() { + const eventBus = new pdfjsViewer.EventBus(); + this.eventBus = eventBus; + + const linkService = new pdfjsViewer.PDFLinkService({ + eventBus, + }); + this.pdfLinkService = linkService; + + this.l10n = new pdfjsViewer.GenericL10n(); + + const container = document.getElementById("viewerContainer"); + const pdfViewer = new pdfjsViewer.PDFViewer({ + container, + eventBus, + linkService, + l10n: this.l10n, + maxCanvasPixels: MAX_CANVAS_PIXELS, + textLayerMode: TEXT_LAYER_MODE, + }); + this.pdfViewer = pdfViewer; + linkService.setViewer(pdfViewer); + + this.pdfHistory = new pdfjsViewer.PDFHistory({ + eventBus, + linkService, + }); + linkService.setHistory(this.pdfHistory); + + document + .getElementById("previous") + .addEventListener("click", function () { + PDFViewerApplication.page--; + }); + + document.getElementById("next").addEventListener("click", function () { + PDFViewerApplication.page++; + }); + + document + .getElementById("zoomIn") + .addEventListener("click", function () { + PDFViewerApplication.zoomIn(); + }); + + document + .getElementById("zoomOut") + .addEventListener("click", function () { + PDFViewerApplication.zoomOut(); + }); + + document + .getElementById("pageNumber") + .addEventListener("click", function () { + this.select(); + }); + + document + .getElementById("pageNumber") + .addEventListener("change", function () { + PDFViewerApplication.page = this.value | 0; + + // Ensure that the page number input displays the correct value, + // even if the value entered by the user was invalid + // (e.g. a floating point number). + if (this.value !== PDFViewerApplication.page.toString()) { + this.value = PDFViewerApplication.page; + } + }); + + eventBus.on("pagesinit", function () { + // We can use pdfViewer now, e.g. let's change default scale. + pdfViewer.currentScaleValue = DEFAULT_SCALE_VALUE; + }); + + eventBus.on( + "pagechanging", + function (evt) { + const page = evt.pageNumber; + const numPages = PDFViewerApplication.pagesCount; + + document.getElementById("pageNumber").value = page; + document.getElementById("previous").disabled = page <= 1; + document.getElementById("next").disabled = page >= numPages; + }, + true, + ); + }, +}; + +window.PDFViewerApplication = PDFViewerApplication; + +document.addEventListener( + "DOMContentLoaded", + function () { + PDFViewerApplication.initUI(); + }, + true, +); + +// The offsetParent is not set until the PDF.js iframe or object is visible; +// waiting for first animation. +const animationStarted = new Promise(function (resolve) { + window.requestAnimationFrame(resolve); +}); + +// We need to delay opening until all HTML is loaded. +animationStarted.then(function () { + PDFViewerApplication.open({ + url: DEFAULT_URL, + }); +}); diff --git a/client/static/pdfjs-main.html b/client/static/pdfjs-main.html new file mode 100644 index 00000000..dff49ed5 --- /dev/null +++ b/client/static/pdfjs-main.html @@ -0,0 +1,54 @@ + + + + + + + + PDF.js viewer + + + + + + +
+

+
+ +
+
+
+ +
+
+
+
+ +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/docs/changelog.md b/docs/changelog.md index 73aa5a6a..2e49090e 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -21,6 +21,9 @@ Changelog * [Github master](https://github.com/bjones1/CodeChat_Editor): * No changes. +* v0.1.15, 2025-Mar-31: + * Correctly view binary files (images, PDFs, etc.) within a project. + * Include support for viewing PDF files in VSCode. * v0.1.14, 2025-Mar-13: * Correct translation of leading slash in Linux/OS X paths to/from a URL. This fixes rewrites of URL in Markdown to long relative paths. diff --git a/docs/design.md b/docs/design.md index 7187138d..f999604e 100644 --- a/docs/design.md +++ b/docs/design.md @@ -42,7 +42,9 @@ These form a set of high-level requirements to guide the project. * 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. + 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 below). * A page in a project build is a single-file page plus: * A TOC, along with previous/next/up navigation. The TOC is synchronized to the current page. @@ -60,17 +62,54 @@ These form a set of high-level requirements to guide the project. 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. + * 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 TOC. + * Show a list of all links. + * Since it's a plain Markdown file, this could include pretty much + anything: a list of index entires; a temporally-sorted list of + pages; an image with links based on a map/diagram; etc. + * Tracking support: auto-scrolls the TOC to the first instance of a + link to 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 context or end of file, whichever comes first. + * 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)). + [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. + * 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.) @@ -116,8 +155,9 @@ 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 -support](index.md#programming-language-support) +### \[Programming language + +support\](index.md#programming-language-support) Initial targets come from the Stack Overflow Developer Survey 2022's section on [programming, scripting, and markup diff --git a/docs/implementation.md b/docs/implementation.md index 10639194..e87718e5 100644 --- a/docs/implementation.md +++ b/docs/implementation.md @@ -19,6 +19,34 @@ CodeChat Editor. If not, see Implementation ============== +### System architecture + + + Architecture ------------------------------------------ @@ -149,7 +177,7 @@ editor-overlay filesystem. the change is from no file to the current file), or a link is followed in the Client's iframe:\ ![Load - diagram](https://www.plantuml.com/plantuml/svg/hLBRJiCm37tlL_XnVO0Fw0EQD40W9fXs-O2mkX0fJKBY8ktlYTVGahQQEeayjBdOvnmVU-b9E6fgbTdmbqTfXIPuldz8pZjqt-YIgvMIg2aJwXmDoeZIGoKLPd2-kBcB8GMi6kV2vZ4yBdRafFueO2Fe4qm5jP3wrfxoa6LiWAfY5fJcsDIyaHvAwUYK0Q_u7E2PfO23BGNri4UZAJpx59hNKDGMlJNQu-BjXIDGbzaGF8r1vJ46fDMcIPFL7hRhHD5bDQob0utU5_2qts_0uLU3dXP3m3Qeqx0E-X81bkqcqoT4_WZUyuyokSX9MtFk_U-9kuIb9F7EbaJOli1EVMJvWnSZyiciUTqTUpLehZB6PX3MF5TzOwrn52ZRwgLEPscMsMESDfbDsuq86AcVqqkTISoRffZb_sK87lQHJ6teIgclHcF-3wAWSgO-x_p94zPHf2wpzijokr5acTVFOZfK3BeCdwPMFm00) + diagram](https://www.plantuml.com/plantuml/svg/hLBDpjCm4BpdAVRO7E01Se1F-W21gA1gMkucte2HOnjxGzMtnpzHGfo8X8eUqiJUdPcTdIT7p5BVoSBuVz48mnJ1XpTlPzyrsbzePqVFKg2YWibO3L8pxg0L4Wk81ozU3IKLFFVM-fTt_l9GanNgMmKdHjz1jz0neLwQU-cxjF5GxT05N3Tz5rw40ouitGk8ltJjuGDB1LV36KsmZLRahrq63R0GTKPdj79u-FmnLA3YHGQUrQ1qE1JCfysQrgQzde-Peh-f2LecqEHz1UylbnDO_DcZeqCEc8f63KUlRoR01Bj9Jms1VmAV-FFEEEIghMNS_V0LjeHS4FiQBKcmqu2Z-e7bJxnKKQvsxTlkqgjikL9h4rEmprNN6wCjUSeqlL3pBEqoUucJceDfzPB08mqvtThCpBnLYct_Do5Ys7EPIjC_Ilsa5PR_GHIqLdVnpTqTOJU8LBn8po1tZAANEMOHcEBnW86n-WSsj3ETUSmsA8Hxc25LG2qwuy6-2BoXBOkjh488wslBRZEHZyxcBNpoZxwJlm40) * If the current file's contents in the IDE are edited:\ ![Edit diagram](https://www.plantuml.com/plantuml/svg/XT1DQiCm40NWlKunItlH2tXH36vBInCI_7C09NeE0cNaIEFa-ed1OCVaPp_l6zxBe-WW_T6flwzl-lYa2k6Ca57J6Ir8AWcM3nanBhJtB629gT9EQAqjKsiTo4Q2iQ9t3ef6OA0APy7oXeABkBVOosklw4C0ouzr4zgKA_BjpANnVDxfjwwt573g4ILP9Xw-6XEnynoVDc2Zfb-t6JCgbudDVwfoi1c6lW80) @@ -533,34 +561,6 @@ Babel-style imports. TODO: GUIs using TinyMCE. See the [how-to guide](https://www.tiny.cloud/docs/tinymce/6/dialog-components/#panel-components). -### System architecture - - - Code style ---------- @@ -571,10 +571,27 @@ Therefore, we use only arrow functions for this codebase. Other than that, follow the [MDN style guide](https://developer.mozilla.org/en-US/docs/MDN/Writing_guidelines/Writing_style_guide/Code_style_guide/JavaScript). +Client modes +------------ + +The CodeChat Editor client supports four modes: + +* Edit: + * Document only: just TinyMCE to edit a pure Markdown file. + * Usual: the usual CodeMirror + TinyMCE editor +* View: + * For a ReadTheDocs / browsing experience: clicking on links navigates to + them immediately, instead of bringing up a context menu. Still use + CodeMirror for syntax highlighting, collapsing, etc. +* Simple viewer: + * For text or binary files that aren't supported by the editor. In project + mode, this displays the TOC on the left and the file contents in the + main area; otherwise, it's only the main area. See: \. + Misc ---- -Eventaully, provide a read-only mode with possible auth (restrict who can view) +Eventually, provide a read-only mode with possible auth (restrict who can view) using JWTs; see [one approach](https://auth0.com/blog/build-an-api-in-rust-with-jwt-authentication-using-actix-web/). diff --git a/extensions/VSCode/package-lock.json b/extensions/VSCode/package-lock.json index dc639fc7..cc6c7e3f 100644 --- a/extensions/VSCode/package-lock.json +++ b/extensions/VSCode/package-lock.json @@ -1,12 +1,12 @@ { "name": "codechat-editor-client", - "version": "0.1.14", + "version": "0.1.15", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "codechat-editor-client", - "version": "0.1.14", + "version": "0.1.15", "license": "GPL-3.0-only", "dependencies": { "escape-html": "^1", @@ -167,22 +167,22 @@ } }, "node_modules/@azure/msal-browser": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-4.7.0.tgz", - "integrity": "sha512-H4AIPhIQVe1qW4+BJaitqod6UGQiXE3juj7q2ZBsOPjuZicQaqcbnBp2gCroF/icS0+TJ9rGuyCBJbjlAqVOGA==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-4.9.1.tgz", + "integrity": "sha512-GTKj/2xvgD918xULWRwoJ3kiCCZNzeopxa/nDfMC4o6KzrnuWbT3K1AtIFUxok9yC6VrUOgIZXMygky06xDA1g==", "dev": true, "license": "MIT", "dependencies": { - "@azure/msal-common": "15.2.1" + "@azure/msal-common": "15.4.0" }, "engines": { "node": ">=0.8.0" } }, "node_modules/@azure/msal-common": { - "version": "15.2.1", - "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-15.2.1.tgz", - "integrity": "sha512-eZHtYE5OHDN0o2NahCENkczQ6ffGc0MoUSAI3hpwGpZBHJXaEQMMZPWtIx86da2L9w7uT+Tr/xgJbGwIkvTZTQ==", + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-15.4.0.tgz", + "integrity": "sha512-reeIUDXt6Xc+FpCBDEbUFQWvJ6SjE0JwsGYIfa3ZCR6Tpzjc9J1v+/InQgfCeJzfTRd7PDJVxI6TSzOmOd7+Ag==", "dev": true, "license": "MIT", "engines": { @@ -190,13 +190,13 @@ } }, "node_modules/@azure/msal-node": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-3.3.0.tgz", - "integrity": "sha512-ulsT3EHF1RQ29X55cxBLgKsIKWni9JdbUqG7sipGVP4uhWcBpmm/vhKOMH340+27Acm9+kHGnN/5XmQ5LrIDgA==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-3.4.1.tgz", + "integrity": "sha512-VlW6ygnKBIqUKIHnA/ubQ+F3rZ8aW3K6VA1bpZ90Ln0vlE4XaA6yGB/FibPJxet7gWinAG1oSpQqPN/PL9AqIw==", "dev": true, "license": "MIT", "dependencies": { - "@azure/msal-common": "15.2.1", + "@azure/msal-common": "15.4.0", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" }, @@ -273,9 +273,9 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.1.0.tgz", - "integrity": "sha512-kLrdPDJE1ckPo94kmPPf9Hfd0DU0Jw6oKYrhe+pwSC0iTUInmTa+w6fw8sGgcfkFJGNdWOUeOaDM4quW4a7OkA==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.0.tgz", + "integrity": "sha512-yJLLmLexii32mGrhW29qvU3QBVTu0GUmEf/J4XsBtVhp4JkIUFN/BjWqTF63yRvGApIDpZm5fa97LtYtINmfeQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -296,9 +296,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.0.tgz", - "integrity": "sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, "license": "MIT", "dependencies": { @@ -344,9 +344,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.22.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.22.0.tgz", - "integrity": "sha512-vLFajx9o8d1/oL2ZkpMYbkLv8nDB6yaIwFNt7nI4+I80U/z03SxmfOMsLbvWr3p7C+Wnoh//aOu2pQW8cS0HCQ==", + "version": "9.23.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.23.0.tgz", + "integrity": "sha512-35MJ8vCPU0ZMxo7zfev2pypqTwWTofFZO6m4KAtdoFhRpLJUpHTZZ+KB3C7Hb1d7bULYwO4lJXGCi5Se+8OMbw==", "dev": true, "license": "MIT", "engines": { @@ -514,9 +514,9 @@ "license": "MIT" }, "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", "dev": true, "license": "MIT" }, @@ -535,9 +535,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.13.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz", - "integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==", + "version": "22.13.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.14.tgz", + "integrity": "sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w==", "dev": true, "license": "MIT", "dependencies": { @@ -562,17 +562,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.1.tgz", - "integrity": "sha512-2X3mwqsj9Bd3Ciz508ZUtoQQYpOhU/kWoUqIf49H8Z0+Vbh6UF/y0OEYp0Q0axOGzaBGs7QxRwq0knSQ8khQNA==", + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.28.0.tgz", + "integrity": "sha512-lvFK3TCGAHsItNdWZ/1FkvpzCxTHUVuFrdnOGLMa0GGCFIbCgQWVk3CzCGdA7kM3qGVc+dfW9tr0Z/sHnGDFyg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.26.1", - "@typescript-eslint/type-utils": "8.26.1", - "@typescript-eslint/utils": "8.26.1", - "@typescript-eslint/visitor-keys": "8.26.1", + "@typescript-eslint/scope-manager": "8.28.0", + "@typescript-eslint/type-utils": "8.28.0", + "@typescript-eslint/utils": "8.28.0", + "@typescript-eslint/visitor-keys": "8.28.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -592,16 +592,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.26.1.tgz", - "integrity": "sha512-w6HZUV4NWxqd8BdeFf81t07d7/YV9s7TCWrQQbG5uhuvGUAW+fq1usZ1Hmz9UPNLniFnD8GLSsDpjP0hm1S4lQ==", + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.28.0.tgz", + "integrity": "sha512-LPcw1yHD3ToaDEoljFEfQ9j2xShY367h7FZ1sq5NJT9I3yj4LHer1Xd1yRSOdYy9BpsrxU7R+eoDokChYM53lQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.26.1", - "@typescript-eslint/types": "8.26.1", - "@typescript-eslint/typescript-estree": "8.26.1", - "@typescript-eslint/visitor-keys": "8.26.1", + "@typescript-eslint/scope-manager": "8.28.0", + "@typescript-eslint/types": "8.28.0", + "@typescript-eslint/typescript-estree": "8.28.0", + "@typescript-eslint/visitor-keys": "8.28.0", "debug": "^4.3.4" }, "engines": { @@ -617,14 +617,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.26.1.tgz", - "integrity": "sha512-6EIvbE5cNER8sqBu6V7+KeMZIC1664d2Yjt+B9EWUXrsyWpxx4lEZrmvxgSKRC6gX+efDL/UY9OpPZ267io3mg==", + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.28.0.tgz", + "integrity": "sha512-u2oITX3BJwzWCapoZ/pXw6BCOl8rJP4Ij/3wPoGvY8XwvXflOzd1kLrDUUUAIEdJSFh+ASwdTHqtan9xSg8buw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.26.1", - "@typescript-eslint/visitor-keys": "8.26.1" + "@typescript-eslint/types": "8.28.0", + "@typescript-eslint/visitor-keys": "8.28.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -635,14 +635,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.26.1.tgz", - "integrity": "sha512-Kcj/TagJLwoY/5w9JGEFV0dclQdyqw9+VMndxOJKtoFSjfZhLXhYjzsQEeyza03rwHx2vFEGvrJWJBXKleRvZg==", + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.28.0.tgz", + "integrity": "sha512-oRoXu2v0Rsy/VoOGhtWrOKDiIehvI+YNrDk5Oqj40Mwm0Yt01FC/Q7nFqg088d3yAsR1ZcZFVfPCTTFCe/KPwg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.26.1", - "@typescript-eslint/utils": "8.26.1", + "@typescript-eslint/typescript-estree": "8.28.0", + "@typescript-eslint/utils": "8.28.0", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, @@ -659,9 +659,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.26.1.tgz", - "integrity": "sha512-n4THUQW27VmQMx+3P+B0Yptl7ydfceUj4ON/AQILAASwgYdZ/2dhfymRMh5egRUrvK5lSmaOm77Ry+lmXPOgBQ==", + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.28.0.tgz", + "integrity": "sha512-bn4WS1bkKEjx7HqiwG2JNB3YJdC1q6Ue7GyGlwPHyt0TnVq6TtD/hiOdTZt71sq0s7UzqBFXD8t8o2e63tXgwA==", "dev": true, "license": "MIT", "engines": { @@ -673,14 +673,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.1.tgz", - "integrity": "sha512-yUwPpUHDgdrv1QJ7YQal3cMVBGWfnuCdKbXw1yyjArax3353rEJP1ZA+4F8nOlQ3RfS2hUN/wze3nlY+ZOhvoA==", + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.28.0.tgz", + "integrity": "sha512-H74nHEeBGeklctAVUvmDkxB1mk+PAZ9FiOMPFncdqeRBXxk1lWSYraHw8V12b7aa6Sg9HOBNbGdSHobBPuQSuA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.26.1", - "@typescript-eslint/visitor-keys": "8.26.1", + "@typescript-eslint/types": "8.28.0", + "@typescript-eslint/visitor-keys": "8.28.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -700,16 +700,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.26.1.tgz", - "integrity": "sha512-V4Urxa/XtSUroUrnI7q6yUTD3hDtfJ2jzVfeT3VK0ciizfK2q/zGC0iDh1lFMUZR8cImRrep6/q0xd/1ZGPQpg==", + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.28.0.tgz", + "integrity": "sha512-OELa9hbTYciYITqgurT1u/SzpQVtDLmQMFzy/N8pQE+tefOyCWT79jHsav294aTqV1q1u+VzqDGbuujvRYaeSQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.26.1", - "@typescript-eslint/types": "8.26.1", - "@typescript-eslint/typescript-estree": "8.26.1" + "@typescript-eslint/scope-manager": "8.28.0", + "@typescript-eslint/types": "8.28.0", + "@typescript-eslint/typescript-estree": "8.28.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -724,13 +724,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.1.tgz", - "integrity": "sha512-AjOC3zfnxd6S4Eiy3jwktJPclqhFHNyd8L6Gycf9WUPoKZpgM5PjkxY1X7uSy61xVpiJDhhk7XT2NVsN3ALTWg==", + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.28.0.tgz", + "integrity": "sha512-hbn8SZ8w4u2pRwgQ1GlUrPKE+t2XvcCW5tTRF7j6SMYIuYG37XuzIW44JCZPa36evi0Oy2SnM664BlIaAuQcvg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.26.1", + "@typescript-eslint/types": "8.28.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -755,9 +755,9 @@ } }, "node_modules/@vscode/vsce": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.2.2.tgz", - "integrity": "sha512-4TqdUq/yKlQTHcQMk/DamR632bq/+IJDomSbexOMee/UAYWqYm0XHWA6scGslsCpzY+sCWEhhl0nqdOB0XW1kw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.3.1.tgz", + "integrity": "sha512-aZ+0UilLsSidWcY6qjQRmsNgJXfFvE3ihj0u9oNLMejFGBBYORTRua0KHx3kx+RW5pxvza2OepzTpct2OdhTUA==", "dev": true, "license": "MIT", "dependencies": { @@ -1087,18 +1087,19 @@ } }, "node_modules/array.prototype.findlastindex": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", - "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", + "es-abstract": "^1.23.9", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -2111,19 +2112,19 @@ } }, "node_modules/eslint": { - "version": "9.22.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.22.0.tgz", - "integrity": "sha512-9V/QURhsRN40xuHXWjV64yvrzMjcz7ZyNoF2jJFmy9j/SLk0u1OLSZgXi28MrXjymnjEGSR80WCdab3RGMDveQ==", + "version": "9.23.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.23.0.tgz", + "integrity": "sha512-jV7AbNoFPAY1EkFYpLq5bslU9NLNO8xnEeQXwErNibVryjk67wHVmddTBilc5srIttJDBrB0eMHKZBFbSIABCw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.2", - "@eslint/config-helpers": "^0.1.0", + "@eslint/config-helpers": "^0.2.0", "@eslint/core": "^0.12.0", - "@eslint/eslintrc": "^3.3.0", - "@eslint/js": "9.22.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.23.0", "@eslint/plugin-kit": "^0.2.7", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -4690,9 +4691,9 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", - "integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", + "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", "dev": true, "license": "ISC", "engines": { @@ -5632,9 +5633,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz", - "integrity": "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", "dev": true, "license": "MIT", "engines": { diff --git a/extensions/VSCode/package.json b/extensions/VSCode/package.json index 3425ccef..e6fb53b7 100644 --- a/extensions/VSCode/package.json +++ b/extensions/VSCode/package.json @@ -1,6 +1,6 @@ { "name": "codechat-editor-client", - "version": "0.1.14", + "version": "0.1.15", "publisher": "CodeChat", "engines": { "vscode": "^1.61.0" diff --git a/extensions/VSCode/src/extension.ts b/extensions/VSCode/src/extension.ts index 899ddf2f..2e92a03f 100644 --- a/extensions/VSCode/src/extension.ts +++ b/extensions/VSCode/src/extension.ts @@ -30,7 +30,7 @@ import process from "node:process"; // ### Third-party packages import escape from "escape-html"; -import vscode from "vscode"; +import vscode, { commands, ViewColumn } from "vscode"; import { WebSocket } from "ws"; // ### Local packages @@ -127,7 +127,7 @@ interface MessageResult { interface JointMessageContents { Update?: UpdateMessageContents; - CurrentFile?: string | undefined; + CurrentFile?: [string, boolean?]; Opened?: IdeType; RequestClose?: null; LoadFile?: string; @@ -439,23 +439,46 @@ export const activate = (context: vscode.ExtensionContext) => { } case "CurrentFile": { - const current_file = value as string; - vscode.workspace - .openTextDocument(current_file) - .then( - (document) => { - ignore_active_editor_change = true; - vscode.window.showTextDocument( - document, - current_editor?.viewColumn + const current_file = value[0] as string; + const is_text = value[1] as boolean | undefined; + if (is_text) { + vscode.workspace + .openTextDocument(current_file) + .then( + (document) => { + ignore_active_editor_change = + true; + vscode.window.showTextDocument( + document, + current_editor?.viewColumn + ); + send_result(id); + }, + (reason) => + send_result(id, { + Err: `Error: unable to open file ${current_file}: ${reason}`, + }) + ); + } else { + // TODO: open using a custom document editor. See [openCustomDocument](https://code.visualstudio.com/api/references/vscode-api#CustomEditorProvider.openCustomDocument), which can evidently be called [indirectly](https://stackoverflow.com/a/65101181/4374935). See also [Built-in Commands](https://code.visualstudio.com/api/references/commands). + // For now, simply respond with an OK, since the following doesn't work. + if (false) { + commands + .executeCommand( + "vscode.open", + vscode.Uri.file(current_file), + { viewColumn: current_editor?.viewColumn } + ) + .then( + () => send_result(id), + (reason) => + send_result(id, { + Err: `Error: unable to open file ${current_file}: ${reason}`, + }) ); - send_result(id); - }, - (reason) => - send_result(id, { - Err: `Error: unable to open file ${current_file}: ${reason}`, - }) - ); + } + send_result(id); + } break; } @@ -635,7 +658,7 @@ const current_file = () => { if (can_render() && ate !== current_editor) { current_editor = ate; send_message({ - CurrentFile: ate!.document.fileName, + CurrentFile: [ate!.document.fileName, undefined], }); } }; diff --git a/server/Cargo.lock b/server/Cargo.lock index f700ddf1..d8097cba 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -378,9 +378,9 @@ checksum = "052f3d9bc969124791aef91c0aa2b5f89ed4410a44e59858a0b10f76297e736c" [[package]] name = "async-trait" -version = "0.1.87" +version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", @@ -499,9 +499,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.16" +version = "1.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" +checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a" dependencies = [ "jobserver", "libc", @@ -530,9 +530,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.32" +version = "4.5.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83" +checksum = "e958897981290da2a852763fe9cdb89cd36977a5d729023127095fa94d95e2ff" dependencies = [ "clap_builder", "clap_derive", @@ -540,9 +540,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.32" +version = "4.5.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8" +checksum = "83b0f35019843db2160b5bb19ae09b4e6411ac33fc6a712003c33e03090e2489" dependencies = [ "anstream", "anstyle", @@ -570,7 +570,7 @@ checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "codechat-editor-server" -version = "0.1.14" +version = "0.1.15" dependencies = [ "actix-files", "actix-http", @@ -603,6 +603,7 @@ dependencies = [ "regex", "serde", "serde_json", + "thiserror 2.0.12", "tokio", "tokio-postgres", "tokio-tungstenite", @@ -707,9 +708,9 @@ checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010" [[package]] name = "deranged" -version = "0.3.11" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "28cfac68e08048ae1883171632c2aef3ebc555621ae56fbccce1cbf22dd7f058" dependencies = [ "powerfmt", ] @@ -890,9 +891,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "form_urlencoded" @@ -989,14 +990,14 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", "libc", - "wasi 0.13.3+wasi-0.2.2", - "windows-targets", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", ] [[package]] @@ -1117,14 +1118,15 @@ checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" [[package]] name = "iana-time-zone" -version = "0.1.61" +version = "0.1.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "b2fd658b06e56721792c5df4475705b6cda790e9298d19d2f8af083457bcd127" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", "windows-core", ] @@ -1179,9 +1181,9 @@ dependencies = [ [[package]] name = "icu_locid_transform_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" [[package]] name = "icu_normalizer" @@ -1203,9 +1205,9 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" [[package]] name = "icu_properties" @@ -1224,9 +1226,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" [[package]] name = "icu_provider" @@ -1436,9 +1438,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9" +checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" [[package]] name = "litemap" @@ -1475,9 +1477,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.26" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" dependencies = [ "serde", ] @@ -1648,9 +1650,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.1" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "open" @@ -1715,9 +1717,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.15" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" +checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6" dependencies = [ "memchr", "thiserror 2.0.12", @@ -1726,9 +1728,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.15" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" +checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5" dependencies = [ "pest", "pest_generator", @@ -1736,9 +1738,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.15" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" +checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841" dependencies = [ "pest", "pest_meta", @@ -1749,9 +1751,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.15" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" +checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0" dependencies = [ "once_cell", "pest", @@ -1905,6 +1907,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "rand" version = "0.8.5" @@ -1962,7 +1970,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.1", + "getrandom 0.3.2", ] [[package]] @@ -2026,9 +2034,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7178faa4b75a30e269c71e61c353ce2748cf3d76f0c44c393f4e60abf49b825" +checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96" dependencies = [ "bitflags 2.9.0", "errno", @@ -2197,9 +2205,9 @@ checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" [[package]] name = "socket2" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" dependencies = [ "libc", "windows-sys 0.52.0", @@ -2269,13 +2277,12 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.18.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c317e0a526ee6120d8dabad239c8dadca62b24b6f168914bbbc8e2fb1f0e567" +checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" dependencies = [ - "cfg-if", "fastrand", - "getrandom 0.3.1", + "getrandom 0.3.2", "once_cell", "rustix", "windows-sys 0.59.0", @@ -2339,9 +2346,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.39" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", @@ -2354,15 +2361,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" -version = "0.2.20" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", @@ -2677,9 +2684,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" -version = "0.13.3+wasi-0.2.2" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] @@ -2760,9 +2767,9 @@ dependencies = [ [[package]] name = "whoami" -version = "1.5.2" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" +checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" dependencies = [ "redox_syscall", "wasite", @@ -2831,9 +2838,9 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" [[package]] name = "windows-sys" @@ -2935,9 +2942,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "wit-bindgen-rt" -version = "0.33.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags 2.9.0", ] @@ -2980,18 +2987,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.23" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" +checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.23" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" +checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" dependencies = [ "proc-macro2", "quote", @@ -3052,18 +3059,18 @@ dependencies = [ [[package]] name = "zstd-safe" -version = "7.2.3" +version = "7.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3051792fbdc2e1e143244dc28c60f73d8470e93f3f9cbd0ead44da5ed802722" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.14+zstd.1.5.7" +version = "2.0.15+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb060d4926e4ac3a3ad15d864e99ceb5f343c6b34f5bd6d81ae6ed417311be5" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" dependencies = [ "cc", "pkg-config", diff --git a/server/Cargo.toml b/server/Cargo.toml index 3afa5e3c..733e6bb7 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -31,7 +31,7 @@ license = "GPL-3.0-only" name = "codechat-editor-server" readme = "../README.md" repository = "https://github.com/bjones1/CodeChat_Editor" -version = "0.1.14" +version = "0.1.15" # This library allows other packages to use core CodeChat Editor features. [lib] @@ -68,6 +68,7 @@ pulldown-cmark = { version = "0.13", default-features = false, features = ["html regex = "1" serde = { version = "1", features = ["derive"] } serde_json = "1" +thiserror = "2.0.12" tokio = { version = "1", features = ["full"] } tokio-postgres = { version = "0.7", features = ["with-chrono-0_4"] } url = "2.5.2" diff --git a/server/src/processing.rs b/server/src/processing.rs index dc701640..400b6dd0 100644 --- a/server/src/processing.rs +++ b/server/src/processing.rs @@ -76,6 +76,7 @@ pub struct CodeChatForWeb { /// of comment delimiters? #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct SourceFileMetadata { + /// The lexer used to transforms source code into code and doc blocks and vice versa. pub mode: String, } @@ -107,14 +108,14 @@ pub type CodeMirrorDocBlocks = Vec<( /// Editor format. #[derive(Debug, PartialEq)] pub enum TranslationResults { - // This file is unknown to and therefore not supported by the CodeChat + /// This file is unknown to and therefore not supported by the CodeChat // Editor. Unknown, - // This is a CodeChat Editor file but it contains errors that prevent its - // translation. The string contains the error message. + /// This is a CodeChat Editor file but it contains errors that prevent its + /// translation. The string contains the error message. Err(String), - // A CodeChat Editor file; the struct contains the file's contents - // translated to CodeMirror. + /// A CodeChat Editor file; the struct contains the file's contents + /// translated to CodeMirror. CodeChat(CodeChatForWeb), } @@ -122,16 +123,18 @@ pub enum TranslationResults { /// rendering of the CodeChat Editor format. #[derive(Debug, PartialEq)] pub enum TranslationResultsString { - // This file is unknown to and therefore not supported by the CodeChat - // Editor. + /// This is a binary file; it must be viewed raw or using the simple viewer. + Binary, + /// This file is unknown to the CodeChat + /// Editor. It must be viewed raw or using the simple viewer. Unknown, - // This is a CodeChat Editor file but it contains errors that prevent its - // translation. The string contains the error message. + /// This is a CodeChat Editor file but it contains errors that prevent its + /// translation. The string contains the error message. Err(String), - // A CodeChat Editor file; the struct contains the file's contents - // translated to CodeMirror. + /// A CodeChat Editor file; the struct contains the file's contents + /// translated to CodeMirror. CodeChat(CodeChatForWeb), - // The table of contents file, translated to HTML. + /// The table of contents file, translated to HTML. Toc(String), } diff --git a/server/src/test_utils.rs b/server/src/test_utils.rs index 53034de2..bd982613 100644 --- a/server/src/test_utils.rs +++ b/server/src/test_utils.rs @@ -46,6 +46,7 @@ use crate::testing_logger; // macro does not handle nested pattern like` Some(Animal(cat))\`. #[macro_export] macro_rules! cast { + // For an enum containing a single value (the typical case). ($target: expr_2021, $pat: path) => {{ // The if let exploits recent Rust compiler's smart pattern matching. // Contrary to other solutions like `into_variant`` and friends, this @@ -55,12 +56,22 @@ macro_rules! cast { // variants. if let $pat(a) = $target { a - } else { + } + else { // If the variant and value mismatch, the macro will simply panic // and report the expected pattern. panic!("mismatch variant when cast to {}", stringify!($pat)); } }}; + // For an enum containing multiple values, return a tuple. I can't figure out how to automatically do this; for now, the caller must provide the correct number of tuple parameters. + ($target: expr_2021, $pat: path, $( $tup: ident),*) => {{ + if let $pat($($tup,)*) = $target { + ($($tup,)*) + } + else { + panic!("mismatch variant when cast to {}", stringify!($pat)); + } + }}; } // Get the name (and module path) to the current function. From diff --git a/server/src/webserver.rs b/server/src/webserver.rs index fe7f15f4..5e82249c 100644 --- a/server/src/webserver.rs +++ b/server/src/webserver.rs @@ -28,7 +28,7 @@ mod vscode; // ### Standard library use std::{ collections::{HashMap, HashSet}, - env, fs, + env, fs, io, path::{self, MAIN_SEPARATOR_STR, Path, PathBuf}, str::FromStr, sync::{Arc, Mutex}, @@ -74,7 +74,7 @@ use url::Url; // ### Local //use crate::capture::EventCapture; use crate::processing::{ - CodeChatForWeb, TranslationResultsString, source_to_codechat_for_web_string, + CodeChatForWeb, TranslationResultsString, find_path_to_toc, source_to_codechat_for_web_string, }; use filewatcher::{ filewatcher_browser_endpoint, filewatcher_client_endpoint, filewatcher_root_fs_redirect, @@ -102,25 +102,36 @@ struct WebsocketQueues { /// for it. This is used to send a response to the HTTP task to an HTTP request /// made to that task. Send: String, response struct ProcessingTaskHttpRequest { + /// The URL provided by this request. + url: String, /// The path of the file requested. file_path: PathBuf, - /// True if this file is a TOC. - is_toc: bool, + /// Flags for this file: none, TOC, raw. + flags: ProcessingTaskHttpRequestFlags, /// True if test mode is enabled. is_test_mode: bool, /// A queue to send the response back to the HTTP task. response_queue: oneshot::Sender, } +#[derive(Debug, PartialEq)] +enum ProcessingTaskHttpRequestFlags { + // No flags provided. + None, + // This file is a TOC. + Toc, + /// This should be sent as the raw file. + Raw, +} + /// Since an `HttpResponse` doesn't implement `Send`, use this as a proxy to /// cover all responses to serving a file. #[derive(Debug)] enum SimpleHttpResponse { /// Return a 200 with the provided string as the HTML body. Ok(String), - /// Return an error (400 status code) with the provided string as the HTML - /// body. - Err(String), + /// Return an error as the HTML body. + Err(SimpleHttpResponseError), /// Serve the raw file content, using the provided content type. Raw(String, Mime), /// The file contents are not UTF-8; serve it from the filesystem path @@ -128,6 +139,25 @@ enum SimpleHttpResponse { Bin(PathBuf), } +// List all the possible errors when responding to an HTTP request. See [The +// definitive guide to error handling in +// Rust](https://www.howtocodeit.com/articles/the-definitive-guide-to-rust-error-handling). +#[derive(Debug, thiserror::Error)] +enum SimpleHttpResponseError { + #[error("Error opening file")] + Io(#[from] io::Error), + #[error("Project path {0:?} has no final component.")] + ProjectPathShort(PathBuf), + #[error("Path {0:?} cannot be translated to a string.")] + PathNotString(PathBuf), + #[error("Path {0:?} is not a project.")] + PathNotProject(PathBuf), + #[error("Bundled file {0} not found.")] + BundledFileNotFound(String), + #[error("Lexer error: {0}.")] + LexerError(String), +} + /// Define the data structure used to pass data between the CodeChat Editor /// Client, the IDE, and the CodeChat Editor Server. #[derive(Debug, Serialize, Deserialize, PartialEq)] @@ -148,7 +178,12 @@ enum EditorMessageContents { /// destinations: IDE, Client. Update(UpdateMessageContents), /// Specify the current file to edit. Valid destinations: IDE, Client. - CurrentFile(String), + CurrentFile( + // A path/URL to this file. + String, + // True if the file is text; False if it's binary; None if the file's type hasn't been determined. This is only used by the IDE, which needs to know whether it's opening a text file or a binary file. When sending this message, the IDE and Client can both send `None`; the Server will determine the value if needed. + Option, + ), // #### These messages may only be sent by the IDE. /// This is the first message sent when the IDE starts up. It may only be @@ -544,9 +579,22 @@ pub async fn filesystem_endpoint( web::Query>, actix_web::error::QueryPayloadError, > = web::Query::>::from_query(req.query_string()); - let is_toc = - query_params.is_ok_and(|query| query.get("mode").is_some_and(|mode| mode == "toc")); + let is_toc = query_params + .as_ref() + .is_ok_and(|query| query.get("mode").is_some_and(|mode| mode == "toc")); + let is_raw = query_params + .as_ref() + .is_ok_and(|query| query.get("raw").is_some()); let is_test_mode = get_test_mode(req); + let flags = if is_toc { + // Both flags should never be set. + assert!(!is_raw); + ProcessingTaskHttpRequestFlags::Toc + } else if is_raw { + ProcessingTaskHttpRequestFlags::Raw + } else { + ProcessingTaskHttpRequestFlags::None + }; // Create a one-shot channel used by the processing task to provide a // response to this request. @@ -569,8 +617,9 @@ pub async fn filesystem_endpoint( // Send it the request. if let Err(err) = processing_tx .send(ProcessingTaskHttpRequest { + url: req.path().to_string(), file_path, - is_toc, + flags, is_test_mode, response_queue: tx, }) @@ -587,7 +636,7 @@ pub async fn filesystem_endpoint( SimpleHttpResponse::Ok(body) => HttpResponse::Ok() .content_type(ContentType::html()) .body(body), - SimpleHttpResponse::Err(body) => html_not_found(&body), + SimpleHttpResponse::Err(body) => html_not_found(&format!("{}", body)), SimpleHttpResponse::Raw(body, content_type) => { HttpResponse::Ok().content_type(content_type).body(body) } @@ -616,6 +665,8 @@ async fn make_simple_http_response( http_request: &ProcessingTaskHttpRequest, // Path to the file currently being edited. current_filepath: &Path, + // True to use the PDF.js viewer for this file. + use_pdf_js: bool, ) -> ( // The response to send back to the HTTP endpoint. SimpleHttpResponse, @@ -629,40 +680,50 @@ async fn make_simple_http_response( // Read the file match File::open(file_path).await { Err(err) => ( - SimpleHttpResponse::Err(format!("

Error opening file {file_path:?}: {err}.")), + SimpleHttpResponse::Err(SimpleHttpResponseError::Io(err)), None, ), Ok(mut fc) => { - let mut file_contents = String::new(); - match fc.read_to_string(&mut file_contents).await { - // If this is a binary file (meaning we can't read the contents - // as UTF-8), just serve it raw; assume this is an - // image/video/etc. - Err(_) => (SimpleHttpResponse::Bin(file_path.clone()), None), - Ok(_) => { - text_file_to_response(http_request, current_filepath, file_path, &file_contents) - .await - } - } + let file_contents = try_read_as_text(&mut fc).await; + // If this is a binary file (meaning we can't read the contents + // as UTF-8), send the contents as none to signal this isn't a + // text file. + file_to_response( + http_request, + current_filepath, + file_contents.as_deref(), + use_pdf_js, + ) + .await } } } +// Determine if the provided file is text or binary. If text, return it as a Unicode string. If binary, return None. +async fn try_read_as_text(file: &mut File) -> Option { + let mut file_contents = String::new(); + // TODO: this is a rather crude way to detect if a file is binary. It's probably slow for large file (the [underlying code](https://github.com/tokio-rs/tokio/blob/master/tokio/src/io/util/read_to_string.rs#L57) looks like it reads the entire file to memory, then converts that to UTF-8). Find a heuristic sniffer instead, such as [libmagic](https://docs.rs/magic/0.13.0-alpha.3/magic/). + if file.read_to_string(&mut file_contents).await.is_ok() { + Some(file_contents) + } else { + None + } +} + // Given a text file, determine the appropriate HTTP response: a Client, or the // file contents itself (if it's not editable by the Client). If responding with // a Client, also return an Update message which will provided the contents for // the Client. -async fn text_file_to_response( +async fn file_to_response( // The HTTP request presented to the processing task. http_request: &ProcessingTaskHttpRequest, // Path to the file currently being edited. This path should be cleaned by // `try_canonicalize`. current_filepath: &Path, - // Path to this text file. This path should be cleaned by - // `try_canonicalize`. - file_path: &Path, - // Contents of this text file. - file_contents: &str, + // Contents of this file, if it's text; None if it was binary data. + file_contents: Option<&str>, + // True to use the PDF.js viewer for this file. + use_pdf_js: bool, ) -> ( // The response to send back to the HTTP endpoint. SimpleHttpResponse, @@ -672,11 +733,11 @@ async fn text_file_to_response( Option, ) { // Use a lossy conversion, since this is UI display, not filesystem access. + let file_path = &http_request.file_path; let Some(file_name) = file_path.file_name() else { return ( - SimpleHttpResponse::Err(format!( - "Path {} has no final component.", - file_path.to_string_lossy() + SimpleHttpResponse::Err(SimpleHttpResponseError::ProjectPathShort( + file_path.to_path_buf(), )), None, ); @@ -689,19 +750,21 @@ async fn text_file_to_response( } else { "" }; - let Some(codechat_editor_js) = - BUNDLED_FILES_MAP.get(&format!("CodeChatEditor{js_test_suffix}.js")) - else { + let codechat_editor_js_name = format!("CodeChatEditor{js_test_suffix}.js"); + let Some(codechat_editor_js) = BUNDLED_FILES_MAP.get(&codechat_editor_js_name) else { return ( - SimpleHttpResponse::Err(format!("CodeChatEditor{js_test_suffix}.js not found")), + SimpleHttpResponse::Err(SimpleHttpResponseError::BundledFileNotFound( + codechat_editor_js_name, + )), None, ); }; - let Some(codehat_editor_css) = - BUNDLED_FILES_MAP.get(&format!("CodeChatEditor{js_test_suffix}.css")) - else { + let codechat_editor_css_name = format!("CodeChatEditor{js_test_suffix}.css"); + let Some(codehat_editor_css) = BUNDLED_FILES_MAP.get(&codechat_editor_css_name) else { return ( - SimpleHttpResponse::Err(format!("CodeChatEditor{js_test_suffix}.css not found")), + SimpleHttpResponse::Err(SimpleHttpResponseError::BundledFileNotFound( + codechat_editor_css_name, + )), None, ); }; @@ -709,18 +772,83 @@ async fn text_file_to_response( // Compare these files, since both have been canonicalized by // `try_canonical`. let is_current_file = file_path == current_filepath; - let (translation_results_string, path_to_toc) = if is_current_file || http_request.is_toc { - source_to_codechat_for_web_string(file_contents, file_path, http_request.is_toc) + let is_toc = http_request.flags == ProcessingTaskHttpRequestFlags::Toc; + let (translation_results_string, path_to_toc) = if let Some(file_contents_text) = file_contents + { + if is_current_file || is_toc { + source_to_codechat_for_web_string(file_contents_text, file_path, is_toc) + } else { + // If this isn't the current file, then don't parse it. + (TranslationResultsString::Unknown, None) + } } else { - // If this isn't the current file, then don't parse it. - (TranslationResultsString::Unknown, None) + ( + TranslationResultsString::Binary, + find_path_to_toc(file_path), + ) }; + let is_project = path_to_toc.is_some(); + // For project files, add in the sidebar. Convert this from a Windows path + // to a Posix path if necessary. + let (sidebar_iframe, sidebar_css) = if is_project { + ( + format!( + r#""#, + path_to_toc.unwrap().to_slash_lossy() + ), + format!( + r#""#, + *CODECHAT_EDITOR_PROJECT_CSS + ), + ) + } else { + ("".to_string(), "".to_string()) + }; + + // Do we need to respond with a [simple viewer](#Client-simple-viewer)? + if (translation_results_string == TranslationResultsString::Binary + || translation_results_string == TranslationResultsString::Unknown) + && is_project + && is_current_file + && http_request.flags != ProcessingTaskHttpRequestFlags::Raw + { + let Some(file_name) = file_name.to_str() else { + return ( + SimpleHttpResponse::Err(SimpleHttpResponseError::PathNotString(PathBuf::from( + file_name, + ))), + None, + ); + }; + return ( + make_simple_viewer( + http_request, + &if use_pdf_js { + // For the [PDF.js viewer](#pdf.js), pass the file to view as the query parameter. + format!( + r#""#, + http_request.url + ) + } else { + format!( + r#""# + ) + }, + ), + None, + ); + } + let codechat_for_web = match translation_results_string { + // The file type is binary. Ask the HTTP server to serve it raw. + TranslationResultsString::Binary => return + (SimpleHttpResponse::Bin(file_path.to_path_buf()), None) + , // The file type is unknown. Serve it raw. TranslationResultsString::Unknown => { return ( SimpleHttpResponse::Raw( - file_contents.to_string(), + file_contents.unwrap().to_string(), mime_guess::from_path(file_path).first_or_text_plain(), ), None, @@ -728,7 +856,7 @@ async fn text_file_to_response( } // Report a lexer error. TranslationResultsString::Err(err_string) => { - return (SimpleHttpResponse::Err(err_string), None); + return (SimpleHttpResponse::Err(SimpleHttpResponseError::LexerError(err_string)), None); } // This is a CodeChat file. The following code wraps the CodeChat for // web results in a CodeChat Editor Client webpage. @@ -760,24 +888,6 @@ async fn text_file_to_response( } }; - let is_project = path_to_toc.is_some(); - // For project files, add in the sidebar. Convert this from a Windows path - // to a Posix path if necessary. - let (sidebar_iframe, sidebar_css) = if is_project { - ( - format!( - r#""#, - path_to_toc.unwrap().to_slash_lossy() - ), - format!( - r#""#, - *CODECHAT_EDITOR_PROJECT_CSS - ), - ) - } else { - ("".to_string(), "".to_string()) - }; - // Add testing mode scripts if requested. let testing_src = if http_request.is_test_mode { r#" @@ -791,18 +901,20 @@ async fn text_file_to_response( // Provided info from the HTTP request, determine the following parameters. let Some(raw_dir) = file_path.parent() else { return ( - SimpleHttpResponse::Err(format!( - "Path {} has no parent.", - file_path.to_string_lossy() + SimpleHttpResponse::Err(SimpleHttpResponseError::ProjectPathShort( + file_path.to_path_buf(), )), None, ); }; let dir = path_display(raw_dir); let Some(file_path) = file_path.to_str() else { - let msg = format!("Error: unable to convert path {file_path:?} to a string."); - error!("{msg}"); - return (SimpleHttpResponse::Err(msg), None); + return ( + SimpleHttpResponse::Err(SimpleHttpResponseError::PathNotString( + file_path.to_path_buf(), + )), + None, + ); }; // Build and return the webpage. ( @@ -852,6 +964,82 @@ async fn text_file_to_response( ) } +// Create a [Client Simple Viewer](#Client-simple-viewer) (which shows just the +// TOC, then whatever HTML is provided). This is useful to show images/videos, +// unsupported text files, error messages, etc. when inside a project. +fn make_simple_viewer(http_request: &ProcessingTaskHttpRequest, html: &str) -> SimpleHttpResponse { + // Use a lossy conversion, since this is UI display, not filesystem access. + let file_path = &http_request.file_path; + let Some(file_name) = file_path.file_name() else { + return SimpleHttpResponse::Err(SimpleHttpResponseError::ProjectPathShort( + file_path.to_path_buf(), + )); + }; + let Some(file_name) = file_name.to_str() else { + return SimpleHttpResponse::Err(SimpleHttpResponseError::PathNotString( + file_path.to_path_buf(), + )); + }; + let file_name = escape_html(file_name); + + let Some(path_to_toc) = find_path_to_toc(file_path) else { + return SimpleHttpResponse::Err(SimpleHttpResponseError::PathNotProject( + file_path.to_path_buf(), + )); + }; + let Some(path_to_toc) = path_to_toc.to_str() else { + return SimpleHttpResponse::Err(SimpleHttpResponseError::PathNotString( + path_to_toc.to_path_buf(), + )); + }; + let path_to_toc = escape_html(path_to_toc); + + SimpleHttpResponse::Ok( + // The JavaScript is a stripped-down version of [on\_navigate from + // CodeChatEditor.mts](../../client/src/CodeChatEditor.mts). + formatdoc!( + r#" + + + + + + {file_name} - The CodeChat Editor + + + + + + {html} + + "#, + *CODECHAT_EDITOR_PROJECT_CSS + ), + ) +} + /// Websockets /// ---------- /// diff --git a/server/src/webserver/filewatcher.rs b/server/src/webserver/filewatcher.rs index 61e769fb..e1c190f9 100644 --- a/server/src/webserver/filewatcher.rs +++ b/server/src/webserver/filewatcher.rs @@ -399,7 +399,7 @@ async fn processing_task(file_path: &Path, app_state: web::Data, conne let url_pathbuf = path_to_url("/fw/fsc", &connection_id.to_string(), cfp); queue_send!(to_websocket_tx.send(EditorMessage { id, - message: EditorMessageContents::CurrentFile(url_pathbuf) + message: EditorMessageContents::CurrentFile(url_pathbuf, None) }), 'task); // Note: it's OK to postpone the increment to here; if the // `queue_send` exits before this runs, the message didn't get @@ -534,7 +534,7 @@ async fn processing_task(file_path: &Path, app_state: web::Data, conne // file, which will still produce an error. let empty_path = PathBuf::new(); let cfp = current_filepath.as_ref().unwrap_or(&empty_path); - let (simple_http_response, option_update) = make_simple_http_response(&http_request, cfp).await; + let (simple_http_response, option_update) = make_simple_http_response(&http_request, cfp, false).await; if let Some(update) = option_update { // Send the update to the client. queue_send!(to_websocket_tx.send(EditorMessage { id, message: update })); @@ -607,7 +607,7 @@ async fn processing_task(file_path: &Path, app_state: web::Data, conne send_response(&to_websocket_tx, m.id, result).await; } - EditorMessageContents::CurrentFile(url_string) => { + EditorMessageContents::CurrentFile(url_string, _is_text) => { let result = match url_to_path(&url_string, FILEWATCHER_PATH_PREFIX) { Err(err) => Err(err), Ok(ref file_path) => 'err_exit: { @@ -795,6 +795,10 @@ mod tests { let m = get_message(&mut $client_rx).await; (m.id, cast!(m.message, $cast_type)) }}; + ($client_rx: expr_2021, $cast_type: ty, $( $tup: ident),*) => {{ + let m = get_message(&mut $client_rx).await; + (m.id, cast!(m.message, $cast_type, $($tup),*)) + }}; } #[actix_web::test] @@ -807,8 +811,14 @@ mod tests { // The initial web request for the Client framework produces a // `CurrentFile`. - let (id, url_string) = get_message_as!(client_rx, EditorMessageContents::CurrentFile); + let (id, (url_string, is_text)) = get_message_as!( + client_rx, + EditorMessageContents::CurrentFile, + file_name, + is_text + ); assert_eq!(id, 0.0); + assert_eq!(is_text, None); // Compute the path this message should contain. let mut test_path = test_dir.clone(); @@ -864,7 +874,12 @@ mod tests { // The initial web request for the Client framework produces a // `CurrentFile`. - let (id, _) = get_message_as!(client_rx, EditorMessageContents::CurrentFile); + let (id, (..)) = get_message_as!( + client_rx, + EditorMessageContents::CurrentFile, + file_name, + is_text + ); assert_eq!(id, 0.0); send_response(&ide_tx_queue, 0.0, Ok(ResultOkTypes::Void)).await; @@ -1064,7 +1079,7 @@ mod tests { ide_tx_queue .send(EditorMessage { id: 7.0, - message: EditorMessageContents::CurrentFile(new_uri.clone()), + message: EditorMessageContents::CurrentFile(new_uri.clone(), None), }) .await .unwrap(); diff --git a/server/src/webserver/vscode.rs b/server/src/webserver/vscode.rs index 615f442a..9f5495ee 100644 --- a/server/src/webserver/vscode.rs +++ b/server/src/webserver/vscode.rs @@ -21,7 +21,7 @@ // ------- // // ### Standard library -use std::{cmp::min, collections::HashMap, path::PathBuf}; +use std::{cmp::min, collections::HashMap, ffi::OsStr, path::PathBuf}; // ### Third-party use actix_web::{ @@ -32,7 +32,7 @@ use actix_web::{ use indoc::formatdoc; use log::{debug, error, warn}; use open; -use tokio::{select, sync::mpsc}; +use tokio::{fs::File, select, sync::mpsc}; // ### Local use super::{ @@ -48,9 +48,8 @@ use crate::{ queue_send, webserver::{ INITIAL_MESSAGE_ID, MESSAGE_ID_INCREMENT, ProcessingTaskHttpRequest, ResultOkTypes, - UpdateMessageContents, escape_html, filesystem_endpoint, html_wrapper, - make_simple_http_response, path_to_url, text_file_to_response, try_canonicalize, - url_to_path, + UpdateMessageContents, escape_html, file_to_response, filesystem_endpoint, html_wrapper, + make_simple_http_response, path_to_url, try_canonicalize, try_read_as_text, url_to_path, }, }; @@ -357,15 +356,16 @@ pub async fn vscode_ide_websocket( } }; - // Process the file contents. + // Process the file contents. Since VSCode doesn't have a PDF viewer, determine if this is a PDF file. (TODO: look at the magic number also -- "%PDF"). + let use_pdf_js = http_request.file_path.extension() == Some(OsStr::new("pdf")); let (simple_http_response, option_update) = match file_contents_option { Some(file_contents) => - text_file_to_response(&http_request, ¤t_file, &http_request.file_path, file_contents).await, + file_to_response(&http_request, ¤t_file, Some(file_contents), use_pdf_js).await, None => { // The file wasn't available in the IDE. // Look for it in the filesystem. debug!("Sending HTTP response."); - make_simple_http_response(&http_request, ¤t_file).await + make_simple_http_response(&http_request, ¤t_file, use_pdf_js).await } }; if let Some(update) = option_update { @@ -404,6 +404,8 @@ pub async fn vscode_ide_websocket( })); Ok(ResultOkTypes::Void) } + // TODO + TranslationResultsString::Binary => Err("TODO".to_string()), TranslationResultsString::Err(err) => Err(format!("Error translating source to CodeChat: {err}").to_string()), TranslationResultsString::Unknown => { // Send the new raw contents. @@ -448,14 +450,14 @@ pub async fn vscode_ide_websocket( // Update the current file; translate it to a URL // then pass it to the Client. - EditorMessageContents::CurrentFile(file_path) => { + EditorMessageContents::CurrentFile(file_path, _is_text) => { debug!("Translating and forwarding it to the Client."); match try_canonicalize(&file_path) { Ok(clean_file_path) => { queue_send!(to_client_tx.send(EditorMessage { id: ide_message.id, message: EditorMessageContents::CurrentFile( - path_to_url("/vsc/fs", &connection_id_task, &clean_file_path) + path_to_url("/vsc/fs", &connection_id_task, &clean_file_path), Some(true) ) })); current_file = file_path.into(); @@ -569,7 +571,7 @@ pub async fn vscode_ide_websocket( // Update the current file; translate it to a URL // then pass it to the IDE. - EditorMessageContents::CurrentFile(url_string) => { + EditorMessageContents::CurrentFile(url_string, _is_text) => { debug!("Forwarding translated path to IDE."); let result = match url_to_path(&url_string, VSCODE_PATH_PREFIX) { Err(err) => Err(format!("Unable to convert URL to path: {err}")), @@ -577,9 +579,15 @@ pub async fn vscode_ide_websocket( match file_path.to_str() { None => Err("Unable to convert path to string.".to_string()), Some(file_path_string) => { + // Use a [binary file sniffer](#binary-file-sniffer) to determine if the file is text or binary. + let is_text = if let Ok(mut fc) = File::open(&file_path).await { + try_read_as_text(&mut fc).await.is_some() + } else { + false + }; queue_send!(to_ide_tx.send(EditorMessage { id: client_message.id, - message: EditorMessageContents::CurrentFile(file_path_string.to_string()) + message: EditorMessageContents::CurrentFile(file_path_string.to_string(), Some(is_text)) })); current_file = file_path; Ok(()) @@ -1091,7 +1099,7 @@ mod test { &mut ws_ide, &EditorMessage { id: 4.0, - message: EditorMessageContents::CurrentFile(file_path_str.clone()), + message: EditorMessageContents::CurrentFile(file_path_str.clone(), None), }, ) .await; @@ -1100,7 +1108,13 @@ mod test { let em = read_message(&mut ws_client).await; assert_eq!(em.id, 4.0); assert_ends_with!( - cast!(&em.message, EditorMessageContents::CurrentFile), + cast!( + &em.message, + EditorMessageContents::CurrentFile, + file_name, + is_text + ) + .0, "/only-in-ide.py" ); @@ -1232,16 +1246,26 @@ mod test { &mut ws_client, &EditorMessage { id: 2.0, - message: EditorMessageContents::CurrentFile(format!( - "http://localhost:8080/vsc/fs/{connection_id}/{}", - &file_path.to_slash().unwrap() - )), + message: EditorMessageContents::CurrentFile( + format!( + "http://localhost:8080/vsc/fs/{connection_id}/{}", + &file_path.to_slash().unwrap(), + ), + None, + ), }, ) .await; let em = read_message(&mut ws_ide).await; - let cf = cast!(em.message, EditorMessageContents::CurrentFile); + let (cf, is_text) = cast!( + em.message, + EditorMessageContents::CurrentFile, + file_name, + is_text + ); assert_eq!(path::absolute(Path::new(&cf)).unwrap(), file_path); + // Since the file doesn't exist, it's classified as binary by default. + assert_eq!(is_text, Some(false)); assert_eq!(em.id, 2.0); send_message( @@ -1423,16 +1447,24 @@ mod test { &mut ws_client, &EditorMessage { id: 2.0, - message: EditorMessageContents::CurrentFile(format!( - "http://localhost:8080/vsc/fs/{connection_id}/{}", - &file_path.to_slash().unwrap() - )), + message: EditorMessageContents::CurrentFile( + format!( + "http://localhost:8080/vsc/fs/{connection_id}/{}", + &file_path.to_slash().unwrap() + ), + None, + ), }, ) .await; let em = read_message(&mut ws_ide).await; - let cf = cast!(em.message, EditorMessageContents::CurrentFile); + let (cf, _) = cast!( + em.message, + EditorMessageContents::CurrentFile, + file_name, + is_text + ); assert_eq!(cf, file_path.to_str().unwrap().to_string()); assert_eq!(em.id, 2.0);