From 3df7e32b29738f074da61fa0e81c8b52a07a578b Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Sat, 6 Dec 2025 19:15:36 -0600 Subject: [PATCH 01/27] Clean: imports. --- server/src/translation.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/server/src/translation.rs b/server/src/translation.rs index 769e21c..126299e 100644 --- a/server/src/translation.rs +++ b/server/src/translation.rs @@ -211,17 +211,20 @@ use lazy_static::lazy_static; use log::{debug, error, warn}; use rand::random; use regex::Regex; -use tokio::sync::mpsc::{Receiver, Sender}; -use tokio::{fs::File, select, sync::mpsc}; +use tokio::{ + fs::File, + select, + sync::mpsc::{self, Receiver, Sender}, +}; -use crate::lexer::supported_languages::MARKDOWN_MODE; -use crate::processing::CodeMirrorDocBlockVec; // ### Local use crate::{ + lexer::supported_languages::MARKDOWN_MODE, processing::{ CodeChatForWeb, CodeMirror, CodeMirrorDiff, CodeMirrorDiffable, CodeMirrorDocBlock, - SourceFileMetadata, TranslationResultsString, codechat_for_web_to_source, - diff_code_mirror_doc_blocks, diff_str, source_to_codechat_for_web_string, + CodeMirrorDocBlockVec, SourceFileMetadata, TranslationResultsString, + codechat_for_web_to_source, diff_code_mirror_doc_blocks, diff_str, + source_to_codechat_for_web_string, }, queue_send, queue_send_func, webserver::{ From 0f739100d2cd02f55790c74f3962c886e701104d Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Sat, 6 Dec 2025 22:42:41 -0600 Subject: [PATCH 02/27] Add: hydrate/dehydrate HTML: better support for Mermaid and Graphviz. --- server/Cargo.toml | 3 + server/src/processing.rs | 285 +++++++++++++++++++++++++++++++-- server/src/processing/tests.rs | 163 ++++++++++++++++++- server/src/webserver.rs | 3 - 4 files changed, 433 insertions(+), 21 deletions(-) diff --git a/server/Cargo.toml b/server/Cargo.toml index 6402b39..c947339 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -76,11 +76,13 @@ dunce = "1.0.5" futures = { version = "0.3.31", optional = true } futures-util = "0.3.29" htmd = { version = "0.5" } +html5ever = "0.36.1" imara-diff = { version = "0.2", features = [] } indoc = "2.0.5" lazy_static = "1" log = "0.4" log4rs = "1.3" +markup5ever_rcdom = "0.36.0" mime = "0.3.17" mime_guess = "2.0.5" minreq = "2.12.0" @@ -89,6 +91,7 @@ notify-debouncer-full = "0.6" path-slash = "0.2.1" pest = "2.7.14" pest_derive = "2.7.14" +phf = "0.13.1" # Per the [docs](https://docs.rs/crate/pulldown-cmark/latest), skip building the # binary. pulldown-cmark = { version = "0.13", default-features = false, features = ["html"] } diff --git a/server/src/processing.rs b/server/src/processing.rs index 4137561..a7f5bab 100644 --- a/server/src/processing.rs +++ b/server/src/processing.rs @@ -31,11 +31,14 @@ use std::rc::{Rc, Weak}; */ use std::{ borrow::Cow, + cell::RefCell, cmp::{max, min}, ffi::OsStr, + io, iter::Map, ops::Range, path::{Path, PathBuf}, + rc::Rc, slice::Iter, }; @@ -51,8 +54,16 @@ use htmd::{ HtmlToMarkdown, options::{LinkStyle, TranslationMode}, }; +use html5ever::{ + Attribute, LocalName, Namespace, ParseOpts, QualName, parse_document, serialize, + serialize::{SerializeOpts, TraversalScope}, + tendril::TendrilSink, + tree_builder::TreeBuilderOpts, +}; use imara_diff::{Algorithm, Diff, Hunk, InternedInput, TokenSource}; use lazy_static::lazy_static; +use markup5ever_rcdom::{Node, NodeData, RcDom, SerializableHandle}; +use phf::phf_map; use pulldown_cmark::{Options, Parser, html}; use regex::Regex; use serde::{Deserialize, Serialize}; @@ -254,7 +265,7 @@ lazy_static! { // Non-greedy wildcard -- match the first separator, so we don't munch // multiple `DOC_BLOCK_SEPARATOR_STRING`s in one replacement. ".*?", - "\n")).unwrap(); + "\n")).unwrap(); } // Use this as a way to end unterminated fenced code blocks and specific types @@ -285,14 +296,16 @@ const DOC_BLOCK_SEPARATOR_STRING: &str = concat!( r#" ~~~~~~~~~~~~~~~~~~~~~~~ - + "# ); // After converting Markdown to HTML, this can be used to split doc blocks -// apart. -const DOC_BLOCK_SEPARATOR_SPLIT_STRING: &str = "\n"; +// apart. Since this is post hydration, the element names are normalized to +// lower case. +const DOC_BLOCK_SEPARATOR_SPLIT_STRING: &str = + "\n"; // Correctly terminated fenced code blocks produce this, which can be removed // from the HTML produced by Markdown conversion. const DOC_BLOCK_SEPARATOR_REMOVE_FENCE: &str = r#" @@ -304,7 +317,8 @@ const DOC_BLOCK_SEPARATOR_REMOVE_FENCE: &str = r#" "#; // The replacement string for the `DOC_BLOCK_SEPARATOR_BROKEN_FENCE` regex. -const DOC_BLOCK_SEPARATOR_MENDED_FENCE: &str = "\n\n"; +const DOC_BLOCK_SEPARATOR_MENDED_FENCE: &str = + "\n\n"; // // The column at which to word wrap doc blocks. @@ -401,6 +415,8 @@ pub enum CodechatForWebToSourceError { HtmlToMarkdownFailed(#[from] HtmlToMarkdownWrappedError), #[error("unable to translate CodeChat to source: {0}")] CannotTranslateCodeChat(#[from] CodeDocBlockVecToSourceError), + #[error("unable to parse HTML {0}")] + ParseFailed(#[from] io::Error), } // Transform `CodeChatForWeb` to source code @@ -436,9 +452,10 @@ pub fn codechat_for_web_to_source( } // Translate the HTML document to Markdown. let converter = HtmlToMarkdownWrapped::new(); - return converter + let wet_html = converter .convert(&code_mirror.doc) - .map_err(CodechatForWebToSourceError::HtmlToMarkdownFailed); + .map_err(CodechatForWebToSourceError::HtmlToMarkdownFailed)?; + return dehydrate_html(&wet_html).map_err(CodechatForWebToSourceError::ParseFailed); } let code_doc_block_vec_html = code_mirror_to_code_doc_blocks(code_mirror); let code_doc_block_vec = doc_block_html_to_markdown(code_doc_block_vec_html) @@ -578,6 +595,8 @@ fn doc_block_html_to_markdown( let mut converter = HtmlToMarkdownWrapped::new(); for code_doc_block in &mut code_doc_block_vec { if let CodeDocBlock::DocBlock(doc_block) = code_doc_block { + let contents = dehydrate_html(&doc_block.contents)?; + // Compute a line wrap width based on the current indent. Set a // minimum of half the line wrap width, to prevent ridiculous // wrapping with large indents. @@ -592,7 +611,7 @@ fn doc_block_html_to_markdown( WORD_WRAP_COLUMN, ), )); - doc_block.contents = converter.convert(&doc_block.contents)?; + doc_block.contents = converter.convert(&contents)?; } } @@ -757,6 +776,9 @@ fn code_doc_block_vec_to_source( pub enum SourceToCodeChatForWebError { #[error("unknown lexer {0}")] UnknownLexer(String), + // Since we want `PartialEq`, we can't use `#[from] io::Error`; instead, convert the IO error to a string. + #[error("unable to parse HTML {0}")] + ParseFailed(String), } // Transform from source code to `CodeChatForWeb` @@ -807,8 +829,9 @@ pub fn source_to_codechat_for_web( version, source: if lexer.language_lexer.lexer_name.as_str() == MARKDOWN_MODE { // Document-only files are easy: just encode the contents. - let html = markdown_to_html(file_contents); - // TODO: process the HTML. + let dry_html = markdown_to_html(file_contents); + let html = hydrate_html(&dry_html) + .map_err(|e| SourceToCodeChatForWebError::ParseFailed(e.to_string()))?; CodeMirrorDiffable::Plain(CodeMirror { doc: html, doc_blocks: vec![], @@ -861,6 +884,9 @@ pub fn source_to_codechat_for_web( .replace_all(&html, DOC_BLOCK_SEPARATOR_MENDED_FENCE); // 2. Remove good fences. let html = html.replace(DOC_BLOCK_SEPARATOR_REMOVE_FENCE, ""); + // 3. Hydrate the cleaned HTML. + let html = hydrate_html(&html) + .map_err(|e| SourceToCodeChatForWebError::ParseFailed(e.to_string()))?; // 3. Split on the separator. let mut doc_block_contents_iter = html.split(DOC_BLOCK_SEPARATOR_SPLIT_STRING); // @@ -969,16 +995,251 @@ pub fn source_to_codechat_for_web_string( /// CommonMark spec.) fn markdown_to_html(markdown: &str) -> String { let mut options = Options::all(); - // Turndown (which converts HTML back to Markdown) doesn't support smart + // htmd (which converts HTML back to Markdown) doesn't support smart // punctuation. options.remove(Options::ENABLE_SMART_PUNCTUATION); - options.remove(Options::ENABLE_MATH); let parser = Parser::new_ext(markdown, options); let mut html_output = String::new(); html::push_html(&mut html_output, parser); html_output } +// A framework to transform HTML by parsing it to a DOM tree, walking the tree, then serializing the tree back to an HTML string. +fn transform_html)>(html_in: &str, transform: T) -> io::Result { + // The approach: transform the HTML to a DOM tree, then walk the three to apply these transformations. + // + // First, parse it to a DOM. + let dom = parse_document( + RcDom::default(), + ParseOpts { + tree_builder: TreeBuilderOpts { + scripting_enabled: true, + ..Default::default() + }, + ..Default::default() + }, + ) + .from_utf8() + .read_from(&mut html_in.as_bytes())?; + + // Walk and transform the DOM. + transform(dom.document.clone()); + + // Serialize the transformed DOM back to a string. + let so = SerializeOpts { + // Don't include the body node in the output. + traversal_scope: TraversalScope::ChildrenOnly(None), + ..Default::default() + }; + let mut bytes = vec![]; + // HTML is: + // ```html + // <-- element 0 + // ... + // ... <-- element 1 + // + // ``` + let body = dom.document.children.borrow()[0].children.borrow()[1].clone(); + //println!("{:#?}", body); + serialize(&mut bytes, &SerializableHandle::from(body.clone()), so)?; + let html_out = String::from_utf8(bytes).map_err(io::Error::other)?; + + Ok(html_out) +} + +// HTML produced from Markdown needs additional processing, termed hydration: +// +// - Transform math, Mermaid, GraphViz, etc. nodes. +// - (Eventually) record document structure information. +// - (Eventually) assign a unique ID to all links that don't have one. +// - (Eventually) fill in autocomplete fields. +// +fn hydrate_html(html: &str) -> io::Result { + transform_html(html, hydrating_walk_node) +} + +fn hydrating_walk_node(node: Rc) { + for child in node.children.borrow_mut().iter_mut() { + let possible_replacement_child = + // Look for a `
` tag
+        if get_node_tag_name(child) == Some("pre")
+            // with no attributes
+            && let NodeData::Element {
+                attrs: ref_child_attrs, ..
+            } = &child.data
+            && ref_child_attrs.borrow().is_empty()
+            // with one `` child
+            && let code_children = child.children.borrow()
+            && code_children.len() == 1
+            && let code_child = code_children.iter().next().unwrap()
+            && get_node_tag_name(code_child) == Some("code")
+            // with only a `class=language-mermaid` attribute
+            && let NodeData::Element {
+                attrs: ref_code_child_attrs, ..
+            } = &code_child.data
+            && let code_child_attrs = ref_code_child_attrs.borrow()
+            && code_child_attrs.len() == 1
+            && let Some(attr) = code_child_attrs.iter().next()
+            && *attr.name.local == *"class"
+            && let Some(element_name) = CODE_BLOCK_LANGUAGE_TO_CUSTOM_ELEMENT.get(&*attr.value)
+            // with only one Text child
+            && let text_children = &code_child.children.borrow()
+            && text_children.len() == 1
+            && let Some(text_child) = text_children.iter().next()
+            && let NodeData::Text { .. } = &text_child.data
+        {
+            // Make the parent node a `element_name` node, with the child's text.
+            let wc_mermaid = Node::new(NodeData::Element {
+                name: QualName::new(None, Namespace::from(""), LocalName::from(*element_name)),
+                attrs: RefCell::new(vec![]),
+                template_contents: RefCell::new(None),
+                mathml_annotation_xml_integration_point: false,
+            });
+            wc_mermaid.children.borrow_mut().push(text_child.clone());
+            Some(wc_mermaid)
+        } else {
+            // See if this is a math node to replace; if not, this returns `None`.
+            replace_math_node(child, true)
+        };
+
+        // Replace the child if we found a replacement; otherwise, walk it.
+        if let Some(replacement_child) = possible_replacement_child {
+            *child = replacement_child;
+        } else {
+            hydrating_walk_node(child.clone());
+        }
+    }
+}
+
+fn replace_math_node(child: &Rc, is_hydrate: bool) -> Option> {
+    // Look for math produced by pulldown-cmark: ` Some(("\\(", "\\)")),
+            "math math-display" => Some(("$$", "$$")),
+            _ => None,
+        };
+
+        // Since we've already borrowed `child`, we can't `borrow_mut` to modify it. Instead, create a new `span` with delimited text and return that.
+        if let Some(delim) = delim {
+            let contents_str = &*contents.borrow();
+            let delimited_text_str = if is_hydrate { format!("{}{}{}", delim.0, contents_str, delim.1) } else {
+                // Only apply the dehydration is the delimiters are correct.
+                if !contents_str.starts_with(delim.0) || !contents_str.ends_with(delim.1) {
+                    return None;
+                }
+                // Return the contents without the beginning and ending delimiters.
+                contents_str[delim.0.len()..contents_str.len() - delim.1.len()].to_string()
+            };
+            let delimited_text_node = Node::new(NodeData::Text {
+                contents: RefCell::new(delimited_text_str.into()),
+            });
+            let span = Node::new(NodeData::Element {
+                name: QualName::new(None, Namespace::from(""), LocalName::from("span")),
+                attrs: RefCell::new(vec![Attribute {
+                    name: QualName::new(None, Namespace::from(""), LocalName::from("class")),
+                    value: attr_value.clone(),
+                }]),
+                template_contents: RefCell::new(None),
+                mathml_annotation_xml_integration_point: false,
+            });
+            span.children.borrow_mut().push(delimited_text_node);
+            Some(span)
+        } else {
+            None
+        }
+    } else {
+        None
+    }
+}
+
+fn dehydrate_html(html: &str) -> io::Result {
+    transform_html(html, dehydrating_walk_node)
+}
+
+fn dehydrating_walk_node(node: Rc) {
+    for child in node.children.borrow_mut().iter_mut() {
+        // Look for a custom element tag
+        let possible_replacement_child = if let Some(child_name) = get_node_tag_name(child)
+        && let Some(language_name) = CUSTOM_ELEMENT_TO_CODE_BLOCK_LANGUAGE.get(child_name)
+            // with no attributes
+            && let NodeData::Element {
+                attrs: ref_attrs, ..
+            } = &child.data
+            && ref_attrs.borrow().is_empty()
+            // and only one Text child
+            && let text_children = &child.children.borrow()
+            && text_children.len() == 1
+            && let Some(text_child) = text_children.iter().next()
+            && let NodeData::Text { .. } = &text_child.data
+        {
+            // Create `
text_child contents
`. + let pre = Node::new(NodeData::Element { + name: QualName::new(None, Namespace::from(""), LocalName::from("pre")), + attrs: RefCell::new(vec![]), + template_contents: RefCell::new(None), + mathml_annotation_xml_integration_point: false, + }); + let code = Node::new(NodeData::Element { + name: QualName::new(None, Namespace::from(""), LocalName::from("code")), + attrs: RefCell::new(vec![Attribute { + name: QualName::new(None, Namespace::from(""), LocalName::from("class")), + value: (*language_name).into(), + }]), + template_contents: RefCell::new(None), + mathml_annotation_xml_integration_point: false, + }); + code.children.borrow_mut().push(text_child.clone()); + pre.children.borrow_mut().push(code); + Some(pre) + } else { + replace_math_node(child, false) + }; + + // Replace the child if we found a replacement; otherwise, walk it. + if let Some(replacement_child) = possible_replacement_child { + *child = replacement_child; + } else { + dehydrating_walk_node(child.clone()); + } + } +} + +fn get_node_tag_name(node: &Rc) -> Option<&str> { + match &node.data { + NodeData::Document => Some("html"), + NodeData::Element { name, .. } => Some(&name.local), + _ => None, + } +} + +// Translate from Markdown class names for code blocks to the appropriate HTML custom element. +static CODE_BLOCK_LANGUAGE_TO_CUSTOM_ELEMENT: phf::Map<&'static str, &'static str> = phf_map! { + "language-mermaid" => "wc-mermaid", + "language-graphviz" => "graphviz-graph", +}; + +static CUSTOM_ELEMENT_TO_CODE_BLOCK_LANGUAGE: phf::Map<&'static str, &'static str> = phf_map! { + "wc-mermaid" => "language-mermaid", + "graphviz-graph" => "language-graphviz" +}; + // ### Diff support // // This section provides methods to diff the previous and current diff --git a/server/src/processing/tests.rs b/server/src/processing/tests.rs index c5f9c0c..2fa9e82 100644 --- a/server/src/processing/tests.rs +++ b/server/src/processing/tests.rs @@ -24,6 +24,7 @@ use std::{path::PathBuf, str::FromStr}; // ### Third-party +use indoc::indoc; use predicates::prelude::predicate::str; use pretty_assertions::assert_eq; @@ -42,9 +43,10 @@ use crate::{ processing::{ CodeDocBlockVecToSourceError, CodeMirrorDiffable, CodeMirrorDocBlockDelete, CodeMirrorDocBlockTransaction, CodeMirrorDocBlockUpdate, CodechatForWebToSourceError, - SourceToCodeChatForWebError, byte_index_of, code_doc_block_vec_to_source, - code_mirror_to_code_doc_blocks, codechat_for_web_to_source, diff_code_mirror_doc_blocks, - diff_str, source_to_codechat_for_web, + HtmlToMarkdownWrapped, SourceToCodeChatForWebError, byte_index_of, + code_doc_block_vec_to_source, code_mirror_to_code_doc_blocks, codechat_for_web_to_source, + dehydrate_html, diff_code_mirror_doc_blocks, diff_str, hydrate_html, markdown_to_html, + source_to_codechat_for_web, }, test_utils::stringit, }; @@ -733,7 +735,7 @@ fn test_source_to_codechat_for_web_1() { "\n\n", vec![ build_codemirror_doc_block(0, 1, "", "//", "\n"), - build_codemirror_doc_block(1, 2, " ", "//", "

Test

\n"), + build_codemirror_doc_block(1, 2, " ", "//", "

Test

\n
"), ] ))) ); @@ -752,8 +754,8 @@ fn test_source_to_codechat_for_web_1() { "c_cpp", "\n\n", vec![ - build_codemirror_doc_block(0, 1, "", "//", "
\n\n"),
-                build_codemirror_doc_block(1, 2, " ", "//", "

Test

\n"), + build_codemirror_doc_block(0, 1, "", "//", "
\n"),
+                build_codemirror_doc_block(1, 2, " ", "//", "

Test

\n
"), ] ))) ); @@ -1234,3 +1236,152 @@ fn test_diff_2() { ] ); } + +#[test] +fn test_hydrate_html_1() { + // These tests check the translation from Markdown to "wet" HTML (what the user provides) instead of dry -> wet HTML. + assert_eq!( + hydrate_html(&markdown_to_html(indoc!( + "```mermaid + flowchart LR + start --> stop + ``` + " + ))) + .unwrap(), + indoc!( + " + flowchart LR + start --> stop + + " + ) + ); + + assert_eq!( + hydrate_html(&markdown_to_html(indoc!( + "```graphviz + digraph { + start -> stop + } + ``` + " + ))) + .unwrap(), + indoc!( + " + digraph { + start -> stop + } + + " + ) + ); + + // Ensure math doesn't need escaping. + assert_eq!( + hydrate_html(&markdown_to_html(indoc!( + " + ${a}_1, b_{2}$ + $a*1, b*2$ + $[a](b)$ + $3 b$ + $a \\; b$ + + $${a}_1, b_{2}, a*1, b*2, [a](b), 3 b, a \\; b$$ + " + ))) + .unwrap(), + indoc!( + r#" +

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

+

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

+ "# + ) + ); +} + +#[test] +fn test_dehydrate_html_1() { + let converter = HtmlToMarkdownWrapped::new(); + assert_eq!( + converter + .convert( + &dehydrate_html(indoc!( + " + flowchart LR + start --> stop + + " + )) + .unwrap() + ) + .unwrap(), + indoc!( + " + ```mermaid + flowchart LR + start --> stop + ``` + " + ) + ); + + assert_eq!( + converter + .convert( + &dehydrate_html(indoc!( + " + digraph { + start -> stop + } + + " + )) + .unwrap() + ) + .unwrap(), + indoc!( + " + ```graphviz + digraph { + start -> stop + } + ``` + " + ) + ); + + assert_eq!( + converter + .convert( + &dehydrate_html(indoc!( + r#" +

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

+

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

+ "# + )) + .unwrap() + ) + .unwrap(), + indoc!( + " + ${a}_1, b_{2}$ + $a*1, b*2$ + $[a](b)$ + $3
b$ + $a \\; b$ + + $${a}_1, b_{2}, a*1, b*2, [a](b), 3 b, a \\; b$$ + " + ) + ); +} diff --git a/server/src/webserver.rs b/server/src/webserver.rs index 1e1473c..17b870c 100644 --- a/server/src/webserver.rs +++ b/server/src/webserver.rs @@ -466,9 +466,6 @@ const MATHJAX_TAGS: &str = concatdoc!( obj.data.contentEditable = false; }], }, - tex: { - inlineMath: [['$', '$'], ['\\(', '\\)']] - }, }; "#, // Per the From 845e3110ab6322a0675be29ad9283270de455683 Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Sun, 7 Dec 2025 20:00:42 -0600 Subject: [PATCH 03/27] Fix: build a local graphviz; avoid the graphviz editor/unneeded JS. --- client/package.json5 | 1 - .../graphviz-webcomponent/graph.js | 189 ++++++++++++++++++ .../graphviz-webcomponent/renderer.js | 30 +++ .../graphviz-webcomponent/separate-engine.js | 22 ++ client/src/tinymce-config.mts | 4 +- 5 files changed, 243 insertions(+), 3 deletions(-) create mode 100644 client/src/third-party/graphviz-webcomponent/graph.js create mode 100644 client/src/third-party/graphviz-webcomponent/renderer.js create mode 100644 client/src/third-party/graphviz-webcomponent/separate-engine.js diff --git a/client/package.json5 b/client/package.json5 index ea73dc5..07cd6d5 100644 --- a/client/package.json5 +++ b/client/package.json5 @@ -64,7 +64,6 @@ '@codemirror/view': '^6.38.8', '@mathjax/mathjax-newcm-font': '4.0.0', codemirror: '^6.0.2', - 'graphviz-webcomponent': 'github:bjones1/graphviz-webcomponent#dist', mathjax: '4.0.0', mermaid: '^11.12.2', 'npm-check-updates': '^19.1.2', diff --git a/client/src/third-party/graphviz-webcomponent/graph.js b/client/src/third-party/graphviz-webcomponent/graph.js new file mode 100644 index 0000000..72d8e83 --- /dev/null +++ b/client/src/third-party/graphviz-webcomponent/graph.js @@ -0,0 +1,189 @@ +import getRenderer from './separate-engine' + +const scaleKey = Symbol('scale') +// Use this to assign a unique ID to each render. Messages with the rendered +// SVG will be received by multiple graphs, if multiple graphs dispatched +// a request for rendering. +let renderId = 1 + +function requestRendering (element, script, receiveResult) { + const renderer = getRenderer() + renderer.addEventListener('message', receiveResult) + renderer.postMessage({ script: script || element.graph, renderId }) + return renderId++ +} + +function closeRendering (receiveResult) { + const renderer = getRenderer() + renderer.removeEventListener('message', receiveResult) +} + +function triggerEvent (element, type, detail) { + element.dispatchEvent(new CustomEvent(type, { detail })) +} + +function applyScale (element) { + const svg = element.shadowRoot.children[0] + const scale = element.scale + if (svg) { + if (scale) { + svg.style.transform = `scale(${scale})` + svg.style.transformOrigin = 'top left' + } else { + svg.style.transform = '' + svg.style.transformOrigin = '' + } + } +} + +function showImage (element, svg) { + element.shadowRoot.innerHTML = svg + applyScale(element) + triggerEvent(element, 'render', svg) +} + +function showError (element, error) { + console.error('Graphviz failed:', error) + element.shadowRoot.innerHTML = error.message + return triggerEvent(element, 'error', error) +} + +function updateGraph (element) { + return new Promise(resolve => { + element.shadowRoot.innerHTML = '' + const script = element.__textContent; + if (!script) return resolve() + const assignedRenderId = requestRendering(element, script, receiveResult) + + function receiveResult ({ data }) { + const { svg, error, renderId } = data + // This render was for a different request. Ignore it. + if (assignedRenderId !== renderId) return + closeRendering(receiveResult) + if (error) { + error.message = error.message.trim() + showError(element, error) + return resolve(error) + } + showImage(element, svg) + resolve(svg) + } + }) +} + +function tryUpdateGraph (element, script) { + return new Promise((resolve, reject) => { + if (!script) { + element.innerHTML = '' + element.shadowRoot.innerHTML = '' + return resolve() + } + const assignedRenderId = requestRendering(element, script, receiveResult) + + function receiveResult ({ data }) { + const { svg, error, renderId } = data + // This render was for a different request. Ignore it. + if (assignedRenderId !== renderId) return + closeRendering(receiveResult) + if (error) return reject(error) + element.innerHTML = script + showImage(element, svg) + resolve(svg) + } + }) +} + +class GraphvizGraphElement extends HTMLElement { + constructor () { + super() + this.attachShadow({ mode: 'open' }) + this.graphCompleted = Promise.resolve() + // From Mermaid web component -- see below. + this.__renderGraph = this.__renderGraph.bind(this); + } + + get scale () { return this[scaleKey] } + set scale (value) { this.setAttribute('scale', value) } + + attributeChangedCallback (name, oldValue, newValue) { + switch (name) { + case 'scale': + this[scaleKey] = newValue + applyScale(this) + } + } + + tryGraph (graph) { + const promise = tryUpdateGraph(this, graph) + this.graphCompleted = promise.catch(error => error) + return promise + } + + static get observedAttributes () { return ['scale'] } + + __renderGraph() { + this.graphCompleted = updateGraph(this).catch(error => error) + } + + // Copied from https://github.com/manolakis/wc-mermaid/blob/master/src/WcMermaid.js: + /** + * @returns {ChildNode[]} + * @private + */ + get __textNodes() { + return Array.from(this.childNodes).filter( + node => node.nodeType === this.TEXT_NODE + ); + } + + /** + * @returns {string} + * @private + */ + get __textContent() { + return this.__textNodes.map(node => node.textContent?.trim()).join(''); + } + + __observeTextNodes() { + if (this.__textNodeObservers) { + this.__cleanTextNodeObservers(); + } + + this.__textNodeObservers = this.__textNodes.map(textNode => { + const observer = new MutationObserver(this.__renderGraph); + + observer.observe(textNode, { characterData: true }); + + return observer; + }); + } + + __cleanTextNodeObservers() { + if (this.__textNodeObservers) { + this.__textNodeObservers.forEach(observer => observer.disconnect()); + } + } + + connectedCallback() { + this.__observer = new MutationObserver(() => { + this.__observeTextNodes(); + this.__renderGraph(); + }); + this.__observer.observe(this, { childList: true }); + this.__observeTextNodes(); + this.__renderGraph(); + } + + disconnectedCallback() { + this.__cleanTextNodeObservers(); + + if (this.__observer) { + this.__observer.disconnect(); + this.__observer = null; + } + } +} + +customElements.define('graphviz-graph', GraphvizGraphElement) + +export default GraphvizGraphElement diff --git a/client/src/third-party/graphviz-webcomponent/renderer.js b/client/src/third-party/graphviz-webcomponent/renderer.js new file mode 100644 index 0000000..2e44c33 --- /dev/null +++ b/client/src/third-party/graphviz-webcomponent/renderer.js @@ -0,0 +1,30 @@ +import { Graphviz } from '@hpcc-js/wasm/graphviz' + +let graphviz +let fatalError + +async function receiveRequest ({ data }) { + const { script, renderId } = data + if (script === undefined) return // prevent endless message loop in tests + /* c8 ignore next */ + if (fatalError) return postMessage({ error: fatalError, renderId }) + try { + if (!graphviz) graphviz = await Graphviz.load() + const svg = graphviz.dot(script) + postMessage({ svg, renderId }) + } catch ({ message }) { + postMessage({ error: { message }, renderId }) + } +} + +/* c8 ignore next 7 */ +function handleRejection (event) { + event.preventDefault() + const { message } = event.reason + const error = { message: `Graphviz failed. ${message}` } + if (message.includes('fetching of the wasm failed')) fatalError = error + postMessage({ error }) +} + +addEventListener('message', receiveRequest) +addEventListener('unhandledrejection', handleRejection) diff --git a/client/src/third-party/graphviz-webcomponent/separate-engine.js b/client/src/third-party/graphviz-webcomponent/separate-engine.js new file mode 100644 index 0000000..25408fc --- /dev/null +++ b/client/src/third-party/graphviz-webcomponent/separate-engine.js @@ -0,0 +1,22 @@ +/* c8 ignore next */ +const { delayWorkerLoading } = window.graphvizWebComponent || {} +let renderer, rendererUrl + +if (!delayWorkerLoading) setTimeout(getRenderer) + +function ensureConfiguration () { + if (!rendererUrl) { + ({ + rendererUrl = 'https://unpkg.com/graphviz-webcomponent@2.0.0/dist/renderer.min.js' + /* c8 ignore next */ + } = window.graphvizWebComponent || {}) + } +} + +export default function getRenderer () { + if (!renderer) { + ensureConfiguration() + renderer = new Worker(rendererUrl) + } + return renderer +} diff --git a/client/src/tinymce-config.mts b/client/src/tinymce-config.mts index 82c15d2..9698eaf 100644 --- a/client/src/tinymce-config.mts +++ b/client/src/tinymce-config.mts @@ -144,9 +144,9 @@ export const init = async ( // Needed to allow custom elements. extended_valid_elements: - "graphviz-graph[graph|scale],graphviz-script-editor[value|tab],graphviz-combined[graph|scale],wc-mermaid", + "graphviz-graph[scale],wc-mermaid", custom_elements: - "graphviz-graph,graphviz-script-editor,graphviz-combined,wc-mermaid", + "graphviz-graph,wc-mermaid", }); // Merge in additional setup code. From 9cfd2631b2c804376ef7e27b0aa643793d36eaa2 Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Sun, 7 Dec 2025 20:30:30 -0600 Subject: [PATCH 04/27] Fix: build rendered locally; use modern version. --- builder/src/main.rs | 17 +++++++++++++++++ client/package.json5 | 1 + client/src/CodeChatEditor.mts | 2 +- client/src/graphviz-webcomponent-setup.mts | 2 +- .../graphviz-webcomponent/renderer.js | 2 +- client/src/tinymce-config.mts | 6 ++---- 6 files changed, 23 insertions(+), 7 deletions(-) diff --git a/builder/src/main.rs b/builder/src/main.rs index 0b7896d..9515266 100644 --- a/builder/src/main.rs +++ b/builder/src/main.rs @@ -555,6 +555,7 @@ fn run_client_build( CLIENT_PATH, true, )?; + // Date: Sun, 7 Dec 2025 20:49:54 -0600 Subject: [PATCH 05/27] Fix: Put graphviz diagram inside a div, so it's treated as a block element. --- client/src/third-party/graphviz-webcomponent/graph.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/third-party/graphviz-webcomponent/graph.js b/client/src/third-party/graphviz-webcomponent/graph.js index 72d8e83..2d916ff 100644 --- a/client/src/third-party/graphviz-webcomponent/graph.js +++ b/client/src/third-party/graphviz-webcomponent/graph.js @@ -37,7 +37,7 @@ function applyScale (element) { } function showImage (element, svg) { - element.shadowRoot.innerHTML = svg + element.shadowRoot.innerHTML = `
${svg}
` applyScale(element) triggerEvent(element, 'render', svg) } From 877cbbc556918abc25ef3adfd87947efbb9e6e1d Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Sun, 7 Dec 2025 21:30:06 -0600 Subject: [PATCH 06/27] Fix: corner failures cases in save/restoreSelection. --- client/src/CodeMirror-integration.mts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/client/src/CodeMirror-integration.mts b/client/src/CodeMirror-integration.mts index ea51294..8ebd9ce 100644 --- a/client/src/CodeMirror-integration.mts +++ b/client/src/CodeMirror-integration.mts @@ -537,9 +537,11 @@ const saveSelection = () => { // contents: either it's not an element (such as a div), ... current_node.nodeType !== Node.ELEMENT_NODE || // or it's not the doc block contents div. - !(current_node as Element).classList.contains( + (!(current_node as Element).classList.contains( "CodeChat-doc-contents", - ); + ) && + // Sometimes, the parent of a custom node (`wc-mermaid`) skips the TinyMCE div and returns the overall div. I don't know why. + !(current_node as Element).classList.contains("CodeChat-doc")); current_node = current_node.parentNode!, is_first = false ) { // Store the index of this node in its' parent list of child @@ -549,7 +551,14 @@ const saveSelection = () => { // trouble when reversing the selection -- sometimes, the // `childNodes` change based on whether text nodes (such as a // newline) are included are not after tinyMCE parses the content. - let p = current_node.parentNode!; + let p = current_node.parentNode; + // In case we go off the rails, give up if there are no more parents. + if (p === null) { + return { + selection_path: [], + selection_offset: 0, + }; + } selection_path.unshift( Array.prototype.indexOf.call( is_first ? p.childNodes : p.children, @@ -589,6 +598,10 @@ const restoreSelection = ({ : selection_node.childNodes )[selection_path.shift()!]! as HTMLElement ); + // Exit on failure. + if (selection_node === undefined) { + return; + } // Use that to set the selection. tinymce_singleton!.selection.setCursorLocation( selection_node, From 777e7b59d6de65c396962e6cfd5a66e68f08d56c Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Sun, 7 Dec 2025 21:30:24 -0600 Subject: [PATCH 07/27] Docs: Update changelog. --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c234087..fcecf72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,13 @@ Changelog [Github master](https://github.com/bjones1/CodeChat_Editor) -------------------------------------------------------------------------------- -* No changes. +* Update Graphviz to latest build; make all Graphviz output a block element, not + an inline element. +* Allow creating Mermaid and Graphviz diagrams using simpler code block syntax. +* Support math free of Markdown escaping. This is a backward-incompatible + change: you must manually remove Markdown escaping from math written before + this release. +* Fix failures when trying to edit a doc block which contains only diagrams. Version 0.1.43 -- 2025-Dec-05 -------------------------------------------------------------------------------- From 23c821567899c992139d45568be39d562775ac24 Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Sun, 7 Dec 2025 22:10:13 -0600 Subject: [PATCH 08/27] Fix: correctly update doc/doc blocks after client re-translation update. --- server/src/translation.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/server/src/translation.rs b/server/src/translation.rs index 126299e..3d8e9c3 100644 --- a/server/src/translation.rs +++ b/server/src/translation.rs @@ -1043,7 +1043,7 @@ impl TranslationTask { false, ) && let TranslationResultsString::CodeChat(ccfw) = ccfws.0 - && let CodeMirrorDiffable::Plain(ref code_mirror_translated) = + && let CodeMirrorDiffable::Plain(code_mirror_translated) = ccfw.source && self.sent_full { @@ -1081,8 +1081,9 @@ impl TranslationTask { cfw.metadata.clone(), cfw.version, cfw_version, - code_mirror_translated, + &code_mirror_translated, ); + debug!("Sending re-translation update back to the Client."); queue_send_func!(self.to_client_tx.send(EditorMessage { id: self.id, message: EditorMessageContents::Update( @@ -1098,6 +1099,10 @@ impl TranslationTask { ) })); self.id += MESSAGE_ID_INCREMENT; + // Update with what was just sent to the client. + self.code_mirror_doc = code_mirror_translated.doc; + self.code_mirror_doc_blocks = + Some(code_mirror_translated.doc_blocks); } }; // Correct EOL endings for use with the IDE. From 16b474b2cab94c07289222dd3061a1bbb6650cea Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Mon, 8 Dec 2025 12:22:37 -0600 Subject: [PATCH 09/27] Docs: Update docs based on improved HTML processing. --- CHANGELOG.md | 1 + README.md | 96 ++++++++++++++++++++++++++++++++++------------------ 2 files changed, 64 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fcecf72..b556ea2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ Changelog change: you must manually remove Markdown escaping from math written before this release. * Fix failures when trying to edit a doc block which contains only diagrams. +* Fix data corruption bug after sending a re-translation back to the Client. Version 0.1.43 -- 2025-Dec-05 -------------------------------------------------------------------------------- diff --git a/README.md b/README.md index c824ea3..a6f5086 100644 --- a/README.md +++ b/README.md @@ -111,62 +111,92 @@ Mathematics -------------------------------------------------------------------------------- The CodeChat Editor uses [MathJax](https://www.mathjax.org/) to support typeset -mathematics. Place the delimiters `$` or `\\(` and `\\)` immediately before and -after in-line mathematics; place `$$` or `\\\[` and `\\\]` immediately before +mathematics. Place the delimiters `$` immediately before and +after in-line mathematics; place `$$` immediately before and after displayed mathematics. For example, | Source | Rendered | | --------------------------------------------- | ------------------------------------------- | -| `$x = \\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}$` | $x = \\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}$ | -| `\\(a^2\\)` | \\(a^2\\) | +| `$x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$` | $x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$ | | `$$a^2$$` | $$a^2$$ | -| `\\\[a^2\\\]` | \\\[a^2\\\] | See [Latex Mathematics](https://en.wikibooks.org/wiki/LaTeX/Mathematics#Symbols) for the syntax used to write mathematics expressions. -### Escaping - -Markdown recognizes several characters common in mathematical expressions; these -must be backslash escaped when used in math expressions to avoid problems. The -following characters should be escaped: `*`, `_`, `\`, `[`, `]`, `<`. - -| Wrong source | Wrong rendered | Correct source | Correctly Rendered | -| ---------------- | -------------- | ------------------ | ------------------ | -| `${a}_1, b_{2}$` | ${a}*1, b*{2}$ | `${a}\_1, b\_{2}$` | ${a}\_1, b\_{2}$ | -| `$a*1, b*2$` | $a*1, b*2$ | `$a\*1, b\*2$` | $a\*1, b\*2$ | -| `$[a](b)$` | $[a](b)$ | `$\[a\](b)$` | $\[a\](b)$ | -| `$3
b$` | $3 b$ | `$3 \ b$` | $3 \ b$ | -| `$a \; b$` | $a \; b$ | `$a \\; b$` | $a \\; b$ | - Diagrams -------------------------------------------------------------------------------- ### Mermaid -The CodeChat Editor contains rudimentary support for diagrams created by +The CodeChat Editor supports diagrams created by [Mermaid](https://mermaid.js.org/). For example, -| Source | Rendered | -| --------------------------------------------- | ------------------------------------------- | -| `graph TD; A --> B;` | graph TD; A --> B; | + + + + + + + + + + + + +
SourceRendered
+ +````markdown +```mermaid +graph TD; A --> B; +``` +```` + + + +```mermaid +graph TD; A --> B; +``` -To edit these diagrams, use an -[HTML entity encoder/decoder](https://mothereff.in/html-entities) and the -[Mermaid live editor](https://mermaid.live/). +
+ +The [Mermaid live editor](https://mermaid.live/) provide an focused environment for creating Mermaid chart. ### Graphviz -The CodeChat Editor contains rudimentary support for diagrams created by +The CodeChat Editor supports diagrams created by [Graphviz](https://graphviz.org/). For example, -| Source | Rendered | -| ----------------------------------------------------- | --------------------------------------------------- | -| `digraph { A -> B }` | digraph { A -> B } | -To edit these diagrams, use an -[HTML entity encoder/decoder](https://mothereff.in/html-entities) and a Graphviz -editor such as [Edotor](https://edotor.net/). + + + + + + + + + + + + +
SourceRendered
+ +````markdown +```graphviz +digraph { A -> B } +``` +```` + + + +```graphviz +digraph { A -> B } +``` + +
+ + +Several on-line tools, such as [Edotor](https://edotor.net/), provide a focused editing experience. ### PlantUML From 4b23fe698c9a178a2d281847564c4598b01cfded Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Mon, 8 Dec 2025 12:23:39 -0600 Subject: [PATCH 10/27] Fix: Update tests based on HTML processing changes. --- client/src/CodeChatEditor-test.mts | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/client/src/CodeChatEditor-test.mts b/client/src/CodeChatEditor-test.mts index 4c79005..f999998 100644 --- a/client/src/CodeChatEditor-test.mts +++ b/client/src/CodeChatEditor-test.mts @@ -102,23 +102,25 @@ window.CodeChatEditor_test = () => { test("GraphViz, Mathjax, Mermaid", async function () { // Wait for the renderers to run. await sleep(1500); - // Make sure GraphViz includes an SVG at the top of the shadow - // root. - assert.equal( + // Make sure GraphViz includes a `div` at the top of the shadow + // root, with a `svg` inside it. + const gv = document.getElementsByTagName("graphviz-graph")[0] - .shadowRoot!.children[0].tagName, - "svg", - ); + .shadowRoot!.children[0]; + assert.equal(gv.tagName, "DIV"); + assert.equal(gv.children[0].tagName, "svg"); + // Mermaid graphs start with a div. - assert.equal( + const mer = document.getElementsByTagName("wc-mermaid")[0].shadowRoot! - .children[0].tagName, - "DIV", - ); + .children[0]; + assert.equal(mer.tagName, "DIV"); + assert.equal(mer.children[0].tagName, "svg"); + // MathJax has its own stuff. assert.equal( document.getElementsByTagName("mjx-container").length, - 1, + 2, ); }); }); From f0d0c99e09cc92dc3ffbc8143cf579906df5b196 Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Mon, 8 Dec 2025 13:08:01 -0600 Subject: [PATCH 11/27] Clean: refactor tests. --- server/tests/overall_core/mod.rs | 445 +++++++++++-------------------- 1 file changed, 152 insertions(+), 293 deletions(-) diff --git a/server/tests/overall_core/mod.rs b/server/tests/overall_core/mod.rs index d644e8a..a93d273 100644 --- a/server/tests/overall_core/mod.rs +++ b/server/tests/overall_core/mod.rs @@ -53,7 +53,12 @@ // // ### Standard library use std::{ - collections::HashMap, env, error::Error, panic::AssertUnwindSafe, path::PathBuf, time::Duration, + collections::HashMap, + env, + error::Error, + panic::AssertUnwindSafe, + path::{Path, PathBuf}, + time::Duration, }; // ### Third-party @@ -63,8 +68,8 @@ use futures::FutureExt; use indoc::indoc; use pretty_assertions::assert_eq; use thirtyfour::{ - By, ChromiumLikeCapabilities, DesiredCapabilities, Key, WebDriver, error::WebDriverError, - start_webdriver_process, + By, ChromiumLikeCapabilities, DesiredCapabilities, Key, WebDriver, WebElement, + error::WebDriverError, start_webdriver_process, }; use tokio::time::sleep; @@ -305,33 +310,17 @@ async fn goto_line( Ok(()) } -// Tests -// ----------------------------------------------------------------------------- -// -// ### Server-side test -// -// Perform most functions a user would: open/switch documents (Markdown, -// CodeChat, plain, PDF), use hyperlinks, perform edits on code and doc blocks. -mod test1 { - use super::*; - harness!(test_server_core); -} -#[tokio::test] -async fn test_server() -> Result<(), Box> { - test1::harness(test_server_core, prep_test_dir!()).await -} - -// Some of the thirtyfour calls are marked as deprecated, though they aren't -// marked that way in the Selenium docs. -#[allow(deprecated)] -async fn test_server_core( - codechat_server: CodeChatEditorServer, - driver_ref: &WebDriver, - test_dir: PathBuf, -) -> Result<(), WebDriverError> { +async fn perform_loadfile( + codechat_server: &CodeChatEditorServer, + test_dir: &Path, + file_name: &str, + file_contents: Option<(String, f64)>, + has_toc: bool, + server_id: f64, +) -> f64 { let mut expected_messages = ExpectedMessages::new(); - let path = canonicalize(test_dir.join("test.py")).unwrap(); + let path = canonicalize(test_dir.join(file_name)).unwrap(); let path_str = path.to_str().unwrap().to_string(); let current_file_id = codechat_server .send_message_current_file(path_str.clone()) @@ -343,40 +332,41 @@ async fn test_server_core( id: current_file_id, message: EditorMessageContents::Result(Ok(ResultOkTypes::Void)), }); - let mut server_id = 6.0; expected_messages.insert(EditorMessage { id: server_id, - message: EditorMessageContents::LoadFile(path), + message: EditorMessageContents::LoadFile(path.clone()), }); expected_messages - .assert_all_messages(&codechat_server, TIMEOUT) + .assert_all_messages(codechat_server, TIMEOUT) .await; // Respond to the load request. - let mut version = 1.0; - codechat_server - .send_result_loadfile(server_id, Some(("# Test\ncode()".to_string(), version))) - .await - .unwrap(); - - // Respond to the load request for the TOC. - let toc_path = canonicalize(test_dir.join("toc.md")).unwrap(); - server_id += MESSAGE_ID_INCREMENT * 2.0; - assert_eq!( - codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), - EditorMessage { - id: server_id, - message: EditorMessageContents::LoadFile(toc_path), - } - ); codechat_server - .send_result_loadfile(server_id, None) + .send_result_loadfile(server_id, file_contents) .await .unwrap(); + let mut server_id = server_id + MESSAGE_ID_INCREMENT; + + if has_toc { + // Respond to the load request for the TOC. + let toc_path = canonicalize(test_dir.join("toc.md")).unwrap(); + server_id += MESSAGE_ID_INCREMENT; + assert_eq!( + codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), + EditorMessage { + id: server_id, + message: EditorMessageContents::LoadFile(toc_path), + } + ); + codechat_server + .send_result_loadfile(server_id, None) + .await + .unwrap(); + server_id -= MESSAGE_ID_INCREMENT; + } // The loadfile produces a message to the client, which comes back here. We // don't need to acknowledge it. - server_id -= MESSAGE_ID_INCREMENT; assert_eq!( codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), EditorMessage { @@ -385,6 +375,15 @@ async fn test_server_core( } ); + if has_toc { + server_id + 2.0 * MESSAGE_ID_INCREMENT + } else { + server_id + } +} + +#[allow(deprecated)] +async fn select_codechat_iframe(driver_ref: &WebDriver) -> WebElement { // Target the iframe containing the Client. let codechat_iframe = driver_ref.find(By::Css("#CodeChat-iframe")).await.unwrap(); driver_ref @@ -393,11 +392,53 @@ async fn test_server_core( .await .unwrap(); + codechat_iframe +} +// Tests +// ----------------------------------------------------------------------------- +// +// ### Server-side test +// +// Perform most functions a user would: open/switch documents (Markdown, +// CodeChat, plain, PDF), use hyperlinks, perform edits on code and doc blocks. +mod test1 { + use super::*; + harness!(test_server_core); +} + +#[tokio::test] +async fn test_server() -> Result<(), Box> { + test1::harness(test_server_core, prep_test_dir!()).await +} + +// Some of the thirtyfour calls are marked as deprecated, though they aren't +// marked that way in the Selenium docs. +#[allow(deprecated)] +async fn test_server_core( + codechat_server: CodeChatEditorServer, + driver_ref: &WebDriver, + test_dir: PathBuf, +) -> Result<(), WebDriverError> { + let mut expected_messages = ExpectedMessages::new(); + let path = canonicalize(test_dir.join("test.py")).unwrap(); + let path_str = path.to_str().unwrap().to_string(); + let mut version = 1.0; + let mut server_id = perform_loadfile( + &codechat_server, + &test_dir, + "test.py", + Some(("# Test\ncode()".to_string(), version)), + true, + 6.0, + ) + .await; + // ### Tests on source code // // #### Doc block tests // // Verify the first doc block. + let codechat_iframe = select_codechat_iframe(driver_ref).await; let indent_css = ".CodeChat-CodeMirror .CodeChat-doc-indent"; let doc_block_indent = driver_ref.find(By::Css(indent_css)).await.unwrap(); assert_eq!(doc_block_indent.inner_html().await.unwrap(), ""); @@ -425,6 +466,7 @@ async fn test_server_core( }) } ); + client_id += MESSAGE_ID_INCREMENT; // Refind it, since it's now switched with a TinyMCE editor. let tinymce_contents = driver_ref.find(By::Id("TinyMCE-inst")).await.unwrap(); @@ -432,7 +474,7 @@ async fn test_server_core( tinymce_contents.send_keys("foo").await.unwrap(); // Verify the updated text. - client_id += MESSAGE_ID_INCREMENT; + // // Update the version from the value provided by the client, which varies // randomly. let msg = codechat_server.get_message_timeout(TIMEOUT).await.unwrap(); @@ -465,13 +507,13 @@ async fn test_server_core( ); version = client_version; codechat_server.send_result(client_id, None).await.unwrap(); + client_id += MESSAGE_ID_INCREMENT; // Edit the indent. It should only allow spaces and tabs, rejecting other // edits. doc_block_indent.send_keys(" 123").await.unwrap(); let msg = codechat_server.get_message_timeout(TIMEOUT).await.unwrap(); let client_version = get_version(&msg); - client_id += MESSAGE_ID_INCREMENT; assert_eq!( msg, EditorMessage { @@ -500,6 +542,7 @@ async fn test_server_core( ); version = client_version; codechat_server.send_result(client_id, None).await.unwrap(); + client_id += MESSAGE_ID_INCREMENT; // #### Code block tests // @@ -510,7 +553,6 @@ async fn test_server_core( // A click will update the current position and focus the code block. code_line.click().await.unwrap(); - client_id += MESSAGE_ID_INCREMENT; assert_eq!( codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), EditorMessage { @@ -524,12 +566,13 @@ async fn test_server_core( } ); codechat_server.send_result(client_id, None).await.unwrap(); + client_id += MESSAGE_ID_INCREMENT; + // Moving left should move us back to the doc block. code_line .send_keys("" + Key::Home + Key::Left) .await .unwrap(); - client_id += MESSAGE_ID_INCREMENT; assert_eq!( codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), EditorMessage { @@ -543,12 +586,12 @@ async fn test_server_core( } ); codechat_server.send_result(client_id, None).await.unwrap(); + client_id += MESSAGE_ID_INCREMENT; // Make an edit to the code. This should also produce a client diff. code_line.send_keys("bar").await.unwrap(); // Verify the updated text. - client_id += MESSAGE_ID_INCREMENT; let msg = codechat_server.get_message_timeout(TIMEOUT).await.unwrap(); let client_version = get_version(&msg); assert_eq!( @@ -578,6 +621,7 @@ async fn test_server_core( } ); codechat_server.send_result(client_id, None).await.unwrap(); + client_id += MESSAGE_ID_INCREMENT; // #### IDE edits // @@ -641,64 +685,16 @@ async fn test_server_core( assert_eq!(code_line.inner_html().await.unwrap(), "bark"); // ### Document-only tests - // - // Load in a document. - let md_path = canonicalize(test_dir.join("test.md")).unwrap(); - let md_path_str = md_path.to_str().unwrap().to_string(); - let current_file_id = codechat_server - .send_message_current_file(md_path_str.clone()) - .await - .unwrap(); - - // These next two messages can come in either order. Work around this. - expected_messages.insert(EditorMessage { - id: current_file_id, - message: EditorMessageContents::Result(Ok(ResultOkTypes::Void)), - }); - server_id += MESSAGE_ID_INCREMENT * 2.0; - expected_messages.insert(EditorMessage { - id: server_id, - message: EditorMessageContents::LoadFile(md_path), - }); - expected_messages - .assert_all_messages(&codechat_server, TIMEOUT) - .await; - - // Provide the requested file contents. - version = 4.0; - codechat_server - .send_result_loadfile( - server_id, - Some(("A **markdown** file.".to_string(), version)), - ) - .await - .unwrap(); - - // Respond to the load request for the TOC. let toc_path = canonicalize(test_dir.join("toc.md")).unwrap(); - server_id += MESSAGE_ID_INCREMENT * 2.0; - assert_eq!( - codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), - EditorMessage { - id: server_id, - message: EditorMessageContents::LoadFile(toc_path.clone()), - } - ); - codechat_server - .send_result_loadfile(server_id, None) - .await - .unwrap(); - - // Absorb the result produced by the Server's Update resulting from the - // LoadFile. - server_id -= MESSAGE_ID_INCREMENT; - assert_eq!( - codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), - EditorMessage { - id: server_id, - message: EditorMessageContents::Result(Ok(ResultOkTypes::Void)) - } - ); + server_id = perform_loadfile( + &codechat_server, + &test_dir, + "test.md", + Some(("A **markdown** file.".to_string(), version)), + true, + server_id, + ) + .await; // Check the content. let body_css = "#CodeChat-body .CodeChat-doc-contents"; @@ -710,7 +706,8 @@ async fn test_server_core( // Perform edits. body_content.send_keys("foo ").await.unwrap(); - client_id += MESSAGE_ID_INCREMENT; + let md_path = canonicalize(test_dir.join("test.md")).unwrap(); + let md_path_str = md_path.to_str().unwrap().to_string(); let msg = codechat_server.get_message_timeout(TIMEOUT).await.unwrap(); let client_version = get_version(&msg); assert_eq!( @@ -743,6 +740,7 @@ async fn test_server_core( } ); codechat_server.send_result(client_id, None).await.unwrap(); + client_id += MESSAGE_ID_INCREMENT; // Perform an IDE edit. version = 5.0; @@ -779,7 +777,6 @@ async fn test_server_core( id: current_file_id, message: EditorMessageContents::Result(Ok(ResultOkTypes::Void)), }); - server_id += MESSAGE_ID_INCREMENT * 2.0; expected_messages.insert(EditorMessage { id: server_id, message: EditorMessageContents::LoadFile(txt_path.clone()), @@ -794,9 +791,9 @@ async fn test_server_core( .send_result_loadfile(server_id, None) .await .unwrap(); + server_id += MESSAGE_ID_INCREMENT; // There's a second request for this file, made by the iframe, plus a // request for the TOC. The ordering isn't fixed; accommodate this. - server_id += MESSAGE_ID_INCREMENT; let msg = codechat_server.get_message_timeout(TIMEOUT).await.unwrap(); assert_eq!(msg.id, server_id); let msg_contents = cast!(msg.message, EditorMessageContents::LoadFile); @@ -823,6 +820,7 @@ async fn test_server_core( .send_result_loadfile(server_id, None) .await .unwrap(); + server_id += MESSAGE_ID_INCREMENT; // Look at the content, which should be an iframe. let plain_content = driver_ref @@ -855,7 +853,6 @@ async fn test_server_core( // Respond to the current file, then load requests for the PDf and the TOC. let pdf_path = canonicalize(test_dir.join("test.pdf")).unwrap(); let pdf_path_str = pdf_path.to_str().unwrap().to_string(); - client_id += MESSAGE_ID_INCREMENT; assert_eq!( codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), EditorMessage { @@ -864,7 +861,7 @@ async fn test_server_core( } ); codechat_server.send_result(client_id, None).await.unwrap(); - server_id += MESSAGE_ID_INCREMENT; + //client_id += MESSAGE_ID_INCREMENT; assert_eq!( codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), EditorMessage { @@ -901,6 +898,7 @@ async fn test_server_core( .send_result_loadfile(server_id, None) .await .unwrap(); + //server_id += MESSAGE_ID_INCREMENT; // Check that the PDF viewer was sent. // @@ -953,67 +951,13 @@ async fn test_client_core( driver_ref: &WebDriver, test_dir: PathBuf, ) -> Result<(), WebDriverError> { - let mut expected_messages = ExpectedMessages::new(); + let mut server_id = + perform_loadfile(&codechat_server, &test_dir, "test.py", None, true, 6.0).await; let path = canonicalize(test_dir.join("test.py")).unwrap(); let path_str = path.to_str().unwrap().to_string(); - let current_file_id = codechat_server - .send_message_current_file(path_str.clone()) - .await - .unwrap(); - // The ordering of these messages isn't fixed -- one can come first, or the - // other. - expected_messages.insert(EditorMessage { - id: current_file_id, - message: EditorMessageContents::Result(Ok(ResultOkTypes::Void)), - }); - let mut server_id = 6.0; - expected_messages.insert(EditorMessage { - id: server_id, - message: EditorMessageContents::LoadFile(path.clone()), - }); - expected_messages - .assert_all_messages(&codechat_server, TIMEOUT) - .await; - - // Respond to the load request. - codechat_server - .send_result_loadfile(server_id, None) - .await - .unwrap(); - - // Respond to the load request for the TOC. - let toc_path = canonicalize(test_dir.join("toc.md")).unwrap(); - server_id += MESSAGE_ID_INCREMENT * 2.0; - assert_eq!( - codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), - EditorMessage { - id: server_id, - message: EditorMessageContents::LoadFile(toc_path.clone()), - } - ); - codechat_server - .send_result_loadfile(server_id, None) - .await - .unwrap(); - - // The loadfile produces a message to the client, which comes back here. We - // don't need to acknowledge it. - server_id -= MESSAGE_ID_INCREMENT; - assert_eq!( - codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), - EditorMessage { - id: server_id, - message: EditorMessageContents::Result(Ok(ResultOkTypes::Void)) - } - ); // Target the iframe containing the Client. - let codechat_iframe = driver_ref.find(By::Css("#CodeChat-iframe")).await.unwrap(); - driver_ref - .switch_to() - .frame_element(&codechat_iframe) - .await - .unwrap(); + let codechat_iframe = select_codechat_iframe(driver_ref).await; // Click on the link for the PDF to test. let toc_iframe = driver_ref.find(By::Css("#CodeChat-sidebar")).await.unwrap(); @@ -1035,7 +979,6 @@ async fn test_client_core( } ); codechat_server.send_result(client_id, None).await.unwrap(); - server_id += MESSAGE_ID_INCREMENT * 2.0; assert_eq!( codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), EditorMessage { @@ -1048,6 +991,7 @@ async fn test_client_core( .await .unwrap(); server_id += MESSAGE_ID_INCREMENT * 2.0; + let toc_path = canonicalize(test_dir.join("toc.md")).unwrap(); assert_eq!( codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), EditorMessage { @@ -1102,83 +1046,33 @@ async fn test_client_updates_core( driver_ref: &WebDriver, test_dir: PathBuf, ) -> Result<(), WebDriverError> { - let mut expected_messages = ExpectedMessages::new(); - let path = canonicalize(test_dir.join("test.py")).unwrap(); - let path_str = path.to_str().unwrap().to_string(); - let current_file_id = codechat_server - .send_message_current_file(path_str.clone()) - .await - .unwrap(); - // The ordering of these messages isn't fixed -- one can come first, or the - // other. - expected_messages.insert(EditorMessage { - id: current_file_id, - message: EditorMessageContents::Result(Ok(ResultOkTypes::Void)), - }); - let mut server_id = 6.0; - expected_messages.insert(EditorMessage { - id: server_id, - message: EditorMessageContents::LoadFile(path.clone()), - }); - expected_messages - .assert_all_messages(&codechat_server, TIMEOUT) - .await; - - // Respond to the load request. let ide_version = 0.0; - codechat_server - .send_result_loadfile( - server_id, - Some(( - indoc!( - " + let server_id = perform_loadfile( + &codechat_server, + &test_dir, + "test.py", + Some(( + indoc!( + " # Test updates in the client that modify the client after appending to a line. def foo(): A comment print() " - ) - .to_string(), - ide_version, - )), - ) - .await - .unwrap(); + ) + .to_string(), + ide_version, + )), + true, + 6.0, + ) + .await; - // Respond to the load request for the TOC. - let toc_path = canonicalize(test_dir.join("toc.md")).unwrap(); - server_id += MESSAGE_ID_INCREMENT * 2.0; - assert_eq!( - codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), - EditorMessage { - id: server_id, - message: EditorMessageContents::LoadFile(toc_path.clone()), - } - ); - codechat_server - .send_result_loadfile(server_id, None) - .await - .unwrap(); - - // The loadfile produces a message to the client, which comes back here. We - // don't need to acknowledge it. - server_id -= MESSAGE_ID_INCREMENT; - assert_eq!( - codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), - EditorMessage { - id: server_id, - message: EditorMessageContents::Result(Ok(ResultOkTypes::Void)) - } - ); - server_id += MESSAGE_ID_INCREMENT * 2.0; + let path = canonicalize(test_dir.join("test.py")).unwrap(); + let path_str = path.to_str().unwrap().to_string(); // Target the iframe containing the Client. - let codechat_iframe = driver_ref.find(By::Css("#CodeChat-iframe")).await.unwrap(); - driver_ref - .switch_to() - .frame_element(&codechat_iframe) - .await - .unwrap(); + select_codechat_iframe(driver_ref).await; // Select the doc block and add to the line, causing a word wrap. let contents_css = ".CodeChat-CodeMirror .CodeChat-doc-contents"; @@ -1292,68 +1186,33 @@ async fn test_4_core( driver_ref: &WebDriver, test_dir: PathBuf, ) -> Result<(), WebDriverError> { - let mut expected_messages = ExpectedMessages::new(); let path = canonicalize(test_dir.join("test.py")).unwrap(); let path_str = path.to_str().unwrap().to_string(); - let current_file_id = codechat_server - .send_message_current_file(path_str.clone()) - .await - .unwrap(); - // The ordering of these messages isn't fixed -- one can come first, or the - // other. - expected_messages.insert(EditorMessage { - id: current_file_id, - message: EditorMessageContents::Result(Ok(ResultOkTypes::Void)), - }); - let mut server_id = 6.0; - expected_messages.insert(EditorMessage { - id: server_id, - message: EditorMessageContents::LoadFile(path.clone()), - }); - expected_messages - .assert_all_messages(&codechat_server, TIMEOUT) - .await; - - // Respond to the load request. let ide_version = 0.0; - codechat_server - .send_result_loadfile( - server_id, - Some(( - indoc!( - " + perform_loadfile( + &codechat_server, + &test_dir, + "test.py", + Some(( + indoc!( + " # 1 2 # 3 4 # 5 " - ) - .to_string(), - ide_version, - )), - ) - .await - .unwrap(); - server_id += MESSAGE_ID_INCREMENT; - - // The loadfile produces a message to the client, which comes back here. We - // don't need to acknowledge it. - assert_eq!( - codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), - EditorMessage { - id: server_id, - message: EditorMessageContents::Result(Ok(ResultOkTypes::Void)) - } - ); + ) + .to_string(), + ide_version, + )), + false, + 6.0, + ) + .await; // Target the iframe containing the Client. - let codechat_iframe = driver_ref.find(By::Css("#CodeChat-iframe")).await.unwrap(); - driver_ref - .switch_to() - .frame_element(&codechat_iframe) - .await - .unwrap(); + select_codechat_iframe(driver_ref).await; // Switch from one doc block to another. It should produce an update with only cursor/scroll info (no contents). let mut client_id = INITIAL_CLIENT_MESSAGE_ID; From 58fc80532e5d1e9819b9981b3f5828e720ec3691 Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Mon, 8 Dec 2025 13:08:17 -0600 Subject: [PATCH 12/27] Fix: Update tests with HTML processing changes. --- .../overall/overall_core/test_client/test.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/server/tests/fixtures/overall/overall_core/test_client/test.py b/server/tests/fixtures/overall/overall_core/test_client/test.py index 67d091a..37645ba 100644 --- a/server/tests/fixtures/overall/overall_core/test_client/test.py +++ b/server/tests/fixtures/overall/overall_core/test_client/test.py @@ -2,12 +2,21 @@ code() # Graphviz: # -# digraph { A -> B } +# ```graphviz +# digraph { +# A -> B +# } +# ``` # # Mermaid: # -# graph TD; A --> B; +# ```mermaid +# graph TD +# A --> B +# ``` # # MathJax: # # $x^2$ +# +# $$x^3$$ \ No newline at end of file From d3a82f11960aae1525511477061938ec3714b899 Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Mon, 8 Dec 2025 14:12:42 -0600 Subject: [PATCH 13/27] Refactor: use macros for boilerplate code. --- server/tests/overall_core/mod.rs | 96 +++++++++++--------------------- 1 file changed, 31 insertions(+), 65 deletions(-) diff --git a/server/tests/overall_core/mod.rs b/server/tests/overall_core/mod.rs index a93d273..9f87a44 100644 --- a/server/tests/overall_core/mod.rs +++ b/server/tests/overall_core/mod.rs @@ -152,7 +152,7 @@ macro_rules! harness { ($func: ident) => { pub async fn harness< 'a, - F: FnOnce(CodeChatEditorServer, &'a WebDriver, PathBuf) -> Fut, + F: FnOnce(CodeChatEditorServer, &'a WebDriver, &'a Path) -> Fut, Fut: Future>, >( // The function which performs tests using thirtyfour. TODO: not @@ -230,7 +230,7 @@ macro_rules! harness { // lifetime (which contains the state referring to // `driver_clone`) ends after the call to `f`, but don't know // how. - $func(codechat_server, driver_ref, test_dir).await?; + $func(codechat_server, driver_ref, &test_dir).await?; Ok(()) }) @@ -253,6 +253,22 @@ macro_rules! harness { }; } +macro_rules! make_test { + // The name of the test function to call inside the harness. + ($test_name: ident, $test_core_name: ident) => { + mod $test_name { + use super::*; + harness!($test_core_name); + } + + #[tokio::test] + async fn $test_name() -> Result<(), Box> { + $test_name::harness($test_core_name, prep_test_dir!()).await + } + + // Some of the thirtyfour calls are marked as deprecated, though they aren't + }; +} // Given an `Update` message with contents, get the version. fn get_version(msg: &EditorMessage) -> f64 { cast!(&msg.message, EditorMessageContents::Update) @@ -401,15 +417,7 @@ async fn select_codechat_iframe(driver_ref: &WebDriver) -> WebElement { // // Perform most functions a user would: open/switch documents (Markdown, // CodeChat, plain, PDF), use hyperlinks, perform edits on code and doc blocks. -mod test1 { - use super::*; - harness!(test_server_core); -} - -#[tokio::test] -async fn test_server() -> Result<(), Box> { - test1::harness(test_server_core, prep_test_dir!()).await -} +make_test!(test_server, test_server_core); // Some of the thirtyfour calls are marked as deprecated, though they aren't // marked that way in the Selenium docs. @@ -417,7 +425,7 @@ async fn test_server() -> Result<(), Box> { async fn test_server_core( codechat_server: CodeChatEditorServer, driver_ref: &WebDriver, - test_dir: PathBuf, + test_dir: &Path, ) -> Result<(), WebDriverError> { let mut expected_messages = ExpectedMessages::new(); let path = canonicalize(test_dir.join("test.py")).unwrap(); @@ -425,7 +433,7 @@ async fn test_server_core( let mut version = 1.0; let mut server_id = perform_loadfile( &codechat_server, - &test_dir, + test_dir, "test.py", Some(("# Test\ncode()".to_string(), version)), true, @@ -688,7 +696,7 @@ async fn test_server_core( let toc_path = canonicalize(test_dir.join("toc.md")).unwrap(); server_id = perform_loadfile( &codechat_server, - &test_dir, + test_dir, "test.md", Some(("A **markdown** file.".to_string(), version)), true, @@ -927,21 +935,7 @@ async fn test_server_core( // // This simply runs client-side tests written in TypeScript, verifying that they // all pass. -mod test2 { - use super::*; - harness!(test_client_core); -} - -#[tokio::test] -async fn test_client() -> Result<(), Box> { - // If both thirtyfour tests start at the same time, both fail; perhaps - // there's some confusion when two requests care made to the same webserver - // from two clients within the same process? In order to avoid then, insert - // a delay to hopefully start this test at a different time than - // `test_server_core`. - sleep(Duration::from_millis(100)).await; - test2::harness(test_client_core, prep_test_dir!()).await -} +make_test!(test_client, test_client_core); // Some of the thirtyfour calls are marked as deprecated, though they aren't // marked that way in the Selenium docs. @@ -949,10 +943,10 @@ async fn test_client() -> Result<(), Box> { async fn test_client_core( codechat_server: CodeChatEditorServer, driver_ref: &WebDriver, - test_dir: PathBuf, + test_dir: &Path, ) -> Result<(), WebDriverError> { let mut server_id = - perform_loadfile(&codechat_server, &test_dir, "test.py", None, true, 6.0).await; + perform_loadfile(&codechat_server, test_dir, "test.py", None, true, 6.0).await; let path = canonicalize(test_dir.join("test.py")).unwrap(); let path_str = path.to_str().unwrap().to_string(); @@ -1022,34 +1016,17 @@ async fn test_client_core( Ok(()) } -mod test3 { - use super::*; - harness!(test_client_updates_core); -} - -#[tokio::test] -async fn test_client_updates() -> Result<(), Box> { - // If both thirtyfour tests start at the same time, both fail; perhaps - // there's some confusion when two requests care made to the same webserver - // from two clients within the same process? In order to avoid then, insert - // a delay to hopefully start this test at a different time than - // `test_server_core`. - sleep(Duration::from_millis(100)).await; - test3::harness(test_client_updates_core, prep_test_dir!()).await -} +make_test!(test_client_updates, test_client_updates_core); -// Some of the thirtyfour calls are marked as deprecated, though they aren't -// marked that way in the Selenium docs. -#[allow(deprecated)] async fn test_client_updates_core( codechat_server: CodeChatEditorServer, driver_ref: &WebDriver, - test_dir: PathBuf, + test_dir: &Path, ) -> Result<(), WebDriverError> { let ide_version = 0.0; let server_id = perform_loadfile( &codechat_server, - &test_dir, + test_dir, "test.py", Some(( indoc!( @@ -1168,30 +1145,19 @@ async fn test_client_updates_core( Ok(()) } -mod test4 { - use super::*; - harness!(test_4_core); -} - -#[tokio::test] -async fn test_4() -> Result<(), Box> { - test4::harness(test_4_core, prep_test_dir!()).await -} +make_test!(test_4, test_4_core); -// Some of the thirtyfour calls are marked as deprecated, though they aren't -// marked that way in the Selenium docs. -#[allow(deprecated)] async fn test_4_core( codechat_server: CodeChatEditorServer, driver_ref: &WebDriver, - test_dir: PathBuf, + test_dir: &Path, ) -> Result<(), WebDriverError> { let path = canonicalize(test_dir.join("test.py")).unwrap(); let path_str = path.to_str().unwrap().to_string(); let ide_version = 0.0; perform_loadfile( &codechat_server, - &test_dir, + test_dir, "test.py", Some(( indoc!( From be77db8405dbba515473e4f70b2a4f3be0b331b4 Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Mon, 8 Dec 2025 22:40:15 -0600 Subject: [PATCH 14/27] Add: test for re-translation data corruption. --- .../overall/overall_core/test_5/test.py | 3 + server/tests/overall_core/mod.rs | 182 +++++++++++++++++- 2 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 server/tests/fixtures/overall/overall_core/test_5/test.py diff --git a/server/tests/fixtures/overall/overall_core/test_5/test.py b/server/tests/fixtures/overall/overall_core/test_5/test.py new file mode 100644 index 0000000..23103ab --- /dev/null +++ b/server/tests/fixtures/overall/overall_core/test_5/test.py @@ -0,0 +1,3 @@ +# The contents of this file don't matter -- tests will supply the content, +# instead of loading it from disk. However, it does need to exist for +# `canonicalize` to find the correct path to this file. diff --git a/server/tests/overall_core/mod.rs b/server/tests/overall_core/mod.rs index 9f87a44..cd79d35 100644 --- a/server/tests/overall_core/mod.rs +++ b/server/tests/overall_core/mod.rs @@ -390,9 +390,10 @@ async fn perform_loadfile( message: EditorMessageContents::Result(Ok(ResultOkTypes::Void)) } ); + server_id += MESSAGE_ID_INCREMENT; if has_toc { - server_id + 2.0 * MESSAGE_ID_INCREMENT + server_id + MESSAGE_ID_INCREMENT } else { server_id } @@ -1232,3 +1233,182 @@ async fn test_4_core( Ok(()) } + +make_test!(test_5, test_5_core); + +async fn test_5_core( + codechat_server: CodeChatEditorServer, + driver_ref: &WebDriver, + test_dir: &Path, +) -> Result<(), WebDriverError> { + let path = canonicalize(test_dir.join("test.py")).unwrap(); + let path_str = path.to_str().unwrap().to_string(); + let version = 0.0; + let orig_text = indoc!( + " + # Test. + # + # ```graphviz + # digraph g { + # A -> B + # } + # ``` + # + # Test. + test() + " + ) + .to_string(); + let server_id = perform_loadfile( + &codechat_server, + test_dir, + "test.py", + Some((orig_text.clone(), version)), + false, + 6.0, + ) + .await; + + // Target the iframe containing the Client. + select_codechat_iframe(driver_ref).await; + + // Focus it. + let contents_css = ".CodeChat-CodeMirror .CodeChat-doc-contents"; + let doc_block_contents = driver_ref.find(By::Css(contents_css)).await.unwrap(); + doc_block_contents.click().await.unwrap(); + // The click produces an updated cursor/scroll location after an autosave + // delay. + let mut client_id = INITIAL_CLIENT_MESSAGE_ID; + assert_eq!( + codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), + EditorMessage { + id: client_id, + message: EditorMessageContents::Update(UpdateMessageContents { + file_path: path_str.clone(), + contents: None, + cursor_position: Some(1), + scroll_position: Some(1.0) + }) + } + ); + client_id += MESSAGE_ID_INCREMENT; + + // Refind it, since it's now switched with a TinyMCE editor. + let tinymce_contents = driver_ref.find(By::Id("TinyMCE-inst")).await.unwrap(); + // Make an edit. + tinymce_contents.send_keys("foo").await.unwrap(); + + // Verify the updated text. + // + // Update the version from the value provided by the client, which varies + // randomly. + let msg = codechat_server.get_message_timeout(TIMEOUT).await.unwrap(); + let client_version = get_version(&msg); + assert_eq!( + msg, + EditorMessage { + id: client_id, + message: EditorMessageContents::Update(UpdateMessageContents { + file_path: path_str.clone(), + contents: Some(CodeChatForWeb { + metadata: SourceFileMetadata { + mode: "python".to_string(), + }, + source: CodeMirrorDiffable::Diff(CodeMirrorDiff { + doc: vec![ + StringDiff { + from: 0, + to: Some(8), + insert: "# fooTest.\n".to_string(), + }, + StringDiff { + from: 24, + to: Some(55,), + insert: "# digraph g { A -> B }\n".to_string(), + } + ], + doc_blocks: vec![], + version, + }), + version: client_version, + }), + cursor_position: Some(1), + scroll_position: Some(1.0) + }) + } + ); + let version = client_version; + codechat_server.send_result(client_id, None).await.unwrap(); + client_id += MESSAGE_ID_INCREMENT; + + // The Server sends the Client a wrapped version of the text; the Client + // replies with a Result(Ok). + assert_eq!( + codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), + EditorMessage { + id: server_id, + message: EditorMessageContents::Result(Ok(ResultOkTypes::Void)) + } + ); + //server_id += MESSAGE_ID_INCREMENT; + + // Send new text, which turns into a diff. + let ide_id = codechat_server + .send_message_update_plain(path_str.clone(), Some((orig_text, version)), Some(1), None) + .await + .unwrap(); + assert_eq!( + codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), + EditorMessage { + id: ide_id, + message: EditorMessageContents::Result(Ok(ResultOkTypes::Void)) + } + ); + + // Make another edit, to push any corrupted text back. + tinymce_contents.send_keys("bar").await.unwrap(); + // Verify the updated text. + // + // Update the version from the value provided by the client, which varies + // randomly. + let msg = codechat_server.get_message_timeout(TIMEOUT).await.unwrap(); + let client_version = get_version(&msg); + assert_eq!( + msg, + EditorMessage { + id: client_id, + message: EditorMessageContents::Update(UpdateMessageContents { + file_path: path_str.clone(), + contents: Some(CodeChatForWeb { + metadata: SourceFileMetadata { + mode: "python".to_string(), + }, + source: CodeMirrorDiffable::Diff(CodeMirrorDiff { + doc: vec![ + StringDiff { + from: 0, + to: Some(8), + insert: "# Tesbart.\n".to_string(), + }, + StringDiff { + from: 24, + to: Some(55,), + insert: "# digraph g { A -> B }\n".to_string(), + } + ], + doc_blocks: vec![], + version, + }), + version: client_version, + }), + cursor_position: Some(1), + scroll_position: Some(1.0) + }) + } + ); + //let version = client_version; + codechat_server.send_result(client_id, None).await.unwrap(); + //client_id += MESSAGE_ID_INCREMENT; + + Ok(()) +} From 74d0b4c44af1ef09975c02799c76093189fbc29c Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Mon, 8 Dec 2025 22:39:50 -0600 Subject: [PATCH 15/27] Fix: used updated htmd to support math. --- server/Cargo.toml | 2 +- server/src/processing/tests.rs | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/server/Cargo.toml b/server/Cargo.toml index c947339..68739d8 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -75,7 +75,7 @@ dunce = "1.0.5" # This is also for integration testing. futures = { version = "0.3.31", optional = true } futures-util = "0.3.29" -htmd = { version = "0.5" } +htmd = { git = "https://github.com/bjones1/htmd.git", branch = "math-support", version = "0.5" } html5ever = "0.36.1" imara-diff = { version = "0.2", features = [] } indoc = "2.0.5" diff --git a/server/src/processing/tests.rs b/server/src/processing/tests.rs index 2fa9e82..653ebf2 100644 --- a/server/src/processing/tests.rs +++ b/server/src/processing/tests.rs @@ -1374,11 +1374,7 @@ fn test_dehydrate_html_1() { .unwrap(), indoc!( " - ${a}_1, b_{2}$ - $a*1, b*2$ - $[a](b)$ - $3
b$ - $a \\; b$ + ${a}_1, b_{2}$ $a*1, b*2$ $[a](b)$ $3 b$ $a \\; b$ $${a}_1, b_{2}, a*1, b*2, [a](b), 3 b, a \\; b$$ " From 1259bf0e9fa07d5cac1b5beae968c83b1db88ae5 Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Mon, 8 Dec 2025 22:40:30 -0600 Subject: [PATCH 16/27] Clean: cargo fmt. --- server/src/processing.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/src/processing.rs b/server/src/processing.rs index a7f5bab..139d78d 100644 --- a/server/src/processing.rs +++ b/server/src/processing.rs @@ -1139,7 +1139,9 @@ fn replace_math_node(child: &Rc, is_hydrate: bool) -> Option> { // Since we've already borrowed `child`, we can't `borrow_mut` to modify it. Instead, create a new `span` with delimited text and return that. if let Some(delim) = delim { let contents_str = &*contents.borrow(); - let delimited_text_str = if is_hydrate { format!("{}{}{}", delim.0, contents_str, delim.1) } else { + let delimited_text_str = if is_hydrate { + format!("{}{}{}", delim.0, contents_str, delim.1) + } else { // Only apply the dehydration is the delimiters are correct. if !contents_str.starts_with(delim.0) || !contents_str.ends_with(delim.1) { return None; From 3de541da7d40cf245b53dbc7013cfe64b28a180b Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Tue, 9 Dec 2025 00:37:57 -0600 Subject: [PATCH 17/27] Fix: update patches to prevent whitespace removal in Graphviz graphs. Stop copying pre-compiled Graphviz renderer. --- builder/src/main.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/builder/src/main.rs b/builder/src/main.rs index 9515266..8c67225 100644 --- a/builder/src/main.rs +++ b/builder/src/main.rs @@ -326,7 +326,7 @@ fn patch_client_libs() -> io::Result<()> { // inside these isn't removed by TinyMCE. However, this was removed in v6.0. // Therefore, manually patch TinyMCE instead. patch_file( - " wc-mermaid", + " wc-mermaid graphviz-graph", "const whitespaceElementsMap = createLookupTable('whitespace_elements', 'pre script noscript style textarea video audio iframe object code", &format!("{CLIENT_PATH}/node_modules/tinymce/tinymce.js"), )?; @@ -351,12 +351,6 @@ fn patch_client_libs() -> io::Result<()> { format!("{CLIENT_PATH}/static/mathjax-newcm-font/chtml"), None, )?; - // Copy over the graphviz files needed. - quick_copy_dir( - format!("{CLIENT_PATH}/node_modules/graphviz-webcomponent/dist/"), - format!("{CLIENT_PATH}/static/graphviz-webcomponent"), - Some("renderer.min.js*".to_string()), - )?; Ok(()) } From c1b64698a3474aee78930166be0750471a830117 Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Tue, 9 Dec 2025 00:41:45 -0600 Subject: [PATCH 18/27] Freeze for release. --- CHANGELOG.md | 1 + README.md | 16 +- client/package.json5 | 10 +- client/pnpm-lock.yaml | 306 ++++++++++++++--------------- extensions/VSCode/Cargo.toml | 2 +- extensions/VSCode/package.json | 8 +- extensions/VSCode/pnpm-lock.yaml | 320 +++++++++++++++---------------- server/Cargo.lock | 155 ++++++++++++--- server/Cargo.toml | 2 +- 9 files changed, 454 insertions(+), 366 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b556ea2..079306b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ Changelog this release. * Fix failures when trying to edit a doc block which contains only diagrams. * Fix data corruption bug after sending a re-translation back to the Client. +* Correct incorrect whitespace removal in Graphviz and Mermaid diagrams. Version 0.1.43 -- 2025-Dec-05 -------------------------------------------------------------------------------- diff --git a/README.md b/README.md index a6f5086..d9d3596 100644 --- a/README.md +++ b/README.md @@ -111,14 +111,14 @@ Mathematics -------------------------------------------------------------------------------- The CodeChat Editor uses [MathJax](https://www.mathjax.org/) to support typeset -mathematics. Place the delimiters `$` immediately before and -after in-line mathematics; place `$$` immediately before -and after displayed mathematics. For example, - -| Source | Rendered | -| --------------------------------------------- | ------------------------------------------- | -| `$x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$` | $x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$ | -| `$$a^2$$` | $$a^2$$ | +mathematics. Place the delimiters `$` immediately before and after in-line +mathematics; place `$$` immediately before and after displayed mathematics. For +example, + +| Source | Rendered | +| ------------------------------------------ | ---------------------------------------- | +| `$x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$` | $x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$ | +| `$$a^2$$` | $$a^2$$ | See [Latex Mathematics](https://en.wikibooks.org/wiki/LaTeX/Mathematics#Symbols) for the syntax used to write mathematics expressions. diff --git a/client/package.json5 b/client/package.json5 index d483a03..ef90c5f 100644 --- a/client/package.json5 +++ b/client/package.json5 @@ -43,7 +43,7 @@ url: 'https://github.com/bjones1/CodeChat_editor', }, type: 'module', - version: '0.1.43', + version: '0.1.44', dependencies: { '@codemirror/commands': '^6.10.0', '@codemirror/lang-cpp': '^6.0.3', @@ -61,7 +61,7 @@ '@codemirror/lang-xml': '^6.1.0', '@codemirror/lang-yaml': '^6.1.2', '@codemirror/state': '^6.5.2', - '@codemirror/view': '^6.38.8', + '@codemirror/view': '^6.39.0', '@hpcc-js/wasm-graphviz': '^1.16.0', '@mathjax/mathjax-newcm-font': '4.0.0', codemirror: '^6.0.2', @@ -76,10 +76,10 @@ '@types/chai': '^5.2.3', '@types/js-beautify': '^1.14.3', '@types/mocha': '^10.0.10', - '@types/node': '^24.10.1', + '@types/node': '^24.10.2', '@types/toastify-js': '^1.12.4', - '@typescript-eslint/eslint-plugin': '^8.48.1', - '@typescript-eslint/parser': '^8.48.1', + '@typescript-eslint/eslint-plugin': '^8.49.0', + '@typescript-eslint/parser': '^8.49.0', chai: '^6.2.1', esbuild: '^0.27.1', eslint: '^9.39.1', diff --git a/client/pnpm-lock.yaml b/client/pnpm-lock.yaml index b54152e..10ede82 100644 --- a/client/pnpm-lock.yaml +++ b/client/pnpm-lock.yaml @@ -57,17 +57,17 @@ importers: specifier: ^6.5.2 version: 6.5.2 '@codemirror/view': - specifier: ^6.38.8 - version: 6.38.8 + specifier: ^6.39.0 + version: 6.39.0 + '@hpcc-js/wasm-graphviz': + specifier: ^1.16.0 + version: 1.16.0 '@mathjax/mathjax-newcm-font': specifier: 4.0.0 version: 4.0.0 codemirror: specifier: ^6.0.2 version: 6.0.2 - graphviz-webcomponent: - specifier: github:bjones1/graphviz-webcomponent#dist - version: https://codeload.github.com/bjones1/graphviz-webcomponent/tar.gz/6dc598f65f2ccca1038e6489b00d4d604e5644c7 mathjax: specifier: 4.0.0 version: 4.0.0 @@ -97,17 +97,17 @@ importers: specifier: ^10.0.10 version: 10.0.10 '@types/node': - specifier: ^24.10.1 - version: 24.10.1 + specifier: ^24.10.2 + version: 24.10.2 '@types/toastify-js': specifier: ^1.12.4 version: 1.12.4 '@typescript-eslint/eslint-plugin': - specifier: ^8.48.1 - version: 8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) + specifier: ^8.49.0 + version: 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) '@typescript-eslint/parser': - specifier: ^8.48.1 - version: 8.48.1(eslint@9.39.1)(typescript@5.9.3) + specifier: ^8.49.0 + version: 8.49.0(eslint@9.39.1)(typescript@5.9.3) chai: specifier: ^6.2.1 version: 6.2.1 @@ -122,7 +122,7 @@ importers: version: 10.1.8(eslint@9.39.1) eslint-plugin-import: specifier: ^2.32.0 - version: 2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1) + version: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1) eslint-plugin-prettier: specifier: ^5.5.4 version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.39.1))(eslint@9.39.1)(prettier@3.7.4) @@ -219,8 +219,8 @@ packages: '@codemirror/state@6.5.2': resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==} - '@codemirror/view@6.38.8': - resolution: {integrity: sha512-XcE9fcnkHCbWkjeKyi0lllwXmBLtyYb5dt89dJyx23I9+LSh5vZDIuk7OLG4VM1lgrXZQcY6cxyZyk5WVPRv/A==} + '@codemirror/view@6.39.0': + resolution: {integrity: sha512-pn7UA5RDNLFpdM4PTyqwb1qQ/hQ3brwUKYAlJGrg3972VHJotgXrVBdBAWcbMkOjERXX609fmqfRldnGkC96kw==} '@esbuild/aix-ppc64@0.27.1': resolution: {integrity: sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==} @@ -416,6 +416,9 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@hpcc-js/wasm-graphviz@1.16.0': + resolution: {integrity: sha512-N7IcFIF6Va9Ko3DT5+N3crI5AHE71BH/H+frxfqgo7fn2LxzGaWDHPSRUsLfCCt3cpAFqsdkyoIc//FA+HxKzw==} + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -472,8 +475,8 @@ packages: '@lezer/lr@1.4.4': resolution: {integrity: sha512-LHL17Mq0OcFXm1pGQssuGTQFPPdxARjKM8f7GA5+sGtHi0K3R84YaSbmche0+RKWHnCsx9asEe5OWOI4FHfe4A==} - '@lezer/markdown@1.6.0': - resolution: {integrity: sha512-AXb98u3M6BEzTnreBnGtQaF7xFTiMA92Dsy5tqEjpacbjRxDSFdN4bKJo9uvU4cEEOS7D2B9MT7kvDgOEIzJSw==} + '@lezer/markdown@1.6.1': + resolution: {integrity: sha512-72ah+Sml7lD8Wn7lnz9vwYmZBo9aQT+I2gjK/0epI+gjdwUbWw3MJ/ZBGEqG1UfrIauRqH37/c5mVHXeCTGXtA==} '@lezer/php@1.0.5': resolution: {integrity: sha512-W7asp9DhM6q0W6DYNwIkLSKOvxlXRrif+UXBMxzsJUuqmhE7oVU+gS3THO4S/Puh7Xzgm858UNaFi6dxTP8dJA==} @@ -499,68 +502,68 @@ packages: '@mermaid-js/parser@0.6.3': resolution: {integrity: sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==} - '@napi-rs/canvas-android-arm64@0.1.83': - resolution: {integrity: sha512-TbKM2fh9zXjqFIU8bgMfzG7rkrIYdLKMafgPhFoPwKrpWk1glGbWP7LEu8Y/WrMDqTGFdRqUmuX89yQEzZbkiw==} + '@napi-rs/canvas-android-arm64@0.1.84': + resolution: {integrity: sha512-pdvuqvj3qtwVryqgpAGornJLV6Ezpk39V6wT4JCnRVGy8I3Tk1au8qOalFGrx/r0Ig87hWslysPpHBxVpBMIww==} engines: {node: '>= 10'} cpu: [arm64] os: [android] - '@napi-rs/canvas-darwin-arm64@0.1.83': - resolution: {integrity: sha512-gp8IDVUloPUmkepHly4xRUOfUJSFNvA4jR7ZRF5nk3YcGzegSFGeICiT4PnYyPgSKEhYAFe1Y2XNy0Mp6Tu8mQ==} + '@napi-rs/canvas-darwin-arm64@0.1.84': + resolution: {integrity: sha512-A8IND3Hnv0R6abc6qCcCaOCujTLMmGxtucMTZ5vbQUrEN/scxi378MyTLtyWg+MRr6bwQJ6v/orqMS9datIcww==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@napi-rs/canvas-darwin-x64@0.1.83': - resolution: {integrity: sha512-r4ZJxiP9OgUbdGZhPDEXD3hQ0aIPcVaywtcTXvamYxTU/SWKAbKVhFNTtpRe1J30oQ25gWyxTkUKSBgUkNzdnw==} + '@napi-rs/canvas-darwin-x64@0.1.84': + resolution: {integrity: sha512-AUW45lJhYWwnA74LaNeqhvqYKK/2hNnBBBl03KRdqeCD4tKneUSrxUqIv8d22CBweOvrAASyKN3W87WO2zEr/A==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@napi-rs/canvas-linux-arm-gnueabihf@0.1.83': - resolution: {integrity: sha512-Uc6aSB05qH1r+9GUDxIE6F5ZF7L0nTFyyzq8ublWUZhw8fEGK8iy931ff1ByGFT04+xHJad1kBcL4R1ZEV8z7Q==} + '@napi-rs/canvas-linux-arm-gnueabihf@0.1.84': + resolution: {integrity: sha512-8zs5ZqOrdgs4FioTxSBrkl/wHZB56bJNBqaIsfPL4ZkEQCinOkrFF7xIcXiHiKp93J3wUtbIzeVrhTIaWwqk+A==} engines: {node: '>= 10'} cpu: [arm] os: [linux] - '@napi-rs/canvas-linux-arm64-gnu@0.1.83': - resolution: {integrity: sha512-eEeaJA7V5KOFq7W0GtoRVbd3ak8UZpK+XLkCgUiFGtlunNw+ZZW9Cr/92MXflGe7o3SqqMUg+f975LPxO/vsOQ==} + '@napi-rs/canvas-linux-arm64-gnu@0.1.84': + resolution: {integrity: sha512-i204vtowOglJUpbAFWU5mqsJgH0lVpNk/Ml4mQtB4Lndd86oF+Otr6Mr5KQnZHqYGhlSIKiU2SYnUbhO28zGQA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@napi-rs/canvas-linux-arm64-musl@0.1.83': - resolution: {integrity: sha512-cAvonp5XpbatVGegF9lMQNchs3z5RH6EtamRVnQvtoRtwbzOMcdzwuLBqDBQxQF79MFbuZNkWj3YRJjZCjHVzw==} + '@napi-rs/canvas-linux-arm64-musl@0.1.84': + resolution: {integrity: sha512-VyZq0EEw+OILnWk7G3ZgLLPaz1ERaPP++jLjeyLMbFOF+Tr4zHzWKiKDsEV/cT7btLPZbVoR3VX+T9/QubnURQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@napi-rs/canvas-linux-riscv64-gnu@0.1.83': - resolution: {integrity: sha512-WFUPQ9qZy31vmLxIJ3MfmHw+R2g/mLCgk8zmh7maJW8snV3vLPA7pZfIS65Dc61EVDp1vaBskwQ2RqPPzwkaew==} + '@napi-rs/canvas-linux-riscv64-gnu@0.1.84': + resolution: {integrity: sha512-PSMTh8DiThvLRsbtc/a065I/ceZk17EXAATv9uNvHgkgo7wdEfTh2C3aveNkBMGByVO3tvnvD5v/YFtZL07cIg==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] - '@napi-rs/canvas-linux-x64-gnu@0.1.83': - resolution: {integrity: sha512-X9YwIjsuy50WwOyYeNhEHjKHO8rrfH9M4U8vNqLuGmqsZdKua/GrUhdQGdjq7lTgdY3g4+Ta5jF8MzAa7UAs/g==} + '@napi-rs/canvas-linux-x64-gnu@0.1.84': + resolution: {integrity: sha512-N1GY3noO1oqgEo3rYQIwY44kfM11vA0lDbN0orTOHfCSUZTUyiYCY0nZ197QMahZBm1aR/vYgsWpV74MMMDuNA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@napi-rs/canvas-linux-x64-musl@0.1.83': - resolution: {integrity: sha512-Vv2pLWQS8EnlSM1bstJ7vVhKA+mL4+my4sKUIn/bgIxB5O90dqiDhQjUDLP+5xn9ZMestRWDt3tdQEkGAmzq/A==} + '@napi-rs/canvas-linux-x64-musl@0.1.84': + resolution: {integrity: sha512-vUZmua6ADqTWyHyei81aXIt9wp0yjeNwTH0KdhdeoBb6azHmFR8uKTukZMXfLCC3bnsW0t4lW7K78KNMknmtjg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@napi-rs/canvas-win32-x64-msvc@0.1.83': - resolution: {integrity: sha512-K1TtjbScfRNYhq8dengLLufXGbtEtWdUXPV505uLFPovyGHzDUGXLFP/zUJzj6xWXwgUjHNLgEPIt7mye0zr6Q==} + '@napi-rs/canvas-win32-x64-msvc@0.1.84': + resolution: {integrity: sha512-YSs8ncurc1xzegUMNnQUTYrdrAuaXdPMOa+iYYyAxydOtg0ppV386hyYMsy00Yip1NlTgLCseRG4sHSnjQx6og==} engines: {node: '>= 10'} cpu: [x64] os: [win32] - '@napi-rs/canvas@0.1.83': - resolution: {integrity: sha512-f9GVB9VNc9vn/nroc9epXRNkVpvNPZh69+qzLJIm9DfruxFqX0/jsXG46OGWAJgkO4mN0HvFHjRROMXKVmPszg==} + '@napi-rs/canvas@0.1.84': + resolution: {integrity: sha512-88FTNFs4uuiFKP0tUrPsEXhpe9dg7za9ILZJE08pGdUveMIDeana1zwfVkqRHJDPJFAmGY3dXmJ99dzsy57YnA==} engines: {node: '>= 10'} '@pkgjs/parseargs@0.11.0': @@ -691,8 +694,8 @@ packages: '@types/mocha@10.0.10': resolution: {integrity: sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==} - '@types/node@24.10.1': - resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} + '@types/node@24.10.2': + resolution: {integrity: sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA==} '@types/toastify-js@1.12.4': resolution: {integrity: sha512-zfZHU4tKffPCnZRe7pjv/eFKzTVHozKewFCKaCjZ4gFinKgJRz/t0bkZiMCXJxPhv/ZoeDGNOeRD09R0kQZ/nw==} @@ -700,63 +703,63 @@ packages: '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} - '@typescript-eslint/eslint-plugin@8.48.1': - resolution: {integrity: sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA==} + '@typescript-eslint/eslint-plugin@8.49.0': + resolution: {integrity: sha512-JXij0vzIaTtCwu6SxTh8qBc66kmf1xs7pI4UOiMDFVct6q86G0Zs7KRcEoJgY3Cav3x5Tq0MF5jwgpgLqgKG3A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.48.1 + '@typescript-eslint/parser': ^8.49.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.48.1': - resolution: {integrity: sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==} + '@typescript-eslint/parser@8.49.0': + resolution: {integrity: sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.48.1': - resolution: {integrity: sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w==} + '@typescript-eslint/project-service@8.49.0': + resolution: {integrity: sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.48.1': - resolution: {integrity: sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w==} + '@typescript-eslint/scope-manager@8.49.0': + resolution: {integrity: sha512-npgS3zi+/30KSOkXNs0LQXtsg9ekZ8OISAOLGWA/ZOEn0ZH74Ginfl7foziV8DT+D98WfQ5Kopwqb/PZOaIJGg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.48.1': - resolution: {integrity: sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw==} + '@typescript-eslint/tsconfig-utils@8.49.0': + resolution: {integrity: sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.48.1': - resolution: {integrity: sha512-1jEop81a3LrJQLTf/1VfPQdhIY4PlGDBc/i67EVWObrtvcziysbLN3oReexHOM6N3jyXgCrkBsZpqwH0hiDOQg==} + '@typescript-eslint/type-utils@8.49.0': + resolution: {integrity: sha512-KTExJfQ+svY8I10P4HdxKzWsvtVnsuCifU5MvXrRwoP2KOlNZ9ADNEWWsQTJgMxLzS5VLQKDjkCT/YzgsnqmZg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.48.1': - resolution: {integrity: sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q==} + '@typescript-eslint/types@8.49.0': + resolution: {integrity: sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.48.1': - resolution: {integrity: sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg==} + '@typescript-eslint/typescript-estree@8.49.0': + resolution: {integrity: sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.48.1': - resolution: {integrity: sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA==} + '@typescript-eslint/utils@8.49.0': + resolution: {integrity: sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.48.1': - resolution: {integrity: sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q==} + '@typescript-eslint/visitor-keys@8.49.0': + resolution: {integrity: sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} acorn-jsx@5.3.2: @@ -1134,8 +1137,8 @@ packages: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} - dompurify@3.3.0: - resolution: {integrity: sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==} + dompurify@3.3.1: + resolution: {integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==} dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} @@ -1385,14 +1388,6 @@ packages: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} - graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - - graphviz-webcomponent@https://codeload.github.com/bjones1/graphviz-webcomponent/tar.gz/6dc598f65f2ccca1038e6489b00d4d604e5644c7: - resolution: {tarball: https://codeload.github.com/bjones1/graphviz-webcomponent/tar.gz/6dc598f65f2ccca1038e6489b00d4d604e5644c7} - version: 2.0.0 - engines: {node: '>=10'} - hachure-fill@0.5.2: resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} @@ -1596,8 +1591,8 @@ packages: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true - katex@0.16.25: - resolution: {integrity: sha512-woHRUZ/iF23GBP1dkDQMh1QBad9dmr8/PAwNA54VrSOVYgI12MAcE14TqnDdQOdzyEonGzMepYnqBMYdsoAr8Q==} + katex@0.16.27: + resolution: {integrity: sha512-aeQoDkuRWSqQN6nSvVCEFvfXdqo1OQiCmmW1kc9xSdjutPv7BGO7pqY9sQRJpMOGrEdfDgF2TfRXe5eUAD2Waw==} hasBin: true keyv@4.5.4: @@ -2144,14 +2139,14 @@ snapshots: dependencies: '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 - '@codemirror/view': 6.38.8 + '@codemirror/view': 6.39.0 '@lezer/common': 1.4.0 '@codemirror/commands@6.10.0': dependencies: '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 - '@codemirror/view': 6.38.8 + '@codemirror/view': 6.39.0 '@lezer/common': 1.4.0 '@codemirror/lang-cpp@6.0.3': @@ -2182,7 +2177,7 @@ snapshots: '@codemirror/lang-javascript': 6.2.4 '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 - '@codemirror/view': 6.38.8 + '@codemirror/view': 6.39.0 '@lezer/common': 1.4.0 '@lezer/css': 1.3.0 '@lezer/html': 1.3.12 @@ -2198,7 +2193,7 @@ snapshots: '@codemirror/language': 6.11.3 '@codemirror/lint': 6.9.2 '@codemirror/state': 6.5.2 - '@codemirror/view': 6.38.8 + '@codemirror/view': 6.39.0 '@lezer/common': 1.4.0 '@lezer/javascript': 1.5.4 @@ -2213,9 +2208,9 @@ snapshots: '@codemirror/lang-html': 6.4.11 '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 - '@codemirror/view': 6.38.8 + '@codemirror/view': 6.39.0 '@lezer/common': 1.4.0 - '@lezer/markdown': 1.6.0 + '@lezer/markdown': 1.6.1 '@codemirror/lang-php@6.0.2': dependencies: @@ -2252,7 +2247,7 @@ snapshots: '@codemirror/autocomplete': 6.20.0 '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 - '@codemirror/view': 6.38.8 + '@codemirror/view': 6.39.0 '@lezer/common': 1.4.0 '@lezer/xml': 1.0.6 @@ -2269,7 +2264,7 @@ snapshots: '@codemirror/language@6.11.3': dependencies: '@codemirror/state': 6.5.2 - '@codemirror/view': 6.38.8 + '@codemirror/view': 6.39.0 '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 '@lezer/lr': 1.4.4 @@ -2278,20 +2273,20 @@ snapshots: '@codemirror/lint@6.9.2': dependencies: '@codemirror/state': 6.5.2 - '@codemirror/view': 6.38.8 + '@codemirror/view': 6.39.0 crelt: 1.0.6 '@codemirror/search@6.5.11': dependencies: '@codemirror/state': 6.5.2 - '@codemirror/view': 6.38.8 + '@codemirror/view': 6.39.0 crelt: 1.0.6 '@codemirror/state@6.5.2': dependencies: '@marijn/find-cluster-break': 1.0.2 - '@codemirror/view@6.38.8': + '@codemirror/view@6.39.0': dependencies: '@codemirror/state': 6.5.2 crelt: 1.0.6 @@ -2422,6 +2417,8 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 + '@hpcc-js/wasm-graphviz@1.16.0': {} + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': @@ -2502,7 +2499,7 @@ snapshots: dependencies: '@lezer/common': 1.4.0 - '@lezer/markdown@1.6.0': + '@lezer/markdown@1.6.1': dependencies: '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 @@ -2545,48 +2542,48 @@ snapshots: dependencies: langium: 3.3.1 - '@napi-rs/canvas-android-arm64@0.1.83': + '@napi-rs/canvas-android-arm64@0.1.84': optional: true - '@napi-rs/canvas-darwin-arm64@0.1.83': + '@napi-rs/canvas-darwin-arm64@0.1.84': optional: true - '@napi-rs/canvas-darwin-x64@0.1.83': + '@napi-rs/canvas-darwin-x64@0.1.84': optional: true - '@napi-rs/canvas-linux-arm-gnueabihf@0.1.83': + '@napi-rs/canvas-linux-arm-gnueabihf@0.1.84': optional: true - '@napi-rs/canvas-linux-arm64-gnu@0.1.83': + '@napi-rs/canvas-linux-arm64-gnu@0.1.84': optional: true - '@napi-rs/canvas-linux-arm64-musl@0.1.83': + '@napi-rs/canvas-linux-arm64-musl@0.1.84': optional: true - '@napi-rs/canvas-linux-riscv64-gnu@0.1.83': + '@napi-rs/canvas-linux-riscv64-gnu@0.1.84': optional: true - '@napi-rs/canvas-linux-x64-gnu@0.1.83': + '@napi-rs/canvas-linux-x64-gnu@0.1.84': optional: true - '@napi-rs/canvas-linux-x64-musl@0.1.83': + '@napi-rs/canvas-linux-x64-musl@0.1.84': optional: true - '@napi-rs/canvas-win32-x64-msvc@0.1.83': + '@napi-rs/canvas-win32-x64-msvc@0.1.84': optional: true - '@napi-rs/canvas@0.1.83': + '@napi-rs/canvas@0.1.84': optionalDependencies: - '@napi-rs/canvas-android-arm64': 0.1.83 - '@napi-rs/canvas-darwin-arm64': 0.1.83 - '@napi-rs/canvas-darwin-x64': 0.1.83 - '@napi-rs/canvas-linux-arm-gnueabihf': 0.1.83 - '@napi-rs/canvas-linux-arm64-gnu': 0.1.83 - '@napi-rs/canvas-linux-arm64-musl': 0.1.83 - '@napi-rs/canvas-linux-riscv64-gnu': 0.1.83 - '@napi-rs/canvas-linux-x64-gnu': 0.1.83 - '@napi-rs/canvas-linux-x64-musl': 0.1.83 - '@napi-rs/canvas-win32-x64-msvc': 0.1.83 + '@napi-rs/canvas-android-arm64': 0.1.84 + '@napi-rs/canvas-darwin-arm64': 0.1.84 + '@napi-rs/canvas-darwin-x64': 0.1.84 + '@napi-rs/canvas-linux-arm-gnueabihf': 0.1.84 + '@napi-rs/canvas-linux-arm64-gnu': 0.1.84 + '@napi-rs/canvas-linux-arm64-musl': 0.1.84 + '@napi-rs/canvas-linux-riscv64-gnu': 0.1.84 + '@napi-rs/canvas-linux-x64-gnu': 0.1.84 + '@napi-rs/canvas-linux-x64-musl': 0.1.84 + '@napi-rs/canvas-win32-x64-msvc': 0.1.84 optional: true '@pkgjs/parseargs@0.11.0': @@ -2732,7 +2729,7 @@ snapshots: '@types/mocha@10.0.10': {} - '@types/node@24.10.1': + '@types/node@24.10.2': dependencies: undici-types: 7.16.0 @@ -2741,16 +2738,15 @@ snapshots: '@types/trusted-types@2.0.7': optional: true - '@typescript-eslint/eslint-plugin@8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.48.1(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.48.1 - '@typescript-eslint/type-utils': 8.48.1(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/utils': 8.48.1(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.48.1 + '@typescript-eslint/parser': 8.49.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.49.0 eslint: 9.39.1 - graphemer: 1.4.0 ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.1.0(typescript@5.9.3) @@ -2758,41 +2754,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.48.1 - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.48.1 + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.49.0 debug: 4.4.3(supports-color@8.1.1) eslint: 9.39.1 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.48.1(typescript@5.9.3)': + '@typescript-eslint/project-service@8.49.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.48.1(typescript@5.9.3) - '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.9.3) + '@typescript-eslint/types': 8.49.0 debug: 4.4.3(supports-color@8.1.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.48.1': + '@typescript-eslint/scope-manager@8.49.0': dependencies: - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/visitor-keys': 8.48.1 + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/visitor-keys': 8.49.0 - '@typescript-eslint/tsconfig-utils@8.48.1(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.49.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.48.1(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.49.0(eslint@9.39.1)(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) - '@typescript-eslint/utils': 8.48.1(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.1)(typescript@5.9.3) debug: 4.4.3(supports-color@8.1.1) eslint: 9.39.1 ts-api-utils: 2.1.0(typescript@5.9.3) @@ -2800,14 +2796,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.48.1': {} + '@typescript-eslint/types@8.49.0': {} - '@typescript-eslint/typescript-estree@8.48.1(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.49.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.48.1(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.48.1(typescript@5.9.3) - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/visitor-keys': 8.48.1 + '@typescript-eslint/project-service': 8.49.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.9.3) + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/visitor-keys': 8.49.0 debug: 4.4.3(supports-color@8.1.1) minimatch: 9.0.5 semver: 7.7.3 @@ -2817,20 +2813,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.48.1(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/utils@8.49.0(eslint@9.39.1)(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) - '@typescript-eslint/scope-manager': 8.48.1 - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) eslint: 9.39.1 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.48.1': + '@typescript-eslint/visitor-keys@8.49.0': dependencies: - '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/types': 8.49.0 eslint-visitor-keys: 4.2.1 acorn-jsx@5.3.2(acorn@8.15.0): @@ -2989,7 +2985,7 @@ snapshots: '@codemirror/lint': 6.9.2 '@codemirror/search': 6.5.11 '@codemirror/state': 6.5.2 - '@codemirror/view': 6.38.8 + '@codemirror/view': 6.39.0 color-convert@2.0.1: dependencies: @@ -3261,7 +3257,7 @@ snapshots: dependencies: esutils: 2.0.3 - dompurify@3.3.0: + dompurify@3.3.1: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -3404,17 +3400,17 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.48.1(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.49.0(eslint@9.39.1)(typescript@5.9.3) eslint: 9.39.1 eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -3425,7 +3421,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -3437,7 +3433,7 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.48.1(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.49.0(eslint@9.39.1)(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -3620,10 +3616,6 @@ snapshots: gopd@1.2.0: {} - graphemer@1.4.0: {} - - graphviz-webcomponent@https://codeload.github.com/bjones1/graphviz-webcomponent/tar.gz/6dc598f65f2ccca1038e6489b00d4d604e5644c7: {} - hachure-fill@0.5.2: {} has-bigints@1.1.0: {} @@ -3813,7 +3805,7 @@ snapshots: dependencies: minimist: 1.2.8 - katex@0.16.25: + katex@0.16.27: dependencies: commander: 8.3.0 @@ -3876,8 +3868,8 @@ snapshots: d3-sankey: 0.12.3 dagre-d3-es: 7.0.13 dayjs: 1.11.19 - dompurify: 3.3.0 - katex: 0.16.25 + dompurify: 3.3.1 + katex: 0.16.27 khroma: 2.1.0 lodash-es: 4.17.21 marked: 16.4.2 @@ -4016,7 +4008,7 @@ snapshots: pdfjs-dist@5.4.449: optionalDependencies: - '@napi-rs/canvas': 0.1.83 + '@napi-rs/canvas': 0.1.84 picocolors@1.1.1: {} diff --git a/extensions/VSCode/Cargo.toml b/extensions/VSCode/Cargo.toml index a2cd644..e3c449b 100644 --- a/extensions/VSCode/Cargo.toml +++ b/extensions/VSCode/Cargo.toml @@ -32,7 +32,7 @@ license = "GPL-3.0-only" name = "codechat-editor-vscode-extension" readme = "../README.md" repository = "https://github.com/bjones1/CodeChat_Editor" -version = "0.1.43" +version = "0.1.44" [lib] crate-type = ["cdylib"] diff --git a/extensions/VSCode/package.json b/extensions/VSCode/package.json index b81701c..8728ac3 100644 --- a/extensions/VSCode/package.json +++ b/extensions/VSCode/package.json @@ -40,7 +40,7 @@ "type": "git", "url": "https://github.com/bjones1/CodeChat_Editor" }, - "version": "0.1.43", + "version": "0.1.44", "activationEvents": [ "onCommand:extension.codeChatEditorActivate", "onCommand:extension.codeChatEditorDeactivate" @@ -84,10 +84,10 @@ "@napi-rs/cli": "^3.5.0", "@tybys/wasm-util": "^0.10.1", "@types/escape-html": "^1.0.4", - "@types/node": "^24.10.1", + "@types/node": "^24.10.2", "@types/vscode": "1.61.0", - "@typescript-eslint/eslint-plugin": "^8.48.1", - "@typescript-eslint/parser": "^8.48.1", + "@typescript-eslint/eslint-plugin": "^8.49.0", + "@typescript-eslint/parser": "^8.49.0", "@vscode/vsce": "^3.7.1", "chalk": "^5.6.2", "esbuild": "^0.27.1", diff --git a/extensions/VSCode/pnpm-lock.yaml b/extensions/VSCode/pnpm-lock.yaml index a886af2..0cd404b 100644 --- a/extensions/VSCode/pnpm-lock.yaml +++ b/extensions/VSCode/pnpm-lock.yaml @@ -20,7 +20,7 @@ importers: version: 1.7.1 '@napi-rs/cli': specifier: ^3.5.0 - version: 3.5.0(@emnapi/runtime@1.7.1)(@types/node@24.10.1) + version: 3.5.0(@emnapi/runtime@1.7.1)(@types/node@24.10.2) '@tybys/wasm-util': specifier: ^0.10.1 version: 0.10.1 @@ -28,17 +28,17 @@ importers: specifier: ^1.0.4 version: 1.0.4 '@types/node': - specifier: ^24.10.1 - version: 24.10.1 + specifier: ^24.10.2 + version: 24.10.2 '@types/vscode': specifier: 1.61.0 version: 1.61.0 '@typescript-eslint/eslint-plugin': - specifier: ^8.48.1 - version: 8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) + specifier: ^8.49.0 + version: 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) '@typescript-eslint/parser': - specifier: ^8.48.1 - version: 8.48.1(eslint@9.39.1)(typescript@5.9.3) + specifier: ^8.49.0 + version: 8.49.0(eslint@9.39.1)(typescript@5.9.3) '@vscode/vsce': specifier: ^3.7.1 version: 3.7.1 @@ -56,7 +56,7 @@ importers: version: 10.1.8(eslint@9.39.1) eslint-plugin-import: specifier: ^2.32.0 - version: 2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1) + version: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1) eslint-plugin-node: specifier: ^11.1.0 version: 11.1.0(eslint@9.39.1) @@ -1041,20 +1041,20 @@ packages: resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} engines: {node: '>=18'} - '@textlint/ast-node-types@15.4.1': - resolution: {integrity: sha512-XifMpBMdo0E1Fuh85YdcYAgy+okNg9WKBzIPIO4JUDnSWUVFihnogrM4cjDapeHkgzSgulwR8oJVJ17eyxI1bA==} + '@textlint/ast-node-types@15.5.0': + resolution: {integrity: sha512-K0LEuuTo4rza8yDrlYkRdXLao8Iz/QBMsQdIxRrOOrLYb4HAtZaypZ78c+J6rDA1UlGxadZVLmkkiv4KV5fMKQ==} - '@textlint/linter-formatter@15.4.1': - resolution: {integrity: sha512-kAV7Sup3vwvqxKvBbf9lx/JaPHkRybQp/LLvA73U1AorPZE6XyfBAFG24BbMiCs4OX1ax4g7kXRuFPgMLWRf+g==} + '@textlint/linter-formatter@15.5.0': + resolution: {integrity: sha512-DPTm2+VXKID41qKQWagg/4JynM6hEEpvbq0PlGsEoC4Xm7IqXIxFym3mSf5+ued0cuiIV1hR9kgXjqGdP035tw==} - '@textlint/module-interop@15.4.1': - resolution: {integrity: sha512-jHtM2E5CR68P3z/+FGrEU5pml2fQVzEo2sez9FEjrVHSPCrHtqHcPaKfsYbQJjc9C48ObwaWrCzRNaL3KedNCQ==} + '@textlint/module-interop@15.5.0': + resolution: {integrity: sha512-rqfouEhBEgZlR9umswWXXRBcmmSM28Trpr9b0duzgehKYVc7wSQCuQMagr6YBJa2NRMfRNinupusbJXMg0ij2A==} - '@textlint/resolver@15.4.1': - resolution: {integrity: sha512-uVssyG3XXXKNY+O7NOajGvQZTyOuhPviwlq7Xek6ZT9K1eDQtA8074cPkAQoLMYhi/TUyOE5P5kpz42UF8Lmdw==} + '@textlint/resolver@15.5.0': + resolution: {integrity: sha512-kK5nFbg5N3kVoZExQI/dnYjCInmTltvXDnuCRrBxHI01i6kO/o8R7Lc2aFkAZ6/NUZuRPalkyDdwZJke4/R2wg==} - '@textlint/types@15.4.1': - resolution: {integrity: sha512-WByVZ3zblbvuI+voWQplUP7seSTKXI9z6TMVXEB3dY3JFrZCIXWKNfLbETX5lZV7fYkCMaDtILO1l6s11wdbQA==} + '@textlint/types@15.5.0': + resolution: {integrity: sha512-EjAPbuA+3NyQ9WyFP7iUlddi35F3mGrf4tb4cZM0nWywbtEJ3+XAYqL+5RsF0qFeSguxGir09NdZOWrG9wVOUQ==} '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} @@ -1071,8 +1071,8 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - '@types/node@24.10.1': - resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} + '@types/node@24.10.2': + resolution: {integrity: sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -1083,63 +1083,63 @@ packages: '@types/vscode@1.61.0': resolution: {integrity: sha512-9k5Nwq45hkRwdfCFY+eKXeQQSbPoA114mF7U/4uJXRBJeGIO7MuJdhF1PnaDN+lllL9iKGQtd6FFXShBXMNaFg==} - '@typescript-eslint/eslint-plugin@8.48.1': - resolution: {integrity: sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA==} + '@typescript-eslint/eslint-plugin@8.49.0': + resolution: {integrity: sha512-JXij0vzIaTtCwu6SxTh8qBc66kmf1xs7pI4UOiMDFVct6q86G0Zs7KRcEoJgY3Cav3x5Tq0MF5jwgpgLqgKG3A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.48.1 + '@typescript-eslint/parser': ^8.49.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.48.1': - resolution: {integrity: sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==} + '@typescript-eslint/parser@8.49.0': + resolution: {integrity: sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.48.1': - resolution: {integrity: sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w==} + '@typescript-eslint/project-service@8.49.0': + resolution: {integrity: sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.48.1': - resolution: {integrity: sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w==} + '@typescript-eslint/scope-manager@8.49.0': + resolution: {integrity: sha512-npgS3zi+/30KSOkXNs0LQXtsg9ekZ8OISAOLGWA/ZOEn0ZH74Ginfl7foziV8DT+D98WfQ5Kopwqb/PZOaIJGg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.48.1': - resolution: {integrity: sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw==} + '@typescript-eslint/tsconfig-utils@8.49.0': + resolution: {integrity: sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.48.1': - resolution: {integrity: sha512-1jEop81a3LrJQLTf/1VfPQdhIY4PlGDBc/i67EVWObrtvcziysbLN3oReexHOM6N3jyXgCrkBsZpqwH0hiDOQg==} + '@typescript-eslint/type-utils@8.49.0': + resolution: {integrity: sha512-KTExJfQ+svY8I10P4HdxKzWsvtVnsuCifU5MvXrRwoP2KOlNZ9ADNEWWsQTJgMxLzS5VLQKDjkCT/YzgsnqmZg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.48.1': - resolution: {integrity: sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q==} + '@typescript-eslint/types@8.49.0': + resolution: {integrity: sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.48.1': - resolution: {integrity: sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg==} + '@typescript-eslint/typescript-estree@8.49.0': + resolution: {integrity: sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.48.1': - resolution: {integrity: sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA==} + '@typescript-eslint/utils@8.49.0': + resolution: {integrity: sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.48.1': - resolution: {integrity: sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q==} + '@typescript-eslint/visitor-keys@8.49.0': + resolution: {integrity: sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@typespec/ts-http-runtime@0.3.2': @@ -1851,9 +1851,6 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - has-bigints@1.1.0: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} @@ -3175,122 +3172,122 @@ snapshots: '@inquirer/ansi@2.0.2': {} - '@inquirer/checkbox@5.0.2(@types/node@24.10.1)': + '@inquirer/checkbox@5.0.2(@types/node@24.10.2)': dependencies: '@inquirer/ansi': 2.0.2 - '@inquirer/core': 11.0.2(@types/node@24.10.1) + '@inquirer/core': 11.0.2(@types/node@24.10.2) '@inquirer/figures': 2.0.2 - '@inquirer/type': 4.0.2(@types/node@24.10.1) + '@inquirer/type': 4.0.2(@types/node@24.10.2) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/confirm@6.0.2(@types/node@24.10.1)': + '@inquirer/confirm@6.0.2(@types/node@24.10.2)': dependencies: - '@inquirer/core': 11.0.2(@types/node@24.10.1) - '@inquirer/type': 4.0.2(@types/node@24.10.1) + '@inquirer/core': 11.0.2(@types/node@24.10.2) + '@inquirer/type': 4.0.2(@types/node@24.10.2) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/core@11.0.2(@types/node@24.10.1)': + '@inquirer/core@11.0.2(@types/node@24.10.2)': dependencies: '@inquirer/ansi': 2.0.2 '@inquirer/figures': 2.0.2 - '@inquirer/type': 4.0.2(@types/node@24.10.1) + '@inquirer/type': 4.0.2(@types/node@24.10.2) cli-width: 4.1.0 mute-stream: 3.0.0 signal-exit: 4.1.0 wrap-ansi: 9.0.2 optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/editor@5.0.2(@types/node@24.10.1)': + '@inquirer/editor@5.0.2(@types/node@24.10.2)': dependencies: - '@inquirer/core': 11.0.2(@types/node@24.10.1) - '@inquirer/external-editor': 2.0.2(@types/node@24.10.1) - '@inquirer/type': 4.0.2(@types/node@24.10.1) + '@inquirer/core': 11.0.2(@types/node@24.10.2) + '@inquirer/external-editor': 2.0.2(@types/node@24.10.2) + '@inquirer/type': 4.0.2(@types/node@24.10.2) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/expand@5.0.2(@types/node@24.10.1)': + '@inquirer/expand@5.0.2(@types/node@24.10.2)': dependencies: - '@inquirer/core': 11.0.2(@types/node@24.10.1) - '@inquirer/type': 4.0.2(@types/node@24.10.1) + '@inquirer/core': 11.0.2(@types/node@24.10.2) + '@inquirer/type': 4.0.2(@types/node@24.10.2) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/external-editor@2.0.2(@types/node@24.10.1)': + '@inquirer/external-editor@2.0.2(@types/node@24.10.2)': dependencies: chardet: 2.1.1 iconv-lite: 0.7.0 optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@inquirer/figures@2.0.2': {} - '@inquirer/input@5.0.2(@types/node@24.10.1)': + '@inquirer/input@5.0.2(@types/node@24.10.2)': dependencies: - '@inquirer/core': 11.0.2(@types/node@24.10.1) - '@inquirer/type': 4.0.2(@types/node@24.10.1) + '@inquirer/core': 11.0.2(@types/node@24.10.2) + '@inquirer/type': 4.0.2(@types/node@24.10.2) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/number@4.0.2(@types/node@24.10.1)': + '@inquirer/number@4.0.2(@types/node@24.10.2)': dependencies: - '@inquirer/core': 11.0.2(@types/node@24.10.1) - '@inquirer/type': 4.0.2(@types/node@24.10.1) + '@inquirer/core': 11.0.2(@types/node@24.10.2) + '@inquirer/type': 4.0.2(@types/node@24.10.2) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/password@5.0.2(@types/node@24.10.1)': + '@inquirer/password@5.0.2(@types/node@24.10.2)': dependencies: '@inquirer/ansi': 2.0.2 - '@inquirer/core': 11.0.2(@types/node@24.10.1) - '@inquirer/type': 4.0.2(@types/node@24.10.1) + '@inquirer/core': 11.0.2(@types/node@24.10.2) + '@inquirer/type': 4.0.2(@types/node@24.10.2) optionalDependencies: - '@types/node': 24.10.1 - - '@inquirer/prompts@8.0.2(@types/node@24.10.1)': - dependencies: - '@inquirer/checkbox': 5.0.2(@types/node@24.10.1) - '@inquirer/confirm': 6.0.2(@types/node@24.10.1) - '@inquirer/editor': 5.0.2(@types/node@24.10.1) - '@inquirer/expand': 5.0.2(@types/node@24.10.1) - '@inquirer/input': 5.0.2(@types/node@24.10.1) - '@inquirer/number': 4.0.2(@types/node@24.10.1) - '@inquirer/password': 5.0.2(@types/node@24.10.1) - '@inquirer/rawlist': 5.0.2(@types/node@24.10.1) - '@inquirer/search': 4.0.2(@types/node@24.10.1) - '@inquirer/select': 5.0.2(@types/node@24.10.1) + '@types/node': 24.10.2 + + '@inquirer/prompts@8.0.2(@types/node@24.10.2)': + dependencies: + '@inquirer/checkbox': 5.0.2(@types/node@24.10.2) + '@inquirer/confirm': 6.0.2(@types/node@24.10.2) + '@inquirer/editor': 5.0.2(@types/node@24.10.2) + '@inquirer/expand': 5.0.2(@types/node@24.10.2) + '@inquirer/input': 5.0.2(@types/node@24.10.2) + '@inquirer/number': 4.0.2(@types/node@24.10.2) + '@inquirer/password': 5.0.2(@types/node@24.10.2) + '@inquirer/rawlist': 5.0.2(@types/node@24.10.2) + '@inquirer/search': 4.0.2(@types/node@24.10.2) + '@inquirer/select': 5.0.2(@types/node@24.10.2) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/rawlist@5.0.2(@types/node@24.10.1)': + '@inquirer/rawlist@5.0.2(@types/node@24.10.2)': dependencies: - '@inquirer/core': 11.0.2(@types/node@24.10.1) - '@inquirer/type': 4.0.2(@types/node@24.10.1) + '@inquirer/core': 11.0.2(@types/node@24.10.2) + '@inquirer/type': 4.0.2(@types/node@24.10.2) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/search@4.0.2(@types/node@24.10.1)': + '@inquirer/search@4.0.2(@types/node@24.10.2)': dependencies: - '@inquirer/core': 11.0.2(@types/node@24.10.1) + '@inquirer/core': 11.0.2(@types/node@24.10.2) '@inquirer/figures': 2.0.2 - '@inquirer/type': 4.0.2(@types/node@24.10.1) + '@inquirer/type': 4.0.2(@types/node@24.10.2) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/select@5.0.2(@types/node@24.10.1)': + '@inquirer/select@5.0.2(@types/node@24.10.2)': dependencies: '@inquirer/ansi': 2.0.2 - '@inquirer/core': 11.0.2(@types/node@24.10.1) + '@inquirer/core': 11.0.2(@types/node@24.10.2) '@inquirer/figures': 2.0.2 - '@inquirer/type': 4.0.2(@types/node@24.10.1) + '@inquirer/type': 4.0.2(@types/node@24.10.2) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/type@4.0.2(@types/node@24.10.1)': + '@inquirer/type@4.0.2(@types/node@24.10.2)': optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@isaacs/balanced-match@4.0.1': {} @@ -3307,9 +3304,9 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 - '@napi-rs/cli@3.5.0(@emnapi/runtime@1.7.1)(@types/node@24.10.1)': + '@napi-rs/cli@3.5.0(@emnapi/runtime@1.7.1)(@types/node@24.10.2)': dependencies: - '@inquirer/prompts': 8.0.2(@types/node@24.10.1) + '@inquirer/prompts': 8.0.2(@types/node@24.10.2) '@napi-rs/cross-toolchain': 1.0.3 '@napi-rs/wasm-tools': 1.0.1 '@octokit/rest': 22.0.1 @@ -3724,9 +3721,9 @@ snapshots: dependencies: '@secretlint/resolver': 10.2.2 '@secretlint/types': 10.2.2 - '@textlint/linter-formatter': 15.4.1 - '@textlint/module-interop': 15.4.1 - '@textlint/types': 15.4.1 + '@textlint/linter-formatter': 15.5.0 + '@textlint/module-interop': 15.5.0 + '@textlint/types': 15.5.0 chalk: 5.6.2 debug: 4.4.3 pluralize: 8.0.0 @@ -3772,15 +3769,15 @@ snapshots: '@sindresorhus/merge-streams@2.3.0': {} - '@textlint/ast-node-types@15.4.1': {} + '@textlint/ast-node-types@15.5.0': {} - '@textlint/linter-formatter@15.4.1': + '@textlint/linter-formatter@15.5.0': dependencies: '@azu/format-text': 1.0.2 '@azu/style-format': 1.0.1 - '@textlint/module-interop': 15.4.1 - '@textlint/resolver': 15.4.1 - '@textlint/types': 15.4.1 + '@textlint/module-interop': 15.5.0 + '@textlint/resolver': 15.5.0 + '@textlint/types': 15.5.0 chalk: 4.1.2 debug: 4.4.3 js-yaml: 4.1.1 @@ -3793,13 +3790,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@textlint/module-interop@15.4.1': {} + '@textlint/module-interop@15.5.0': {} - '@textlint/resolver@15.4.1': {} + '@textlint/resolver@15.5.0': {} - '@textlint/types@15.4.1': + '@textlint/types@15.5.0': dependencies: - '@textlint/ast-node-types': 15.4.1 + '@textlint/ast-node-types': 15.5.0 '@tybys/wasm-util@0.10.1': dependencies: @@ -3813,7 +3810,7 @@ snapshots: '@types/json5@0.0.29': {} - '@types/node@24.10.1': + '@types/node@24.10.2': dependencies: undici-types: 7.16.0 @@ -3823,16 +3820,15 @@ snapshots: '@types/vscode@1.61.0': {} - '@typescript-eslint/eslint-plugin@8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.48.1(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.48.1 - '@typescript-eslint/type-utils': 8.48.1(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/utils': 8.48.1(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.48.1 + '@typescript-eslint/parser': 8.49.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.49.0 eslint: 9.39.1 - graphemer: 1.4.0 ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.1.0(typescript@5.9.3) @@ -3840,41 +3836,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.48.1 - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.48.1 + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.49.0 debug: 4.4.3 eslint: 9.39.1 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.48.1(typescript@5.9.3)': + '@typescript-eslint/project-service@8.49.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.48.1(typescript@5.9.3) - '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.9.3) + '@typescript-eslint/types': 8.49.0 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.48.1': + '@typescript-eslint/scope-manager@8.49.0': dependencies: - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/visitor-keys': 8.48.1 + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/visitor-keys': 8.49.0 - '@typescript-eslint/tsconfig-utils@8.48.1(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.49.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.48.1(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.49.0(eslint@9.39.1)(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) - '@typescript-eslint/utils': 8.48.1(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.1)(typescript@5.9.3) debug: 4.4.3 eslint: 9.39.1 ts-api-utils: 2.1.0(typescript@5.9.3) @@ -3882,14 +3878,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.48.1': {} + '@typescript-eslint/types@8.49.0': {} - '@typescript-eslint/typescript-estree@8.48.1(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.49.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.48.1(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.48.1(typescript@5.9.3) - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/visitor-keys': 8.48.1 + '@typescript-eslint/project-service': 8.49.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.9.3) + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/visitor-keys': 8.49.0 debug: 4.4.3 minimatch: 9.0.5 semver: 7.7.3 @@ -3899,20 +3895,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.48.1(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/utils@8.49.0(eslint@9.39.1)(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) - '@typescript-eslint/scope-manager': 8.48.1 - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) eslint: 9.39.1 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.48.1': + '@typescript-eslint/visitor-keys@8.49.0': dependencies: - '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/types': 8.49.0 eslint-visitor-keys: 4.2.1 '@typespec/ts-http-runtime@0.3.2': @@ -4506,11 +4502,11 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.48.1(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.49.0(eslint@9.39.1)(typescript@5.9.3) eslint: 9.39.1 eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: @@ -4522,7 +4518,7 @@ snapshots: eslint-utils: 2.1.0 regexpp: 3.2.0 - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -4533,7 +4529,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -4545,7 +4541,7 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.48.1(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.49.0(eslint@9.39.1)(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -4795,8 +4791,6 @@ snapshots: graceful-fs@4.2.11: {} - graphemer@1.4.0: {} - has-bigints@1.1.0: {} has-flag@4.0.0: {} diff --git a/server/Cargo.lock b/server/Cargo.lock index 48f5529..8c491c0 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -505,9 +505,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" [[package]] name = "bcder" @@ -651,9 +651,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.48" +version = "1.2.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c481bdbf0ed3b892f6f806287d72acd515b352a4ec27a208489b8c1bc839633a" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" dependencies = [ "find-msvc-tools", "jobserver", @@ -746,7 +746,7 @@ checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "codechat-editor-server" -version = "0.1.43" +version = "0.1.44" dependencies = [ "actix-files", "actix-http", @@ -768,11 +768,13 @@ dependencies = [ "futures", "futures-util", "htmd", + "html5ever 0.36.1", "imara-diff", "indoc", "lazy_static", "log", "log4rs", + "markup5ever_rcdom 0.36.0+unofficial", "mime", "mime_guess", "minreq", @@ -781,6 +783,7 @@ dependencies = [ "path-slash", "pest", "pest_derive", + "phf 0.13.1", "predicates", "pretty_assertions", "pulldown-cmark 0.13.0", @@ -1282,13 +1285,13 @@ checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "flate2" -version = "1.1.7" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2152dbcb980c05735e2a651d96011320a949eb31a0c8b38b72645ce97dec676" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" dependencies = [ "crc32fast", + "libz-rs-sys", "miniz_oxide", - "zlib-rs", ] [[package]] @@ -1578,11 +1581,10 @@ dependencies = [ [[package]] name = "htmd" version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60ae59466542f2346e43d4a5e9b4432a1fc915b279c9fc0484e9ed7379121454" +source = "git+https://github.com/bjones1/htmd.git?branch=math-support#af6bc129874d33eb7f4d7fb6f0a39b04c668b2f5" dependencies = [ - "html5ever", - "markup5ever_rcdom", + "html5ever 0.35.0", + "markup5ever_rcdom 0.35.0+unofficial", "phf 0.13.1", ] @@ -1593,10 +1595,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55d958c2f74b664487a2035fe1dadb032c48718a03b63f3ab0b8537db8549ed4" dependencies = [ "log", - "markup5ever", + "markup5ever 0.35.0", "match_token", ] +[[package]] +name = "html5ever" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6452c4751a24e1b99c3260d505eaeee76a050573e61f30ac2c924ddc7236f01e" +dependencies = [ + "log", + "markup5ever 0.36.1", +] + [[package]] name = "http" version = "0.2.12" @@ -2112,6 +2124,15 @@ dependencies = [ "redox_syscall", ] +[[package]] +name = "libz-rs-sys" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b484ba8d4f775eeca644c452a56650e544bf7e617f1d170fe7298122ead5222" +dependencies = [ + "zlib-rs", +] + [[package]] name = "linux-raw-sys" version = "0.11.0" @@ -2234,7 +2255,18 @@ checksum = "311fe69c934650f8f19652b3946075f0fc41ad8757dbb68f1ca14e7900ecc1c3" dependencies = [ "log", "tendril", - "web_atoms", + "web_atoms 0.1.3", +] + +[[package]] +name = "markup5ever" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c3294c4d74d0742910f8c7b466f44dda9eb2d5742c1e430138df290a1e8451c" +dependencies = [ + "log", + "tendril", + "web_atoms 0.2.0", ] [[package]] @@ -2243,10 +2275,22 @@ version = "0.35.0+unofficial" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8bcd53df4748257345b8bc156d620340ce0f015ec1c7ef1cff475543888a31d" dependencies = [ - "html5ever", - "markup5ever", + "html5ever 0.35.0", + "markup5ever 0.35.0", "tendril", - "xml5ever", + "xml5ever 0.35.0", +] + +[[package]] +name = "markup5ever_rcdom" +version = "0.36.0+unofficial" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e5fc8802e8797c0dfdd2ce5c21aa0aee21abbc7b3b18559100651b3352a7b63" +dependencies = [ + "html5ever 0.36.1", + "markup5ever 0.36.1", + "tendril", + "xml5ever 0.36.1", ] [[package]] @@ -2591,6 +2635,16 @@ dependencies = [ "phf_shared 0.11.3", ] +[[package]] +name = "phf_codegen" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", +] + [[package]] name = "phf_generator" version = "0.11.3" @@ -2999,9 +3053,9 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "reqwest" -version = "0.12.24" +version = "0.12.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" +checksum = "b6eff9328d40131d43bd911d42d79eb6a47312002a4daefc9e37f17e74a7701a" dependencies = [ "base64", "bytes", @@ -3383,9 +3437,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "simple-file-manifest" @@ -3460,6 +3514,19 @@ dependencies = [ "serde", ] +[[package]] +name = "string_cache" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.13.1", + "precomputed-hash", + "serde", +] + [[package]] name = "string_cache_codegen" version = "0.5.4" @@ -3472,6 +3539,18 @@ dependencies = [ "quote", ] +[[package]] +name = "string_cache_codegen" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", + "proc-macro2", + "quote", +] + [[package]] name = "stringmatch" version = "0.4.0" @@ -3886,9 +3965,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf146f99d442e8e68e585f5d798ccd3cad9a7835b917e09728880a862706456" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ "bitflags 2.10.0", "bytes", @@ -4282,9 +4361,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57ffde1dc01240bdf9992e3205668b235e59421fd085e8a317ed98da0178d414" dependencies = [ "phf 0.11.3", - "phf_codegen", - "string_cache", - "string_cache_codegen", + "phf_codegen 0.11.3", + "string_cache 0.8.9", + "string_cache_codegen 0.5.4", +] + +[[package]] +name = "web_atoms" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acd0c322f146d0f8aad130ce6c187953889359584497dac6561204c8e17bb43d" +dependencies = [ + "phf 0.13.1", + "phf_codegen 0.13.1", + "string_cache 0.9.0", + "string_cache_codegen 0.6.1", ] [[package]] @@ -4757,7 +4848,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee3f1e41afb31a75aef076563b0ad3ecc24f5bd9d12a72b132222664eb76b494" dependencies = [ "log", - "markup5ever", + "markup5ever 0.35.0", +] + +[[package]] +name = "xml5ever" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f57dd51b88a4b9f99f9b55b136abb86210629d61c48117ddb87f567e51e66be7" +dependencies = [ + "log", + "markup5ever 0.36.1", ] [[package]] diff --git a/server/Cargo.toml b/server/Cargo.toml index 68739d8..96a6138 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -32,7 +32,7 @@ license = "GPL-3.0-only" name = "codechat-editor-server" readme = "../README.md" repository = "https://github.com/bjones1/CodeChat_Editor" -version = "0.1.43" +version = "0.1.44" # This library allows other packages to use core CodeChat Editor features. [lib] From ef13c530ac3b4233cb48eb56ff479e86707dffeb Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Tue, 9 Dec 2025 08:02:26 -0600 Subject: [PATCH 19/27] Fix: correct ordering of conversions. --- server/src/processing.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/server/src/processing.rs b/server/src/processing.rs index 139d78d..25849bf 100644 --- a/server/src/processing.rs +++ b/server/src/processing.rs @@ -452,10 +452,11 @@ pub fn codechat_for_web_to_source( } // Translate the HTML document to Markdown. let converter = HtmlToMarkdownWrapped::new(); - let wet_html = converter - .convert(&code_mirror.doc) - .map_err(CodechatForWebToSourceError::HtmlToMarkdownFailed)?; - return dehydrate_html(&wet_html).map_err(CodechatForWebToSourceError::ParseFailed); + let dry_html = + dehydrate_html(&code_mirror.doc).map_err(CodechatForWebToSourceError::ParseFailed)?; + return converter + .convert(&dry_html) + .map_err(CodechatForWebToSourceError::HtmlToMarkdownFailed); } let code_doc_block_vec_html = code_mirror_to_code_doc_blocks(code_mirror); let code_doc_block_vec = doc_block_html_to_markdown(code_doc_block_vec_html) From 8d4e54b1437346e42fddec337227696b650afcfb Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Tue, 9 Dec 2025 09:20:47 -0600 Subject: [PATCH 20/27] Fix: verify we get a "pong" message from the webserver on startup. --- server/src/ide/vscode/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/ide/vscode/tests.rs b/server/src/ide/vscode/tests.rs index 6317839..ab6bdca 100644 --- a/server/src/ide/vscode/tests.rs +++ b/server/src/ide/vscode/tests.rs @@ -202,7 +202,7 @@ async fn _prep_test( while now.elapsed().unwrap().as_millis() < 100 { if minreq::get(format!("http://127.0.0.1:{IP_PORT}/ping",)) .send() - .is_ok() + .is_ok_and(|response| response.as_bytes() == b"pong") { break; } From 4326eda7b2b5c4573acdd4fc79eb2667247c36ca Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Tue, 9 Dec 2025 14:18:28 -0600 Subject: [PATCH 21/27] Fix: remove unused is_user_change flag. --- client/src/CodeMirror-integration.mts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/client/src/CodeMirror-integration.mts b/client/src/CodeMirror-integration.mts index 8ebd9ce..fc5f32e 100644 --- a/client/src/CodeMirror-integration.mts +++ b/client/src/CodeMirror-integration.mts @@ -190,7 +190,6 @@ export const docBlockField = StateField.define({ effect.value.indent, effect.value.delimiter, effect.value.content, - false, ), ...decorationOptions, }).range(effect.value.from, effect.value.to), @@ -277,9 +276,6 @@ export const docBlockField = StateField.define({ prev.spec.widget.contents, effect.value.contents, ), - // Assume this isn't a user change unless it's - // specified. - effect.value.is_user_change ?? false, ), ...decorationOptions, }).range(from, to), @@ -334,7 +330,6 @@ export const docBlockField = StateField.define({ indent, delimiter, contents, - false, ), ...decorationOptions, }).range(from, to), @@ -375,9 +370,6 @@ type updateDocBlockType = { indent?: string; delimiter?: string; contents: string | StringDiff[]; - // True if this update comes from a user change, as opposed to an update - // received from the IDE. - is_user_change?: boolean; }; // Define an update. @@ -420,7 +412,6 @@ class DocBlockWidget extends WidgetType { readonly indent: string, readonly delimiter: string, readonly contents: string, - readonly is_user_change: boolean, ) { // TODO: I don't understand why I don't need to store the provided // parameters in the object: `this.indent = indent;`, etc. @@ -463,9 +454,6 @@ class DocBlockWidget extends WidgetType { updateDOM(dom: HTMLElement, _view: EditorView): boolean { // If this change was produced by a user edit, then the DOM was already // updated. Stop here. - if (this.is_user_change) { - return true; - } (dom.childNodes[0] as HTMLDivElement).innerHTML = this.indent; // The contents div could be a TinyMCE instance, or just a plain div. From 80567a0113f9d7be650c454bdbaec3e9e21b1a43 Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Tue, 9 Dec 2025 14:49:46 -0600 Subject: [PATCH 22/27] Test: revised test to use edits that cause re-translation. change test to ensure that Graphviz and Mermaid diagrams newlines are preserved. --- server/tests/overall_core/mod.rs | 113 ++++++++++++++++++++++++++----- 1 file changed, 96 insertions(+), 17 deletions(-) diff --git a/server/tests/overall_core/mod.rs b/server/tests/overall_core/mod.rs index cd79d35..9d85250 100644 --- a/server/tests/overall_core/mod.rs +++ b/server/tests/overall_core/mod.rs @@ -1024,23 +1024,21 @@ async fn test_client_updates_core( driver_ref: &WebDriver, test_dir: &Path, ) -> Result<(), WebDriverError> { - let ide_version = 0.0; - let server_id = perform_loadfile( + let mut ide_version = 0.0; + let orig_text = indoc!( + " + # Test updates in the client that modify the client after appending to a line. + def foo(): + A comment + print() + " + ) + .to_string(); + let mut server_id = perform_loadfile( &codechat_server, test_dir, "test.py", - Some(( - indoc!( - " - # Test updates in the client that modify the client after appending to a line. - def foo(): - A comment - print() - " - ) - .to_string(), - ide_version, - )), + Some((orig_text.clone(), ide_version)), true, 6.0, ) @@ -1102,6 +1100,7 @@ async fn test_client_updates_core( message: EditorMessageContents::Result(Ok(ResultOkTypes::Void)) } ); + server_id += MESSAGE_ID_INCREMENT; goto_line(&codechat_server, driver_ref, &mut client_id, &path_str, 4) .await @@ -1142,6 +1141,72 @@ async fn test_client_updates_core( } ); codechat_server.send_result(client_id, None).await.unwrap(); + client_id += MESSAGE_ID_INCREMENT; + + // The Server sends the Client a re-translated version of the text with the new doc block; the Client + // replies with a Result(Ok). + assert_eq!( + codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), + EditorMessage { + id: server_id, + message: EditorMessageContents::Result(Ok(ResultOkTypes::Void)) + } + ); + //server_id += MESSAGE_ID_INCREMENT; + + // Send the original text back, to ensure the re-translation correctly updated the Client. + ide_version = 1.0; + let ide_id = codechat_server + .send_message_update_plain( + path_str.clone(), + Some((orig_text, ide_version)), + Some(1), + None, + ) + .await + .unwrap(); + assert_eq!( + codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), + EditorMessage { + id: ide_id, + message: EditorMessageContents::Result(Ok(ResultOkTypes::Void)) + } + ); + sleep(Duration::from_secs(1000)).await; + + // Trigger a client edit to send the Client contents back. + let code_line = driver_ref.find(By::Css(code_line_css)).await.unwrap(); + code_line.send_keys(" ").await.unwrap(); + + let msg = codechat_server.get_message_timeout(TIMEOUT).await.unwrap(); + let new_client_version = get_version(&msg); + assert_eq!( + msg, + EditorMessage { + id: client_id, + message: EditorMessageContents::Update(UpdateMessageContents { + file_path: path_str.clone(), + contents: Some(CodeChatForWeb { + metadata: SourceFileMetadata { + mode: "python".to_string(), + }, + source: CodeMirrorDiffable::Diff(CodeMirrorDiff { + doc: vec![StringDiff { + from: 79, + to: Some(90), + insert: "def foo(): \n".to_string() + }], + doc_blocks: vec![], + version: ide_version, + }), + version: new_client_version, + }), + cursor_position: Some(2), + scroll_position: Some(1.0) + }) + } + ); + codechat_server.send_result(client_id, None).await.unwrap(); Ok(()) } @@ -1236,6 +1301,7 @@ async fn test_4_core( make_test!(test_5, test_5_core); +// Verify that newlines in Mermaid and Graphviz diagrams aren't removed. async fn test_5_core( codechat_server: CodeChatEditorServer, driver_ref: &WebDriver, @@ -1250,12 +1316,14 @@ async fn test_5_core( # # ```graphviz # digraph g { - # A -> B + # A -> B # } # ``` # - # Test. - test() + # ```mermaid + # graph TD + # A --> B + # ``` " ) .to_string(); @@ -1410,5 +1478,16 @@ async fn test_5_core( codechat_server.send_result(client_id, None).await.unwrap(); //client_id += MESSAGE_ID_INCREMENT; + // The Server sends the Client a wrapped version of the text; the Client + // replies with a Result(Ok). + assert_eq!( + codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), + EditorMessage { + id: server_id, + message: EditorMessageContents::Result(Ok(ResultOkTypes::Void)) + } + ); + //server_id += MESSAGE_ID_INCREMENT; + Ok(()) } From 82b6defd71c15bc37bf7405a942b178d67704aa7 Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Tue, 9 Dec 2025 15:39:03 -0600 Subject: [PATCH 23/27] Clean: format code. Fix: tests use older CodeMirror until bug is fixed. --- client/package.json5 | 2 +- client/src/CodeMirror-integration.mts | 6 +---- client/src/debug_enabled.mts | 1 + server/tests/overall_core/mod.rs | 39 +++++++++------------------ 4 files changed, 15 insertions(+), 33 deletions(-) diff --git a/client/package.json5 b/client/package.json5 index ef90c5f..e6e085b 100644 --- a/client/package.json5 +++ b/client/package.json5 @@ -61,7 +61,7 @@ '@codemirror/lang-xml': '^6.1.0', '@codemirror/lang-yaml': '^6.1.2', '@codemirror/state': '^6.5.2', - '@codemirror/view': '^6.39.0', + '@codemirror/view': '=6.38.8', '@hpcc-js/wasm-graphviz': '^1.16.0', '@mathjax/mathjax-newcm-font': '4.0.0', codemirror: '^6.0.2', diff --git a/client/src/CodeMirror-integration.mts b/client/src/CodeMirror-integration.mts index fc5f32e..659c4f2 100644 --- a/client/src/CodeMirror-integration.mts +++ b/client/src/CodeMirror-integration.mts @@ -326,11 +326,7 @@ export const docBlockField = StateField.define({ contents, ]: CodeMirrorDocBlockTuple) => Decoration.replace({ - widget: new DocBlockWidget( - indent, - delimiter, - contents, - ), + widget: new DocBlockWidget(indent, delimiter, contents), ...decorationOptions, }).range(from, to), ), diff --git a/client/src/debug_enabled.mts b/client/src/debug_enabled.mts index a59aed4..4f7fba2 100644 --- a/client/src/debug_enabled.mts +++ b/client/src/debug_enabled.mts @@ -16,6 +16,7 @@ // // `debug_enable.mts` -- Configure debug features // ============================================================================= +// // True to enable additional debug logging. export const DEBUG_ENABLED = false; diff --git a/server/tests/overall_core/mod.rs b/server/tests/overall_core/mod.rs index 9d85250..d3b85a5 100644 --- a/server/tests/overall_core/mod.rs +++ b/server/tests/overall_core/mod.rs @@ -1172,7 +1172,6 @@ async fn test_client_updates_core( message: EditorMessageContents::Result(Ok(ResultOkTypes::Void)) } ); - sleep(Duration::from_secs(1000)).await; // Trigger a client edit to send the Client contents back. let code_line = driver_ref.find(By::Css(code_line_css)).await.unwrap(); @@ -1327,7 +1326,7 @@ async fn test_5_core( " ) .to_string(); - let server_id = perform_loadfile( + let mut server_id = perform_loadfile( &codechat_server, test_dir, "test.py", @@ -1383,18 +1382,11 @@ async fn test_5_core( mode: "python".to_string(), }, source: CodeMirrorDiffable::Diff(CodeMirrorDiff { - doc: vec![ - StringDiff { - from: 0, - to: Some(8), - insert: "# fooTest.\n".to_string(), - }, - StringDiff { - from: 24, - to: Some(55,), - insert: "# digraph g { A -> B }\n".to_string(), - } - ], + doc: vec![StringDiff { + from: 0, + to: Some(8), + insert: "# fooTest.\n".to_string(), + },], doc_blocks: vec![], version, }), @@ -1418,7 +1410,7 @@ async fn test_5_core( message: EditorMessageContents::Result(Ok(ResultOkTypes::Void)) } ); - //server_id += MESSAGE_ID_INCREMENT; + server_id += MESSAGE_ID_INCREMENT; // Send new text, which turns into a diff. let ide_id = codechat_server @@ -1452,18 +1444,11 @@ async fn test_5_core( mode: "python".to_string(), }, source: CodeMirrorDiffable::Diff(CodeMirrorDiff { - doc: vec![ - StringDiff { - from: 0, - to: Some(8), - insert: "# Tesbart.\n".to_string(), - }, - StringDiff { - from: 24, - to: Some(55,), - insert: "# digraph g { A -> B }\n".to_string(), - } - ], + doc: vec![StringDiff { + from: 0, + to: Some(8), + insert: "# Tesbart.\n".to_string(), + },], doc_blocks: vec![], version, }), From 71224b4c8295dae658a8df42b57c4a6c2d225a10 Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Tue, 9 Dec 2025 15:46:20 -0600 Subject: [PATCH 24/27] Freeze for release. --- CHANGELOG.md | 4 +- client/pnpm-lock.yaml | 74 +++++++++--------- extensions/VSCode/Cargo.lock | 146 ++++++++++++++++++++++++++++------- server/Cargo.lock | 8 +- 4 files changed, 162 insertions(+), 70 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 079306b..12fc274 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,8 +22,8 @@ Changelog [Github master](https://github.com/bjones1/CodeChat_Editor) -------------------------------------------------------------------------------- -* Update Graphviz to latest build; make all Graphviz output a block element, not - an inline element. +* Update Graphviz to latest build; make Graphviz output a block element, not an + inline element. * Allow creating Mermaid and Graphviz diagrams using simpler code block syntax. * Support math free of Markdown escaping. This is a backward-incompatible change: you must manually remove Markdown escaping from math written before diff --git a/client/pnpm-lock.yaml b/client/pnpm-lock.yaml index 10ede82..65cf112 100644 --- a/client/pnpm-lock.yaml +++ b/client/pnpm-lock.yaml @@ -57,8 +57,8 @@ importers: specifier: ^6.5.2 version: 6.5.2 '@codemirror/view': - specifier: ^6.39.0 - version: 6.39.0 + specifier: ^6.38.8 + version: 6.38.8 '@hpcc-js/wasm-graphviz': specifier: ^1.16.0 version: 1.16.0 @@ -219,8 +219,8 @@ packages: '@codemirror/state@6.5.2': resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==} - '@codemirror/view@6.39.0': - resolution: {integrity: sha512-pn7UA5RDNLFpdM4PTyqwb1qQ/hQ3brwUKYAlJGrg3972VHJotgXrVBdBAWcbMkOjERXX609fmqfRldnGkC96kw==} + '@codemirror/view@6.38.8': + resolution: {integrity: sha512-XcE9fcnkHCbWkjeKyi0lllwXmBLtyYb5dt89dJyx23I9+LSh5vZDIuk7OLG4VM1lgrXZQcY6cxyZyk5WVPRv/A==} '@esbuild/aix-ppc64@0.27.1': resolution: {integrity: sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==} @@ -448,8 +448,8 @@ packages: '@lezer/common@1.4.0': resolution: {integrity: sha512-DVeMRoGrgn/k45oQNu189BoW4SZwgZFzJ1+1TV5j2NJ/KFC83oa/enRqZSGshyeMk5cPWMhsKs9nx+8o0unwGg==} - '@lezer/cpp@1.1.3': - resolution: {integrity: sha512-ykYvuFQKGsRi6IcE+/hCSGUhb/I4WPjd3ELhEblm2wS2cOznDFzO+ubK2c+ioysOnlZ3EduV+MVQFCPzAIoY3w==} + '@lezer/cpp@1.1.4': + resolution: {integrity: sha512-aYSdZyUueeTgnfXQntiGUqKNW5WujlAsIbbHzkfJDneSZoyjPg8ObmWG3bzDPVYMC/Kf4l43WJLCunPnYFfQ0g==} '@lezer/css@1.3.0': resolution: {integrity: sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw==} @@ -472,8 +472,8 @@ packages: '@lezer/json@1.0.3': resolution: {integrity: sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==} - '@lezer/lr@1.4.4': - resolution: {integrity: sha512-LHL17Mq0OcFXm1pGQssuGTQFPPdxARjKM8f7GA5+sGtHi0K3R84YaSbmche0+RKWHnCsx9asEe5OWOI4FHfe4A==} + '@lezer/lr@1.4.5': + resolution: {integrity: sha512-/YTRKP5yPPSo1xImYQk7AZZMAgap0kegzqCSYHjAL9x1AZ0ZQW+IpcEzMKagCsbTsLnVeWkxYrCNeXG8xEPrjg==} '@lezer/markdown@1.6.1': resolution: {integrity: sha512-72ah+Sml7lD8Wn7lnz9vwYmZBo9aQT+I2gjK/0epI+gjdwUbWw3MJ/ZBGEqG1UfrIauRqH37/c5mVHXeCTGXtA==} @@ -2139,20 +2139,20 @@ snapshots: dependencies: '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.0 + '@codemirror/view': 6.38.8 '@lezer/common': 1.4.0 '@codemirror/commands@6.10.0': dependencies: '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.0 + '@codemirror/view': 6.38.8 '@lezer/common': 1.4.0 '@codemirror/lang-cpp@6.0.3': dependencies: '@codemirror/language': 6.11.3 - '@lezer/cpp': 1.1.3 + '@lezer/cpp': 1.1.4 '@codemirror/lang-css@6.3.1': dependencies: @@ -2177,7 +2177,7 @@ snapshots: '@codemirror/lang-javascript': 6.2.4 '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.0 + '@codemirror/view': 6.38.8 '@lezer/common': 1.4.0 '@lezer/css': 1.3.0 '@lezer/html': 1.3.12 @@ -2193,7 +2193,7 @@ snapshots: '@codemirror/language': 6.11.3 '@codemirror/lint': 6.9.2 '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.0 + '@codemirror/view': 6.38.8 '@lezer/common': 1.4.0 '@lezer/javascript': 1.5.4 @@ -2208,7 +2208,7 @@ snapshots: '@codemirror/lang-html': 6.4.11 '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.0 + '@codemirror/view': 6.38.8 '@lezer/common': 1.4.0 '@lezer/markdown': 1.6.1 @@ -2240,14 +2240,14 @@ snapshots: '@codemirror/state': 6.5.2 '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.4 + '@lezer/lr': 1.4.5 '@codemirror/lang-xml@6.1.0': dependencies: '@codemirror/autocomplete': 6.20.0 '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.0 + '@codemirror/view': 6.38.8 '@lezer/common': 1.4.0 '@lezer/xml': 1.0.6 @@ -2258,35 +2258,35 @@ snapshots: '@codemirror/state': 6.5.2 '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.4 + '@lezer/lr': 1.4.5 '@lezer/yaml': 1.0.3 '@codemirror/language@6.11.3': dependencies: '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.0 + '@codemirror/view': 6.38.8 '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.4 + '@lezer/lr': 1.4.5 style-mod: 4.1.3 '@codemirror/lint@6.9.2': dependencies: '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.0 + '@codemirror/view': 6.38.8 crelt: 1.0.6 '@codemirror/search@6.5.11': dependencies: '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.0 + '@codemirror/view': 6.38.8 crelt: 1.0.6 '@codemirror/state@6.5.2': dependencies: '@marijn/find-cluster-break': 1.0.2 - '@codemirror/view@6.39.0': + '@codemirror/view@6.38.8': dependencies: '@codemirror/state': 6.5.2 crelt: 1.0.6 @@ -2449,23 +2449,23 @@ snapshots: '@lezer/common@1.4.0': {} - '@lezer/cpp@1.1.3': + '@lezer/cpp@1.1.4': dependencies: '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.4 + '@lezer/lr': 1.4.5 '@lezer/css@1.3.0': dependencies: '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.4 + '@lezer/lr': 1.4.5 '@lezer/go@1.0.1': dependencies: '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.4 + '@lezer/lr': 1.4.5 '@lezer/highlight@1.2.3': dependencies: @@ -2475,27 +2475,27 @@ snapshots: dependencies: '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.4 + '@lezer/lr': 1.4.5 '@lezer/java@1.1.3': dependencies: '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.4 + '@lezer/lr': 1.4.5 '@lezer/javascript@1.5.4': dependencies: '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.4 + '@lezer/lr': 1.4.5 '@lezer/json@1.0.3': dependencies: '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.4 + '@lezer/lr': 1.4.5 - '@lezer/lr@1.4.4': + '@lezer/lr@1.4.5': dependencies: '@lezer/common': 1.4.0 @@ -2508,31 +2508,31 @@ snapshots: dependencies: '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.4 + '@lezer/lr': 1.4.5 '@lezer/python@1.1.18': dependencies: '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.4 + '@lezer/lr': 1.4.5 '@lezer/rust@1.0.2': dependencies: '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.4 + '@lezer/lr': 1.4.5 '@lezer/xml@1.0.6': dependencies: '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.4 + '@lezer/lr': 1.4.5 '@lezer/yaml@1.0.3': dependencies: '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.4 + '@lezer/lr': 1.4.5 '@marijn/find-cluster-break@1.0.2': {} @@ -2985,7 +2985,7 @@ snapshots: '@codemirror/lint': 6.9.2 '@codemirror/search': 6.5.11 '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.0 + '@codemirror/view': 6.38.8 color-convert@2.0.1: dependencies: diff --git a/extensions/VSCode/Cargo.lock b/extensions/VSCode/Cargo.lock index 8477fdb..81a7d93 100644 --- a/extensions/VSCode/Cargo.lock +++ b/extensions/VSCode/Cargo.lock @@ -445,9 +445,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.48" +version = "1.2.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c481bdbf0ed3b892f6f806287d72acd515b352a4ec27a208489b8c1bc839633a" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" dependencies = [ "find-msvc-tools", "jobserver", @@ -522,7 +522,7 @@ checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "codechat-editor-server" -version = "0.1.43" +version = "0.1.44" dependencies = [ "actix-files", "actix-http", @@ -540,11 +540,13 @@ dependencies = [ "dunce", "futures-util", "htmd", + "html5ever 0.36.1", "imara-diff", "indoc", "lazy_static", "log", "log4rs", + "markup5ever_rcdom 0.36.0+unofficial", "mime", "mime_guess", "minreq", @@ -553,6 +555,7 @@ dependencies = [ "path-slash", "pest", "pest_derive", + "phf 0.13.1", "pulldown-cmark 0.13.0", "rand 0.9.2", "regex", @@ -570,7 +573,7 @@ dependencies = [ [[package]] name = "codechat-editor-vscode-extension" -version = "0.1.43" +version = "0.1.44" dependencies = [ "codechat-editor-server", "log", @@ -846,9 +849,9 @@ checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "flate2" -version = "1.1.7" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2152dbcb980c05735e2a651d96011320a949eb31a0c8b38b72645ce97dec676" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" dependencies = [ "crc32fast", "miniz_oxide", @@ -1059,11 +1062,10 @@ dependencies = [ [[package]] name = "htmd" version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60ae59466542f2346e43d4a5e9b4432a1fc915b279c9fc0484e9ed7379121454" +source = "git+https://github.com/bjones1/htmd.git?branch=math-support#af6bc129874d33eb7f4d7fb6f0a39b04c668b2f5" dependencies = [ - "html5ever", - "markup5ever_rcdom", + "html5ever 0.35.0", + "markup5ever_rcdom 0.35.0+unofficial", "phf 0.13.1", ] @@ -1074,10 +1076,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55d958c2f74b664487a2035fe1dadb032c48718a03b63f3ab0b8537db8549ed4" dependencies = [ "log", - "markup5ever", + "markup5ever 0.35.0", "match_token", ] +[[package]] +name = "html5ever" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6452c4751a24e1b99c3260d505eaeee76a050573e61f30ac2c924ddc7236f01e" +dependencies = [ + "log", + "markup5ever 0.36.1", +] + [[package]] name = "http" version = "0.2.12" @@ -1185,9 +1197,9 @@ checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ "icu_collections", "icu_locale_core", @@ -1199,9 +1211,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" @@ -1499,7 +1511,18 @@ checksum = "311fe69c934650f8f19652b3946075f0fc41ad8757dbb68f1ca14e7900ecc1c3" dependencies = [ "log", "tendril", - "web_atoms", + "web_atoms 0.1.3", +] + +[[package]] +name = "markup5ever" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c3294c4d74d0742910f8c7b466f44dda9eb2d5742c1e430138df290a1e8451c" +dependencies = [ + "log", + "tendril", + "web_atoms 0.2.0", ] [[package]] @@ -1508,10 +1531,22 @@ version = "0.35.0+unofficial" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8bcd53df4748257345b8bc156d620340ce0f015ec1c7ef1cff475543888a31d" dependencies = [ - "html5ever", - "markup5ever", + "html5ever 0.35.0", + "markup5ever 0.35.0", + "tendril", + "xml5ever 0.35.0", +] + +[[package]] +name = "markup5ever_rcdom" +version = "0.36.0+unofficial" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e5fc8802e8797c0dfdd2ce5c21aa0aee21abbc7b3b18559100651b3352a7b63" +dependencies = [ + "html5ever 0.36.1", + "markup5ever 0.36.1", "tendril", - "xml5ever", + "xml5ever 0.36.1", ] [[package]] @@ -1593,9 +1628,9 @@ checksum = "dce6dd36094cac388f119d2e9dc82dc730ef91c32a6222170d630e5414b956e6" [[package]] name = "napi" -version = "3.6.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3af30fe8e799dda3a555c496c59e960e4cff1e931b63acbaf3a3b25d9fad22b6" +checksum = "f27a163b545fd2184d2efdccf3d3df56acdb63465f2fcfebcaee0463c1e91783" dependencies = [ "bitflags 2.10.0", "ctor", @@ -1879,6 +1914,16 @@ dependencies = [ "phf_shared 0.11.3", ] +[[package]] +name = "phf_codegen" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", +] + [[package]] name = "phf_generator" version = "0.11.3" @@ -2314,9 +2359,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "siphasher" @@ -2375,6 +2420,19 @@ dependencies = [ "serde", ] +[[package]] +name = "string_cache" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.13.1", + "precomputed-hash", + "serde", +] + [[package]] name = "string_cache_codegen" version = "0.5.4" @@ -2387,6 +2445,18 @@ dependencies = [ "quote", ] +[[package]] +name = "string_cache_codegen" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", + "proc-macro2", + "quote", +] + [[package]] name = "stringprep" version = "0.1.5" @@ -2924,9 +2994,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57ffde1dc01240bdf9992e3205668b235e59421fd085e8a317ed98da0178d414" dependencies = [ "phf 0.11.3", - "phf_codegen", - "string_cache", - "string_cache_codegen", + "phf_codegen 0.11.3", + "string_cache 0.8.9", + "string_cache_codegen 0.5.4", +] + +[[package]] +name = "web_atoms" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acd0c322f146d0f8aad130ce6c187953889359584497dac6561204c8e17bb43d" +dependencies = [ + "phf 0.13.1", + "phf_codegen 0.13.1", + "string_cache 0.9.0", + "string_cache_codegen 0.6.1", ] [[package]] @@ -3332,7 +3414,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee3f1e41afb31a75aef076563b0ad3ecc24f5bd9d12a72b132222664eb76b494" dependencies = [ "log", - "markup5ever", + "markup5ever 0.35.0", +] + +[[package]] +name = "xml5ever" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f57dd51b88a4b9f99f9b55b136abb86210629d61c48117ddb87f567e51e66be7" +dependencies = [ + "log", + "markup5ever 0.36.1", ] [[package]] diff --git a/server/Cargo.lock b/server/Cargo.lock index 8c491c0..6261210 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -1811,9 +1811,9 @@ checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ "icu_collections", "icu_locale_core", @@ -1825,9 +1825,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" From ca3c8e24b55138461034554c1c7a1b9c5316bbc1 Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Tue, 9 Dec 2025 15:52:29 -0600 Subject: [PATCH 25/27] Fix: lock codemirror/view version in lockfile. --- client/pnpm-lock.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/pnpm-lock.yaml b/client/pnpm-lock.yaml index 65cf112..143a904 100644 --- a/client/pnpm-lock.yaml +++ b/client/pnpm-lock.yaml @@ -57,7 +57,7 @@ importers: specifier: ^6.5.2 version: 6.5.2 '@codemirror/view': - specifier: ^6.38.8 + specifier: =6.38.8 version: 6.38.8 '@hpcc-js/wasm-graphviz': specifier: ^1.16.0 From 77f3ede2b486848b1e05b06dde58f31cfd588e66 Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Tue, 9 Dec 2025 16:17:41 -0600 Subject: [PATCH 26/27] Fix: improve test code to wait for server start. --- server/src/ide/vscode/tests.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/server/src/ide/vscode/tests.rs b/server/src/ide/vscode/tests.rs index ab6bdca..a3d1b14 100644 --- a/server/src/ide/vscode/tests.rs +++ b/server/src/ide/vscode/tests.rs @@ -199,16 +199,20 @@ async fn _prep_test( // Ensure the webserver is running. let _ = &*WEBSERVER_HANDLE; let now = SystemTime::now(); - while now.elapsed().unwrap().as_millis() < 100 { + let mut started = false; + while now.elapsed().unwrap().as_millis() < 500 { if minreq::get(format!("http://127.0.0.1:{IP_PORT}/ping",)) .send() .is_ok_and(|response| response.as_bytes() == b"pong") { + started = true; break; } sleep(Duration::from_millis(10)).await; } + assert!(started, "Webserver failed to start."); + // Connect to the VSCode IDE websocket. let ws_ide = connect_async_ide(connection_id).await; let ws_client = connect_async_client(connection_id).await; From 21d4fa1f57568d534b5c10dc91ac129b7cfb43bc Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Tue, 9 Dec 2025 16:28:10 -0600 Subject: [PATCH 27/27] Fix: turn on Rust backtraces for CI runs. --- .github/workflows/check.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 42d4243..d6c5589 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -23,6 +23,10 @@ name: Check on: pull_request: +env: + # Always display a backtrace, to help track down errors. + RUST_BACKTRACE: 1 + jobs: check: strategy: