From efdc4dfb1c0c77f202501d84eccdce2424bb7664 Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Wed, 8 Jul 2026 11:42:30 +0500 Subject: [PATCH 01/11] Fix: more CI resilience. --- server/tests/overall_2.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/server/tests/overall_2.rs b/server/tests/overall_2.rs index 69dce212..faa473a4 100644 --- a/server/tests/overall_2.rs +++ b/server/tests/overall_2.rs @@ -33,7 +33,11 @@ use std::path::PathBuf; use dunce::canonicalize; use indoc::indoc; use pretty_assertions::assert_eq; -use thirtyfour::{By, WebDriver, error::WebDriverError, extensions::query::ElementQueryable}; +use thirtyfour::{ + By, WebDriver, + error::WebDriverError, + extensions::query::{ElementQueryable, ElementWaitable}, +}; // ### Local use crate::overall_common::{ @@ -206,7 +210,11 @@ async fn test_5_core( select_codechat_iframe(&driver).await; // Focus it. - let doc_block_contents = driver.find(By::Css(".CodeChat-doc")).await.unwrap(); + let doc_block_contents = driver + .query(By::Css(".CodeChat-doc")) + .first() + .await + .unwrap(); doc_block_contents.click().await.unwrap(); // The click produces an updated cursor/scroll location after an autosave // delay. @@ -230,6 +238,10 @@ async fn test_5_core( // Refind it, since it's now switched with a TinyMCE editor. let tinymce_contents = driver.query(By::Id("TinyMCE-inst")).first().await.unwrap(); + // Wait for it to become clickable before interacting with it, since the + // switch from the doc block to the TinyMCE editor can leave the element + // briefly present but not yet interactable. + tinymce_contents.wait_until().clickable().await.unwrap(); // Make an edit. tinymce_contents.send_keys("foo").await.unwrap(); From 76bb8045067e26fd71383f2d05b6355686291477 Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Wed, 8 Jul 2026 12:25:52 +0500 Subject: [PATCH 02/11] Docs: review and improve keyboard navigation docs. --- client/src/CodeMirror-integration.mts | 85 ++++++++++++++++----------- 1 file changed, 50 insertions(+), 35 deletions(-) diff --git a/client/src/CodeMirror-integration.mts b/client/src/CodeMirror-integration.mts index ea32eeb0..42b49db8 100644 --- a/client/src/CodeMirror-integration.mts +++ b/client/src/CodeMirror-integration.mts @@ -807,13 +807,37 @@ const on_dirty = ( // // Doc blocks are `Decoration.replace` widgets drawn over empty lines in the // document, so CodeMirror's default cursor movement treats them as an atomic -// region and arrow keys skip over them to the next code block. The keymap below -// intercepts the arrow keys and, when the cursor would move into a doc block, -// dispatches a CodeMirror selection into that block's range instead. That -// selection change is then picked up by `DocBlockPlugin.update`, which focuses -// the block's contents div (the `focusin` handler promotes it to TinyMCE). This -// keeps a single focus path -- the same one used for mouse clicks and -// IDE-driven cursor sync. +// region and arrow keys (mostly) skip over them to the next code block. +// +// ### Specification +// +// The requirements for correct keyboard cursor navigation are: +// +// * When the cursor is located at the beginning of a code/doc block preceded by +// a code/doc block, pressing the left arrow key should move the cursor to the +// end of the preceding code/doc block. +// * When the cursor is located on the first line of a code/doc block preceded +// by a code/doc block, pressing the up arrow key should move the cursor into +// the last line of the preceding code/doc block, moving the cursor as little +// horizontally as possible. +// * When the cursor is located at the end of a code/doc block followed by a +// code/doc block, pressing the right arrow key should move the cursor to the +// beginning of the following code/doc block. +// * When the cursor is located on the last line of a code/doc block followed by +// a code/doc block, pressing the down arrow key should move the cursor to the +// following code/doc block, moving the cursor as little horizontally as +// possible. +// * Pressing the PageUp/PageDown keys should move the cursor by viewport, +// rather than limited cursor movement within the current code/doc block. +// +// ### Implementation notes +// +// The keymap below intercepts the arrow keys and, when the cursor would move +// into a doc block, dispatches a CodeMirror selection into that block's range +// instead. That selection change is then picked up by `DocBlockPlugin.update`, +// which focuses the block's contents div (the `focusin` handler promotes it to +// TinyMCE). This keeps a single focus path -- the same one used for mouse +// clicks and IDE-driven cursor sync. // // Given a doc position `pos`, return the range (`from`/`to`) of the doc block // that starts exactly at `pos`, or `null` if there isn't one. Doc blocks can @@ -822,7 +846,9 @@ const on_dirty = ( // touches `pos` -- otherwise, at a shared boundary, the block ending at `pos` // could be returned instead of the one starting there. const doc_block_starting_at = ( + // The CodeMirror view whose doc blocks are searched. view: EditorView, + // The document position to check for a doc block starting there. pos: number, ): { from: number; to: number } | null => { let found: { from: number; to: number } | null = null; @@ -866,19 +892,12 @@ const select_doc_block_edge = (view: EditorView, pos: number): boolean => { export const docBlockNavKeymap = keymap.of([ { // Down arrow at the bottom of a code block: enter the doc block below, - // caret at its start. Look right after the current line's contents, - // which is where a following doc block's decoration would begin. A - // line's `.to` is the position of (before) its own trailing newline; - // the doc block placeholder that follows starts one position later, - // right after that newline -- hence `+ 1` (see the matching - // `main.head + 1` in the `ArrowRight` handler below, which lands on the - // same position). Chaining from one doc block into the next (once focus - // has actually entered a doc block) happens outside CodeMirror entirely - // -- see `DocBlockPlugin`'s `focusin` handler -- so this keymap only - // ever needs to handle the first entry from a code line; a `main.head` - // that happens to equal a doc block's `to` (this doc block's own - // placeholder boundary) is just the following code line's start, not a - // sign we're "already chained" out of it. + // caret at its start. A line's `.to` sits just before its trailing + // newline, so the following doc block's placeholder starts one position + // later -- hence `+ 1` (matches `main.head + 1` in the `ArrowRight` + // handler below). Chaining from one doc block into the next happens + // outside CodeMirror, in `DocBlockPlugin`'s `focusin` handler, so this + // only needs to handle first entry from a code line. key: "ArrowDown", run: (view) => { const { main } = view.state.selection; @@ -942,9 +961,9 @@ export const docBlockNavKeymap = keymap.of([ } const line = view.state.doc.lineAt(main.head); if (main.head === line.from + 1) { - // One character away from the line's start. If a doc block - // ends exactly at this line's start, the default motion would - // jump straight into it; land the cursor at the line's start + // One character away from the line's start. If a doc block ends + // exactly at this line's start, the default motion would jump + // straight into it; land the cursor at the line's start // instead, so a further ArrowLeft press is needed to enter the // doc block. if (doc_block_ending_at(view, line.from) !== null) { @@ -967,18 +986,14 @@ export const docBlockNavKeymap = keymap.of([ }, { // Home on a code line: same atomic-widget problem as ArrowLeft above, - // but with no "second press" case to fall through to -- Home always - // means "stay on this line," so if a doc block ends exactly at the - // line's start, always stop the cursor there ourselves instead of - // letting the default motion (or `DocBlockPlugin.update`) treat it as - // entry into that doc block. This includes the case where the cursor - // is already at the line's start (a second, redundant Home press): - // even though the selection doesn't move, we still need to dispatch - // with `stayInCodeBlockAnnotation` so `DocBlockPlugin.update` doesn't - // treat the (unchanged) selection as entry into the preceding doc - // block -- falling through to `false` here would let the default Home - // command dispatch a plain selection update instead, without that - // annotation. + // but with no "second press" case -- Home always means "stay on this + // line," so if a doc block ends exactly at the line's start, always + // stop the cursor there ourselves, dispatching with + // `stayInCodeBlockAnnotation` even when the selection doesn't move (a + // redundant Home press), so `DocBlockPlugin.update` doesn't treat it as + // entry into the preceding doc block. Falling through to `false` here + // would let the default Home command dispatch a plain selection update + // instead, without that annotation. key: "Home", run: (view) => { const { main } = view.state.selection; From 45943121bf815bd93962b8ce0eb2d013b6c83a31 Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Sat, 11 Jul 2026 06:27:49 +0500 Subject: [PATCH 03/11] Fix: replace typecasts with instanceof checks. --- client/src/CodeChatEditor.mts | 35 ++++---- client/src/CodeChatEditorFramework.mts | 16 ++-- client/src/CodeMirror-integration.mts | 106 +++++++++++++------------ 3 files changed, 79 insertions(+), 78 deletions(-) diff --git a/client/src/CodeChatEditor.mts b/client/src/CodeChatEditor.mts index fcdcd53f..f9a0cde6 100644 --- a/client/src/CodeChatEditor.mts +++ b/client/src/CodeChatEditor.mts @@ -288,9 +288,8 @@ const _open_lp = async ( // variable. current_metadata = codechat_for_web["metadata"]; const source = codechat_for_web["source"]; - const codechat_body = document.getElementById( - "CodeChat-body", - ) as HTMLDivElement; + const codechat_body = document.getElementById("CodeChat-body"); + assert(codechat_body instanceof HTMLDivElement); if (is_doc_only()) { // Per the // [docs](https://docs.mathjax.org/en/latest/web/typeset.html#updating-previously-typeset-content), @@ -439,9 +438,8 @@ const save_lp = async ( let code_mirror_diffable: CodeMirrorDiffable = {}; if (is_doc_only()) { // Untypeset all math before saving the document. - const codechat_body = document.getElementById( - "CodeChat-body", - ) as HTMLDivElement; + const codechat_body = document.getElementById("CodeChat-body"); + assert(codechat_body instanceof HTMLDivElement); mathJaxUnTypeset(codechat_body); // Use a try/finally to ensure that the document is retypeset even // if errors occur. @@ -496,15 +494,13 @@ export const saveSelection = () => { let current_node = sel.anchorNode; // Continue until we find the div which contains the doc block // contents: either it's not an element (such as a div), ... - current_node.nodeType !== Node.ELEMENT_NODE || + !(current_node instanceof Element) || // or it's not the doc block contents div. - (!(current_node as Element).classList.contains( - "CodeChat-doc-contents", - ) && + (!current_node.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.classList.contains("CodeChat-doc")); current_node = current_node.parentNode! ) { // Store the index of this node in its' parent list of child @@ -538,15 +534,15 @@ export const restoreSelection = ({ // Copy the selection over to TinyMCE by indexing the selection path to find // the selected node. if (selection_path.length && typeof selection_offset === "number") { - let selection_node = tinymce_instance()!.getContentAreaContainer(); + let selection_node: Node = + tinymce_instance()!.getContentAreaContainer(); // Avoid mutating `selection_path` by making a copy of it. const selection_path_copy = [...selection_path]; while (selection_path_copy.length) { - const new_selection_node = selection_node.childNodes[ - selection_path_copy.shift()! - ] as HTMLElement; + const new_selection_node = + selection_node.childNodes[selection_path_copy.shift()!]; // If we get lost during the descent, then stop just before that. - if (new_selection_node === undefined) { + if (!(new_selection_node instanceof Node)) { break; } selection_node = new_selection_node; @@ -748,9 +744,10 @@ export const on_error = (event: Event) => { on_dom_content_loaded(async () => { // Intercept links in this document to save before following the link. window.navigation.addEventListener("navigate", on_navigate); - const ccb = document.getElementById("CodeChat-sidebar") as - HTMLIFrameElement | undefined; - ccb?.contentWindow?.navigation.addEventListener("navigate", on_navigate); + const ccb = document.getElementById("CodeChat-sidebar"); + if (ccb instanceof HTMLIFrameElement) { + ccb.contentWindow?.navigation.addEventListener("navigate", on_navigate); + } document.addEventListener("click", on_click); // Provide basic error reporting for uncaught errors. window.addEventListener("unhandledrejection", on_error); diff --git a/client/src/CodeChatEditorFramework.mts b/client/src/CodeChatEditorFramework.mts index 443bd2b0..01f7b63a 100644 --- a/client/src/CodeChatEditorFramework.mts +++ b/client/src/CodeChatEditorFramework.mts @@ -440,12 +440,12 @@ const set_content = async ( if (client === undefined) { // See if this is the [simple viewer](#Client-simple-viewer). Otherwise, // it's just the bare document to replace. + const contentsElement = + root_iframe!.contentDocument?.getElementById("CodeChat-contents"); const cw = - ( - root_iframe!.contentDocument?.getElementById( - "CodeChat-contents", - ) as HTMLIFrameElement | undefined - )?.contentWindow ?? root_iframe!.contentWindow!; + (contentsElement instanceof HTMLIFrameElement + ? contentsElement.contentWindow + : undefined) ?? root_iframe!.contentWindow!; cw.document.open(); assert("Plain" in contents.source); cw.document.write(contents.source.Plain.doc); @@ -489,9 +489,9 @@ export const page_init = ( webSocketComm = new WebSocketComm( `${protocol}//${window.location.host}/${ws_pathname}`, ); - root_iframe = document.getElementById( - "CodeChat-iframe", - )! as HTMLIFrameElement; + const iframe_element = document.getElementById("CodeChat-iframe"); + assert(iframe_element instanceof HTMLIFrameElement); + root_iframe = iframe_element; window.CodeChatEditorFramework = { webSocketComm, }; diff --git a/client/src/CodeMirror-integration.mts b/client/src/CodeMirror-integration.mts index 42b49db8..fac4e079 100644 --- a/client/src/CodeMirror-integration.mts +++ b/client/src/CodeMirror-integration.mts @@ -272,7 +272,8 @@ export const docBlockField = StateField.define({ if (newlines !== "\n".repeat(newlines.length)) { halt_on_error(`Attempt to overwrite text: "${newlines}".`); } - const prev_widget = prev.spec.widget as DocBlockWidget; + const prev_widget = prev.spec.widget; + assert(prev_widget instanceof DocBlockWidget); doc_blocks = doc_blocks.update({ // Remove the old doc block. We assume there's only one // block in the provided from/to range. @@ -328,7 +329,8 @@ export const docBlockField = StateField.define({ toJSON: (value: DecorationSet, _state: EditorState) => { const json_result = []; for (const iter = value.iter(); iter.value !== null; iter.next()) { - const w = iter.value.spec.widget as DocBlockWidget; + const w = iter.value.spec.widget; + assert(w instanceof DocBlockWidget); json_result.push([ iter.from, iter.to, @@ -512,7 +514,9 @@ class DocBlockWidget extends WidgetType { // sanitized: the server only allows whitespace for the indent; only // specific, safe delimiters are allowed. The Client only allows editing // the indent, and only whitespace is allowed there as well. - (dom.childNodes[0] as HTMLDivElement).innerHTML = this.indent; + const dom_indent = dom.childNodes[0]; + assert(dom_indent instanceof HTMLDivElement); + dom_indent.innerHTML = this.indent; dom.dataset.delimiter = this.delimiter; // Update the contents. The contents div could be a TinyMCE instance, or @@ -690,8 +694,9 @@ export const mathJaxUnTypeset = (node: HTMLElement) => { // Given a doc block div element, return the contents div and if TinyMCE is // attached to that div. -const get_contents = (element: HTMLElement): [HTMLDivElement, boolean] => { - const contents_div = element.childNodes[1] as HTMLDivElement; +const get_contents = (element: Element): [HTMLDivElement, boolean] => { + const contents_div = element.childNodes[1]; + assert(contents_div instanceof HTMLDivElement); const tinymce_inst = tinymce?.get(contents_div.id); // Note the use of `!=` to check both `undefined` (TinyMCE not loaded) and // `null`. @@ -702,15 +707,14 @@ const get_contents = (element: HTMLElement): [HTMLDivElement, boolean] => { // block or not. If not, return false; if so, return the doc block div. const element_is_in_doc_block = ( target: EventTarget | null, -): boolean | HTMLDivElement => { - if (target === null) { - return false; - } - // Look for either a CodeMirror ancestor or a CodeChat doc block ancestor. - const ancestor = (target as HTMLElement).closest(".cm-line, .CodeChat-doc"); - // If it's a doc block, then tell Code Mirror not to handle this event. - if (ancestor?.classList.contains("CodeChat-doc")) { - return ancestor as HTMLDivElement; +): boolean | Element => { + if (target instanceof HTMLElement) { + // Look for either a CodeMirror ancestor or a CodeChat doc block ancestor. + const ancestor = target.closest(".cm-line, .CodeChat-doc"); + // If it's a doc block, then tell CodeMirror not to handle this event. + if (ancestor?.classList.contains("CodeChat-doc")) { + return ancestor; + } } return false; }; @@ -753,9 +757,7 @@ const on_dirty = ( whenReady(async () => { on_dirty_scheduled = false; // Find the doc block parent div. - const target = (event_target as HTMLDivElement).closest( - ".CodeChat-doc", - )! as HTMLDivElement; + const target = event_target.closest(".CodeChat-doc")!; // We can only get the position (the `from` value) for the doc block. // Use this to find the `to` value for the doc block. @@ -767,7 +769,8 @@ const on_dirty = ( return; } // Send an update to the state field associated with this DOM element. - const indent_div = target.childNodes[0] as HTMLDivElement; + const indent_div = target.childNodes[0]; + assert(indent_div instanceof HTMLDivElement); const indent = indent_div.innerHTML; const delimiter = indent_div.getAttribute("data-delimiter")!; const [contents_div, is_tinymce] = get_contents(target); @@ -834,7 +837,7 @@ const on_dirty = ( // // The keymap below intercepts the arrow keys and, when the cursor would move // into a doc block, dispatches a CodeMirror selection into that block's range -// instead. That selection change is then picked up by `DocBlockPlugin.update`, +// instead. That selection change is then picked up by `DocBlockPlugin.update`, // which focuses the block's contents div (the `focusin` handler promotes it to // TinyMCE). This keeps a single focus path -- the same one used for mouse // clicks and IDE-driven cursor sync. @@ -1028,11 +1031,7 @@ export const DocBlockPlugin = ViewPlugin.fromClass( // by the inline `onmousedown` handler in // `DocBlockWidget.toDOM`), don't steal it away into the // contents div. - if ( - (document.activeElement as HTMLElement | null)?.closest( - ".CodeChat-doc-indent", - ) - ) { + if (document.activeElement?.closest(".CodeChat-doc-indent")) { return; } // If one of this update's transactions deliberately stopped the @@ -1070,11 +1069,10 @@ export const DocBlockPlugin = ViewPlugin.fromClass( // Ensure we have a valid dom. This also checks for // undefined. const dom_at_pos = update.view.domAtPos(from); - const dom = dom_at_pos.node.childNodes[ - dom_at_pos.offset - ] as HTMLDivElement | null; + const dom = + dom_at_pos.node.childNodes[dom_at_pos.offset]; if ( - dom == null || + !(dom instanceof HTMLElement) || dom.className !== "CodeChat-doc" ) { return; @@ -1082,7 +1080,8 @@ export const DocBlockPlugin = ViewPlugin.fromClass( // Focus the contents div. This fires the `focusin` // handler, which promotes the block to TinyMCE. - const contents = dom.childNodes[1] as HTMLElement; + const contents = dom.childNodes[1]; + assert(contents instanceof HTMLDivElement); contents.focus(); // Place the caret at the natural edge: when the @@ -1150,13 +1149,15 @@ export const DocBlockPlugin = ViewPlugin.fromClass( // update() method above, but this is VERY slow, since update is // called frequently. focusin: (event: FocusEvent, _view: EditorView) => { - const target_or_false = element_is_in_doc_block(event.target); - if (!target_or_false) { + const event_target = event.target; + const target_or_false = element_is_in_doc_block(event_target); + if (!(target_or_false instanceof HTMLDivElement)) { return false; } // Set up for editing the indent of doc blocks. - const target = target_or_false as HTMLDivElement; - const indent_div = target.childNodes[0] as HTMLDivElement; + const target = target_or_false; + const indent_div = target.childNodes[0]; + assert(indent_div instanceof HTMLDivElement); // Use the // [beforeinput](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/beforeinput_event) // event to allow only whitespace in the indent. Note that @@ -1182,15 +1183,17 @@ export const DocBlockPlugin = ViewPlugin.fromClass( ); indent_div.addEventListener("input", (event) => { // Signal that this indent is dirty. - on_dirty(event.target as HTMLElement); + const target = event.target; + if (target instanceof HTMLElement) { + on_dirty(target); + } }); // If the target is in the indent, not the contents, then the // following code isn't needed. if ( - (event.target as HTMLElement).closest( - ".CodeChat-doc-contents", - ) === null + !(event_target instanceof HTMLDivElement) || + event_target.closest(".CodeChat-doc-contents") === null ) { return false; } @@ -1306,7 +1309,7 @@ export const DocBlockPlugin = ViewPlugin.fromClass( // div TinyMCE stores edits in. const target = event.target.bodyElement; - if (target == null) { + if (target === null) { return; } if (!ignoreTinyMceDirty) { @@ -1316,13 +1319,12 @@ export const DocBlockPlugin = ViewPlugin.fromClass( ); editor.on("input", (event: InputEvent) => { - const target = - event.target as HTMLElement; - if (target == null) { - return; + const target = event.target; + // Sometimes, I see non-elements here. + if (target instanceof HTMLElement) { + ignoreTinyMceDirty = true; + on_dirty(target); } - ignoreTinyMceDirty = true; - on_dirty(target); }); // Send updates on cursor movement. @@ -1446,13 +1448,13 @@ export const DocBlockPlugin = ViewPlugin.fromClass( // // Once the indent loses focus, make it uneditable again. focusout: (event: FocusEvent, _view: EditorView) => { - const indent_div = (event.target as HTMLElement)?.closest( - ".CodeChat-doc-indent", - ) as HTMLDivElement | null; - if (indent_div === null) { - return false; + const target = event.target; + if (target instanceof HTMLElement) { + const indent_div = target.closest(".CodeChat-doc-indent"); + if (indent_div instanceof HTMLElement) { + indent_div.contentEditable = "false"; + } } - indent_div.contentEditable = "false"; return false; }, }, @@ -1697,8 +1699,10 @@ export const CodeMirror_load = async ( }, CodeMirror_JSON_fields, ); + const codechat_div = codechat_body.childNodes[0]; + assert(codechat_div instanceof HTMLDivElement); current_view = new EditorView({ - parent: codechat_body.childNodes[0] as HTMLDivElement, + parent: codechat_div, state, scrollTo: scrollSnapshot, }); From c49638ce00f3f7bcb96d88541079d157fcd32fd9 Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Sat, 11 Jul 2026 09:26:31 +0500 Subject: [PATCH 04/11] Fix: avoid scrolling when applying Server updates to the Client. --- client/src/CodeMirror-integration.mts | 10 + .../test.rs | 3 + server/tests/overall_5.rs | 203 +++++++++++++++++- server/tests/overall_common/mod.rs | 45 +++- 4 files changed, 254 insertions(+), 7 deletions(-) create mode 100644 server/tests/fixtures/overall_5/test_edit_preserves_cursor_scroll_in_large_doc_block/test.rs diff --git a/client/src/CodeMirror-integration.mts b/client/src/CodeMirror-integration.mts index fac4e079..a366b1b5 100644 --- a/client/src/CodeMirror-integration.mts +++ b/client/src/CodeMirror-integration.mts @@ -1707,6 +1707,11 @@ export const CodeMirror_load = async ( scrollTo: scrollSnapshot, }); } else { + // When editing large doc blocks, they may be deleted then re-created by + // CodeMirror, which causes unexpected scrolling. To avoid this, save + // then restore the scroll after updating CodeMirror. + const currentScrollTop = current_view.scrollDOM.scrollTop; + // This contains a diff, instead of plain text. Apply the text diff. // // First, apply just the text edits. Use an annotation so that the doc @@ -1749,6 +1754,11 @@ export const CodeMirror_load = async ( effects: stateEffects, annotations: noAutosaveAnnotation.of(true), }); + + // Restore the scroll position. + requestAnimationFrame( + () => (current_view.scrollDOM.scrollTop = currentScrollTop), + ); } scroll_to_line(cursor_position, scroll_line); }; diff --git a/server/tests/fixtures/overall_5/test_edit_preserves_cursor_scroll_in_large_doc_block/test.rs b/server/tests/fixtures/overall_5/test_edit_preserves_cursor_scroll_in_large_doc_block/test.rs new file mode 100644 index 00000000..3b1386f5 --- /dev/null +++ b/server/tests/fixtures/overall_5/test_edit_preserves_cursor_scroll_in_large_doc_block/test.rs @@ -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_5.rs b/server/tests/overall_5.rs index 8c7583d5..8bb65039 100644 --- a/server/tests/overall_5.rs +++ b/server/tests/overall_5.rs @@ -40,21 +40,207 @@ use thirtyfour::{By, Key, WebDriver, error::WebDriverError}; // ### Local use crate::overall_common::{ CodeChatEditorServerLog, TIMEOUT, assert_no_more_messages, beginning_of_line, end_of_line, - perform_loadfile, select_codechat_iframe, + get_version, perform_loadfile, select_codechat_iframe, }; -use code_chat_editor::webserver::{ - CursorPosition, EditorMessage, EditorMessageContents, INITIAL_CLIENT_MESSAGE_ID, - MESSAGE_ID_INCREMENT, UpdateMessageContents, +use code_chat_editor::{ + processing::{ + CodeChatForWeb, CodeMirrorDiff, CodeMirrorDiffable, SourceFileMetadata, StringDiff, + }, + webserver::{ + CursorPosition, EditorMessage, EditorMessageContents, INITIAL_CLIENT_MESSAGE_ID, + MESSAGE_ID_INCREMENT, ResultOkTypes, UpdateMessageContents, + }, }; use test_utils::prep_test_dir; // Tests // ----- make_test!( - test_cursor_home_from_code_after_doc_block, - test_cursor_home_from_code_after_doc_block_core + test_edit_preserves_cursor_scroll_in_large_doc_block, + test_edit_preserves_cursor_scroll_in_large_doc_block_core ); +// Regression/stress test for the scroll-preservation logic when editing long doc blocks. This test builds a large document -- a big +// (100-paragraph) doc block, followed by 100 alternating one-character +// code/doc block pairs -- edits a paragraph in the middle of the big doc +// block, and checks that both the reported cursor and scroll position are +// unchanged afterward. +async fn test_edit_preserves_cursor_scroll_in_large_doc_block_core( + codechat_server: CodeChatEditorServerLog, + driver: WebDriver, + test_dir: PathBuf, +) -> Result<(), WebDriverError> { + let path = canonicalize(test_dir.join("test.rs")).unwrap(); + let path_str = path.to_str().unwrap().to_string(); + let ide_version = 0.0; + let version = ide_version; + + // A one-line doc block (plain `//` comment). + let mut orig_text = "// L1\n/// Pt\n/// ==\n///\n".to_string(); + // A 100-paragraph doc block (rustdoc-style `///` comments). + // Since the delimiter (`///`) differs from the preceding block's (`//`), + // these lines don't merge into the first doc block. This also produces an empty line at the end of these paragraphs, which will cause a server re-translation when it cleans this up. + for i in 0..100 { + orig_text += &format!("/// P{i}\n///\n"); + } + // 100 instances of a one-line code block, then a one-line doc block. + for i in 0..100 { + orig_text += &format!("{i}\n// {i}\n"); + } + + let server_id = perform_loadfile( + &codechat_server, + &test_dir, + "test.rs", + Some((orig_text, ide_version)), + false, + 6.0, + ) + .await; + + // Target the iframe containing the Client. + select_codechat_iframe(&driver).await; + + let mut client_id = INITIAL_CLIENT_MESSAGE_ID; + + // The 100-paragraph doc block is the second `.CodeChat-doc-contents` div + // (the first is the single-line "L1" doc block). + let doc_blocks = driver + .find_all(By::Css(".CodeChat-CodeMirror .CodeChat-doc-contents")) + .await + .unwrap(); + let big_doc_block = &doc_blocks[1]; + + // Click a paragraph in the middle of the big doc block, to focus it and + // place the cursor there. + let paragraphs = big_doc_block.find_all(By::Css("p")).await.unwrap(); + assert_eq!( + paragraphs.len(), + 100, + "Expected 100 paragraphs in the big doc block." + ); + let middle_paragraph = ¶graphs[50]; + middle_paragraph.click().await.unwrap(); + + // The click produces an updated cursor/scroll location after an autosave + // delay. Record it as the baseline to compare against after the edit. + let msg_before = codechat_server.get_message_timeout(TIMEOUT).await.unwrap(); + assert_eq!(msg_before.id, client_id); + let UpdateMessageContents { + cursor_position: cursor_position_before, + scroll_position: scroll_position_before, + .. + } = match msg_before.message { + EditorMessageContents::Update(contents) => contents, + other => panic!("Expected an Update message, got: {other:#?}"), + }; + codechat_server.send_result(client_id, None).await.unwrap(); + client_id += MESSAGE_ID_INCREMENT; + + // Make an edit in the middle of the big doc block: refind the paragraph, + // since it's now switched to a TinyMCE editor, then type a character. + let tinymce_contents = driver.find(By::Id("TinyMCE-inst")).await.unwrap(); + tinymce_contents.send_keys("x").await.unwrap(); + + // A cursor-only update (carrying no `contents`) may precede the text + // update carrying the edit; accept and skip any of those, then inspect + // the first update that does carry contents. + let msg = loop { + let msg = codechat_server.get_message_timeout(TIMEOUT).await.unwrap(); + assert_eq!(msg.id, client_id); + let is_cursor_only = matches!( + &msg.message, + EditorMessageContents::Update(UpdateMessageContents { contents: None, .. }) + ); + if !is_cursor_only { + break msg; + } + codechat_server.send_result(client_id, None).await.unwrap(); + client_id += MESSAGE_ID_INCREMENT; + }; + // use std::time::Duration; + // use tokio::time::sleep; + // sleep(Duration::from_hours(1)).await; + + let client_version = get_version(&msg); + assert_eq!( + msg, + EditorMessage { + id: client_id, + message: EditorMessageContents::Update(UpdateMessageContents { + file_path: path_str.clone(), + cursor_position: Some(CursorPosition::Line(105)), + scroll_position: Some(1.0), + is_re_translation: false, + contents: Some(CodeChatForWeb { + metadata: SourceFileMetadata { + mode: "rust".to_string(), + }, + source: CodeMirrorDiffable::Diff(CodeMirrorDiff { + doc: vec![ + StringDiff { + from: 614, + to: Some(622), + insert: "/// P50x\n".to_string() + }, + // The server removes the empty line after the last paragraph in the big doc block. This also causes a re-translation. + StringDiff { + from: 1210, + to: Some(1214), + insert: "".to_string() + } + ], + doc_blocks: vec![], + version, + }), + version: client_version, + }), + }) + } + ); + codechat_server.send_result(client_id, None).await.unwrap(); + client_id += MESSAGE_ID_INCREMENT; + + // Editing a doc block prompts the Server to send the Client a + // re-translated version of the document; the Client's acknowledgement + // comes back here as a `Result(Ok)` carrying the Server's ID. + assert_eq!( + codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), + EditorMessage { + id: server_id, + message: EditorMessageContents::Result(Ok(ResultOkTypes::Void)) + } + ); + + // The re-translation settles the cursor, producing a final cursor/scroll + // update from the Client. Verify neither the cursor nor the scroll + // position moved as a result of the edit. + let msg_after = codechat_server.get_message_timeout(TIMEOUT).await.unwrap(); + assert_eq!(msg_after.id, client_id); + let UpdateMessageContents { + cursor_position: cursor_position_after, + scroll_position: scroll_position_after, + .. + } = match msg_after.message { + EditorMessageContents::Update(contents) => contents, + other => panic!("Expected an Update message, got: {other:#?}"), + }; + assert_eq!( + cursor_position_after, cursor_position_before, + "Cursor position changed after editing the middle of the large doc block." + ); + assert_eq!( + scroll_position_after, scroll_position_before, + "Scroll position changed after editing the middle of the large doc block." + ); + codechat_server.send_result(client_id, None).await.unwrap(); + //client_id += MESSAGE_ID_INCREMENT; + + assert_no_more_messages(&codechat_server).await; + + Ok(()) +} + // Regression test to ensure left arrow allows placing the cursor at the // beginning of a code block, then moves back to the end of the preceding doc // block on another left arrow press. The preceding doc block here spans two @@ -65,6 +251,11 @@ make_test!( // rather than moving up into the preceding doc block, and that pressing Home // a second time (a no-op, since the cursor is already at the start of the // line) also keeps the cursor on the current line. +make_test!( + test_cursor_home_from_code_after_doc_block, + test_cursor_home_from_code_after_doc_block_core +); + async fn test_cursor_home_from_code_after_doc_block_core( codechat_server: CodeChatEditorServerLog, driver: WebDriver, diff --git a/server/tests/overall_common/mod.rs b/server/tests/overall_common/mod.rs index 38be0205..f12938d6 100644 --- a/server/tests/overall_common/mod.rs +++ b/server/tests/overall_common/mod.rs @@ -52,6 +52,7 @@ use assert_fs::TempDir; use dunce::canonicalize; use futures::FutureExt; use pretty_assertions::assert_eq; +use serde_json::Value; use thirtyfour::{ BrowserLogEntry, By, ChromiumLikeCapabilities, DesiredCapabilities, Key, LoggingPrefsLogLevel, TypingData, WebDriver, WebElement, error::WebDriverError, @@ -361,6 +362,44 @@ pub async fn harness< )))) } +/// Decode a `BrowserLogEntry::message` produced by a `console.*` call. +/// chromedriver formats these as +/// ` : `, where the serialized +/// message is the JSON encoding of each argument passed to `console.*` +/// (strings included, hence the embedded quotes/escapes). Strip the +/// ` :` prefix and decode the remainder, falling +/// back to the raw message if it doesn't match the expected shape. +fn decode_console_message(message: &str) -> String { + // Split off the ` :` prefix: the first two + // whitespace-separated fields. + let mut parts = message.splitn(3, ' '); + let (Some(_source_url), Some(_line_col), Some(serialized)) = + (parts.next(), parts.next(), parts.next()) + else { + return message.to_string(); + }; + + // The serialized message is a whitespace-separated sequence of + // JSON-encoded values (one per `console.*` argument). Decode each, + // rendering strings without their surrounding quotes and falling back to + // the raw text for anything that isn't valid JSON. + let mut decoded_parts = Vec::new(); + let mut deserializer = serde_json::Deserializer::from_str(serialized).into_iter::(); + for value in &mut deserializer { + match value { + Ok(Value::String(s)) => decoded_parts.push(s), + Ok(other) => decoded_parts.push(other.to_string()), + Err(_) => return message.to_string(), + } + } + // If there's leftover, unparsed text, give up and return the raw message. + if !serialized[deserializer.byte_offset()..].trim().is_empty() { + return message.to_string(); + } + + decoded_parts.join(" ") +} + /// Drain the browser's `console.*` / uncaught-error log buffer, re-emit each /// entry through the Rust `tracing` macros (mapping the Selenium log level to /// the closest Rust log level), and return the drained entries so callers can @@ -381,7 +420,11 @@ async fn forward_browser_logs(driver: &WebDriver) -> Vec { for entry in &entries { // chromedriver emits SCREAMING levels (`SEVERE`, `WARNING`, `INFO`, // `DEBUG`, `FINE`, ...). Map them onto Rust log levels. - let msg = format!("JS console [{}]: {}", entry.level, entry.message); + let msg = format!( + "JS console [{}]: {}", + entry.level, + decode_console_message(&entry.message) + ); match entry.level.as_str() { "SEVERE" => error!("{msg}"), "WARNING" => warn!("{msg}"), From 114147006f0966386d951be8c0dd61f4066e7bee Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Sat, 11 Jul 2026 09:26:55 +0500 Subject: [PATCH 05/11] Clean: remove overlong comments. --- server/tests/overall_4.rs | 88 --------------------------------------- 1 file changed, 88 deletions(-) diff --git a/server/tests/overall_4.rs b/server/tests/overall_4.rs index 404c48d2..9bc93198 100644 --- a/server/tests/overall_4.rs +++ b/server/tests/overall_4.rs @@ -358,67 +358,6 @@ async fn test_horizontal_scroll_preserved_core( make_test!(test_arrow_key_navigation, test_arrow_key_navigation_core); -// Regression test replacing the old Client-only unit test (which drove -// `docBlockNavKeymap` directly by calling `runScopeHandlers` on a bare -// CodeMirror instance, with hand-built `doc`/`doc_blocks` data). That approach -// never reflected real behavior: its synthetic `doc` string collapsed a code -// line's own trailing newline with the following doc block's placeholder -// newline (e.g. `"a\nb\n\nc"`, where offset 3 -- "b"'s own newline -- doubled -// as doc block 1's placeholder). The real Server never produces this shape: -// it always appends each doc block's placeholder newline(s) *in addition to* -// the preceding code block's own trailing newline (see the comment above -// `source.push_str(&"\n".repeat(doc_block.lines))` in -// `processing.rs::source_to_codechat_for_web`). -// -// Against a real document, that one-character difference used to matter: -// `docBlockNavKeymap`'s `ArrowDown` handler computed -// `search_pos = lineAt(main.head).to`, which lands on the code line's own -// newline -- one position *before* where the doc block actually starts -- so -// the lookup never matched, the keymap reported the key unhandled, and -// CodeMirror's default `ArrowDown` jumped straight over the (atomic) doc block -// widgets to the next real code line instead. This was a real, reproducible -// off-by-one bug the old synthetic test could never have caught; it's now -// fixed by using `lineAt(main.head).to + 1`, matching the `main.head + 1` -// already used by the analogous `ArrowRight` handler. `ArrowUp`'s -// `search_pos = lineAt(main.head).from` never had this problem, since a doc -// block's placeholder newline(s) sit immediately before the following code -// line with nothing in between. -// -// Fixing the off-by-one also exposed a second, related bug: both handlers had -// a "chained navigation" branch that checked whether `main.head` already sat -// at a doc block boundary, to decide whether this was a continued chain -// through consecutive doc blocks rather than a fresh entry from a code line. -// But `main.head` sitting at a doc block's boundary is exactly what a fresh -// arrival at the neighboring code line's edge looks like too (a doc block's -// `to` is numerically identical to the following code line's `from`), so the -// branch misfired on fresh entries, treating them as chained and skipping -// straight past the intended doc block. In practice this branch was -// unreachable for its intended purpose anyway: chaining between two -// consecutive, already-focused doc blocks happens entirely outside CodeMirror -// (via the browser's native contenteditable caret handling and -// `DocBlockPlugin`'s `focusin` promotion -- see the comment further below), -// so by the time a second consecutive doc-block-entering keypress could -// occur, CodeMirror would no longer even have focus for `docBlockNavKeymap` -// to run. Both handlers now just look at the fixed boundary position with no -// "chained" special case. -// -// This test drives real keyboard input through WebDriver, so each -// `ArrowDown`/`ArrowUp` goes wherever the browser's actual focus is -- exactly -// as a user's keystrokes would -- rather than assuming CodeMirror stays -// focused across every keypress the way the old test did. It uses the same -// document shape as the old test: a code block (`a`, `b`), two consecutive -// one-line doc blocks with different indents (so they remain separate blocks -// -- see the merge rule in `lexer.rs`), then another code block (`c`). -// -// After each keypress, the test waits for the autosave timer to fire and -// checks the `cursor_position` the Client reports back to the IDE. A doc -// block's cursor is computed from `document.activeElement` client-side (see -// `set_CodeMirror_positions` in `CodeMirror-integration.mts`) and sent to the -// Server as a `DomLocation`, which the Server then translates into the -// doc block's source line number before forwarding to the IDE (see the -// comment on `CursorPosition::DomLocation` in `webserver.rs`) -- so a `Line` -// value naming a doc block's line is proof that real DOM focus, not just -// CodeMirror's internal selection, moved into that block. async fn test_arrow_key_navigation_core( codechat_server: CodeChatEditorServerLog, driver: WebDriver, @@ -619,33 +558,6 @@ make_test!( // fact), which isn't enough to catch this -- entering a one-line doc block, // its first line and its last line are the same line, so an off-by-one in // "first vs. last" can't show up there. -// -// This test uses six consecutive comment lines sharing the same indent -// ("# 3", "#", then four "# " lines), which the lexer merges -// into a *single* six-line doc block (see the check `last_doc_block.indent == -// indent && last_doc_block.delimiter == delimiter` in `lexer.rs`, which -// appends each comment's contents to the previous one rather than starting a -// new doc block). The blank `#` line forces the Markdown contents to render -// as two separate paragraphs rather than one line-wrapped paragraph (matching -// the `// One`, `//`, `// Two` pattern used by the analogous Rust unit test in -// `processing/tests.rs`); without it, CommonMark would join "3" and the -// second paragraph into a single visual line, unable to expose a -// first-vs-last-line bug. The second paragraph's four lines are themselves -// pre-wrapped (each already at the Server's own word-wrap width for this doc -// block -- see the `wrapped_line` comment below), so CommonMark's soft-wrap -// rule joins them into one long visual paragraph *without* the Server's -// HTML-to-Markdown caret-location logic (`doc_block_html_to_markdown` in -// `processing.rs`) needing to invent any new line breaks when it re-wraps -// them to locate the caret. That matters: an *unwrapped* single long source -// line for this paragraph also fails this test, but for a different reason -// than a beginning/end caret mix-up -- the re-wrap invents extra line breaks -// not present in the actual CodeMirror source, inflating the reported line -// number past the end of the document entirely. Pre-wrapping the source -// avoids that confound, isolating this test to the beginning-vs-end caret -// question alone; both the line-number check and a direct DOM caret-position -// check below pass under these conditions, indicating that particular defect -// doesn't reproduce here. That doc block spans CodeMirror lines 3-8; the -// following code line "c" is line 9. async fn test_arrow_key_navigation_multiline_doc_block_core( codechat_server: CodeChatEditorServerLog, driver: WebDriver, From df17853f2e47d2a43fe55544ae1ddb43b0890e4e Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Sat, 11 Jul 2026 09:32:55 +0500 Subject: [PATCH 06/11] Docs: Update changelog. --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 666662c7..afc9f6f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,9 @@ Changelog [Github master](https://github.com/bjones1/CodeChat_Editor) ----------------------------------------------------------- -* No changes. +* Fix unexpected scrolling when server updates are applied to a doc block that + exceeds the screen height. +* Fix errors due to incorrect TypeScript typecasts. Version 0.1.60 -- 2026-Jul-08 ----------------------------- From 792aab5dfe3e0b6d25c5b79fa839ab0e9714ddb3 Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Sat, 11 Jul 2026 09:55:35 +0500 Subject: [PATCH 07/11] Fix: correctly update delimiter. --- CHANGELOG.md | 1 + client/src/CodeMirror-integration.mts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index afc9f6f7..ee27a1cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ Changelog * Fix unexpected scrolling when server updates are applied to a doc block that exceeds the screen height. * Fix errors due to incorrect TypeScript typecasts. +* Correctly update the delimiter when a doc block is updated. Version 0.1.60 -- 2026-Jul-08 ----------------------------- diff --git a/client/src/CodeMirror-integration.mts b/client/src/CodeMirror-integration.mts index a366b1b5..f826fc7e 100644 --- a/client/src/CodeMirror-integration.mts +++ b/client/src/CodeMirror-integration.mts @@ -517,7 +517,7 @@ class DocBlockWidget extends WidgetType { const dom_indent = dom.childNodes[0]; assert(dom_indent instanceof HTMLDivElement); dom_indent.innerHTML = this.indent; - dom.dataset.delimiter = this.delimiter; + dom_indent.dataset.delimiter = this.delimiter; // Update the contents. The contents div could be a TinyMCE instance, or // just a plain div. Handle both cases. Again, we assume sanitized From f3ff51a26a5e14d02c3fb4de7c5a4c6dd4d81f00 Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Sat, 11 Jul 2026 10:01:37 +0500 Subject: [PATCH 08/11] Clean: wrap. --- client/src/CodeMirror-integration.mts | 3 +- server/src/main.rs | 15 ++-- server/src/processing.rs | 13 ++- server/tests/overall_4.rs | 114 +++++++++++++------------- server/tests/overall_5.rs | 93 +++++++++++---------- server/tests/overall_common/mod.rs | 103 ++++++++++++----------- 6 files changed, 172 insertions(+), 169 deletions(-) diff --git a/client/src/CodeMirror-integration.mts b/client/src/CodeMirror-integration.mts index f826fc7e..ddb86c62 100644 --- a/client/src/CodeMirror-integration.mts +++ b/client/src/CodeMirror-integration.mts @@ -709,7 +709,8 @@ const element_is_in_doc_block = ( target: EventTarget | null, ): boolean | Element => { if (target instanceof HTMLElement) { - // Look for either a CodeMirror ancestor or a CodeChat doc block ancestor. + // Look for either a CodeMirror ancestor or a CodeChat doc block + // ancestor. const ancestor = target.closest(".cm-line, .CodeChat-doc"); // If it's a doc block, then tell CodeMirror not to handle this event. if (ancestor?.classList.contains("CodeChat-doc")) { diff --git a/server/src/main.rs b/server/src/main.rs index 8406e7e5..21e504fb 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -19,13 +19,13 @@ //! This file implements the command-line interface (CLI) for the CodeChat //! Editor server binary. It provides three subcommands: //! -//! - `serve`: Start the webserver in the foreground. -//! - `start`: Spawn the webserver as a background child process, polling -//! until it responds to a ping, then optionally open a browser. -//! - `stop`: Send a stop request to a running server instance. +//! * `serve`: Start the webserver in the foreground. +//! * `start`: Spawn the webserver as a background child process, polling until +//! it responds to a ping, then optionally open a browser. +//! * `stop`: Send a stop request to a running server instance. //! -//! All subcommands accept `--host` and `--port` options to control the -//! server's network address. +//! All subcommands accept `--host` and `--port` options to control the server's +//! network address. // Imports // ------- // @@ -339,7 +339,8 @@ fn parse_credentials(s: &str) -> Result { }) } -/// This is used by `ping` to transform the "access connections from any address" address into localhost, a valid destination address for a ping. +/// This is used by `ping` to transform the "access connections from any +/// address" address into localhost, a valid destination address for a ping. fn fix_addr(addr: &SocketAddr) -> SocketAddr { if addr.ip() == IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)) { let mut addr = *addr; diff --git a/server/src/processing.rs b/server/src/processing.rs index bd960f4d..1f595fce 100644 --- a/server/src/processing.rs +++ b/server/src/processing.rs @@ -249,9 +249,8 @@ pub enum TranslationResultsString { lazy_static! { /// Match the lexer directive in a source file. static ref LEXER_DIRECTIVE: Regex = Regex::new(r"CodeChat Editor lexer: (\w+)").unwrap(); - /// If this matches, it means an - /// unterminated fenced code block. This should be replaced with the - /// `` terminator. + /// If this matches, it means an unterminated fenced code block. This should + /// be replaced with the `` terminator. static ref DOC_BLOCK_SEPARATOR_BROKEN_FENCE: Regex = Regex::new(concat!( // Allow the `.` wildcard to match newlines. "(?s)", @@ -315,8 +314,7 @@ 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"; -// - +// // The column at which to word wrap doc blocks. const WORD_WRAP_COLUMN: usize = 80; // The minimum width for doc block word wrap, since large indents may leave @@ -926,7 +924,7 @@ pub fn source_to_codechat_for_web( // Convert the Markdown to HTML. let html = markdown_to_html(&doc_contents); - // Break it back into doc blocks: + // Break it back into doc blocks: // // 1. Mend broken fences. let html = DOC_BLOCK_SEPARATOR_BROKEN_FENCE @@ -938,8 +936,7 @@ pub fn source_to_codechat_for_web( .map_err(|e| SourceToCodeChatForWebError::ParseFailed(e.to_string()))?; // 4. Split on the separator. let mut doc_block_contents_iter = html.split(DOC_BLOCK_SEPARATOR_SPLIT_STRING); - // - + // // Translate each `CodeDocBlock` to its `CodeMirror` equivalent. let mut len = len_utf16(&code_mirror.doc); for code_or_doc_block in code_doc_block_arr { diff --git a/server/tests/overall_4.rs b/server/tests/overall_4.rs index 9bc93198..a391bb95 100644 --- a/server/tests/overall_4.rs +++ b/server/tests/overall_4.rs @@ -366,10 +366,10 @@ async fn test_arrow_key_navigation_core( let path = canonicalize(test_dir.join("test.py")).unwrap(); let path_str = path.to_str().unwrap().to_string(); let ide_version = 0.0; - // Source lines, and the CodeMirror line each becomes on the Client: - // "a" -> 1, "b" -> 2, "# 3" (doc block 1, indent "") -> 3, - // " # 4" (doc block 2, indent " ") -> 4, "c" -> 5. The differing indent - // keeps the two doc blocks separate instead of merging into one. + // Source lines, and the CodeMirror line each becomes on the Client: "a" -> + // 1, "b" -> 2, "# 3" (doc block 1, indent "") -> 3, " # 4" (doc block 2, + // indent " ") -> 4, "c" -> 5. The differing indent keeps the two doc blocks + // separate instead of merging into one. let orig_text = "a\nb\n# 3\n # 4\nc\n".to_string(); perform_loadfile( &codechat_server, @@ -413,8 +413,8 @@ async fn test_arrow_key_navigation_core( // ### `ArrowDown` from a code line enters the doc block below it. // - // Click near the start of line "b" (the last line of the top code - // block), then move to its end with `End`, which is the boundary + // Click near the start of line "b" (the last line of the top code block), + // then move to its end with `End`, which is the boundary // `docBlockNavKeymap`'s `ArrowDown` handler looks for. let code_lines = driver .find_all(By::Css(".CodeChat-CodeMirror .cm-line")) @@ -427,9 +427,9 @@ async fn test_arrow_key_navigation_core( end_of_line(&code_lines[1], "").await.unwrap(); assert_cursor_line(&codechat_server, &mut client_id, &path_str, 2).await; - // With the off-by-one fixed, this moves focus into the first doc block - // ("# 3", line 3) rather than skipping both (atomic) doc block widgets to - // land on "c" (line 5). + // With the off-by-one fixed, this moves focus into the first doc block ("# + // 3", line 3) rather than skipping both (atomic) doc block widgets to land + // on "c" (line 5). driver .action_chain() .send_keys(Key::Down) @@ -446,9 +446,9 @@ async fn test_arrow_key_navigation_core( // already at the very end of that block's content) moves focus into the // second, following doc block -- the browser's default caret-boundary // handling for the contenteditable region falls through to the adjacent - // `.CodeChat-doc-contents` div, which then goes through the same - // `focusin` promotion as any other doc block. Doc block 2 (" # 4") - // translates to line 4. + // `.CodeChat-doc-contents` div, which then goes through the same `focusin` + // promotion as any other doc block. Doc block 2 (" # 4") translates to + // line 4. driver .action_chain() .send_keys(Key::Down) @@ -464,11 +464,11 @@ async fn test_arrow_key_navigation_core( // CodeMirror's, so unlike the doc-block-to-doc-block case above (which // works because both blocks are DOM siblings the browser's default // caret-boundary handling walks between), nothing hands focus back to - // CodeMirror when there's no further doc block to chain into via that - // same mechanism. In practice, though, the browser's caret-boundary walk + // CodeMirror when there's no further doc block to chain into via that same + // mechanism. In practice, though, the browser's caret-boundary walk // continues past doc block 2's contents into its `.CodeChat-doc` sibling - // structure and on to the next code line "c" (line 5) once the indent - // div is no longer permanently `contenteditable` (see the `mousedown`/ + // structure and on to the next code line "c" (line 5) once the indent div + // is no longer permanently `contenteditable` (see the `mousedown`/ // `focusout` handlers in `DocBlockPlugin`, which toggle it instead). driver .action_chain() @@ -483,10 +483,9 @@ async fn test_arrow_key_navigation_core( // `ArrowUp`'s boundary math has no off-by-one (a doc block's placeholder // newline(s) sit immediately before the following code line), so this // direction has always worked. Click directly on code line "c" to give - // CodeMirror real focus there (the doc block gap above left focus stuck - // in doc block 2's TinyMCE instance), then press `Home` so the cursor - // sits at the exact line start `docBlockNavKeymap`'s `ArrowUp` handler - // looks for. + // CodeMirror real focus there (the doc block gap above left focus stuck in + // doc block 2's TinyMCE instance), then press `Home` so the cursor sits at + // the exact line start `docBlockNavKeymap`'s `ArrowUp` handler looks for. let c_line = driver .find(By::XPath("//*[contains(@class, 'cm-line')][text()='c']")) .await @@ -513,26 +512,27 @@ async fn test_arrow_key_navigation_core( // The Client reports the doc block's cursor as a `DomLocation` (a DOM // coordinate), but the Server translates that into a plain `Line` before // forwarding to the IDE -- `DomLocation` is a Client/Server-only detail - // (see the comment on `CursorPosition::DomLocation` in `webserver.rs`). - // Doc block 2 (" # 4") translates to line 4. + // (see the comment on `CursorPosition::DomLocation` in `webserver.rs`). Doc + // block 2 (" # 4") translates to line 4. assert_cursor_line(&codechat_server, &mut client_id, &path_str, 4).await; // ### Chaining `ArrowUp` between two consecutive doc blocks overshoots + // // straight to the code above, skipping doc block 1. // - // Focus is now genuinely in the second doc block's - // `.CodeChat-doc-contents` div (promoted to TinyMCE), outside CodeMirror - // and thus outside `docBlockNavKeymap`, with the caret at the very end of - // that block's content ("entering from below" lands the caret at the - // end; see `DocBlockPlugin`'s `focusin` handler). Each doc block's - // wrapper (`.CodeChat-doc`) is no longer permanently `contenteditable` - // on its indent (see the `mousedown`/`focusout` handlers in - // `DocBlockPlugin`), so the browser's native caret-boundary walk now - // carries `ArrowUp` all the way from doc block 2's contents, through doc - // block 1's (very short, single-character) contents, and out the other - // side into code line "b" (line 2) in a single keypress -- reported as a - // plain `Line`, not a `DomLocation`, confirming focus landed in - // CodeMirror rather than a doc block's DOM. + // Focus is now genuinely in the second doc block's `.CodeChat-doc-contents` + // div (promoted to TinyMCE), outside CodeMirror and thus outside + // `docBlockNavKeymap`, with the caret at the very end of that block's + // content ("entering from below" lands the caret at the end; see + // `DocBlockPlugin`'s `focusin` handler). Each doc block's wrapper + // (`.CodeChat-doc`) is no longer permanently `contenteditable` on its + // indent (see the `mousedown`/`focusout` handlers in `DocBlockPlugin`), so + // the browser's native caret-boundary walk now carries `ArrowUp` all the + // way from doc block 2's contents, through doc block 1's (very short, + // single-character) contents, and out the other side into code line "b" + // (line 2) in a single keypress -- reported as a plain `Line`, not a + // `DomLocation`, confirming focus landed in CodeMirror rather than a doc + // block's DOM. driver .action_chain() .send_keys(Key::Up) @@ -554,10 +554,10 @@ make_test!( // Regression test for a manually-observed bug: moving the cursor from a code // line to a *multi-line* doc block places the cursor at the doc block's first // line rather than its last line. `test_arrow_key_navigation_core` above only -// exercises single-line doc blocks (two separate one-line doc blocks, in -// fact), which isn't enough to catch this -- entering a one-line doc block, -// its first line and its last line are the same line, so an off-by-one in -// "first vs. last" can't show up there. +// exercises single-line doc blocks (two separate one-line doc blocks, in fact), +// which isn't enough to catch this -- entering a one-line doc block, its first +// line and its last line are the same line, so an off-by-one in "first vs. +// last" can't show up there. async fn test_arrow_key_navigation_multiline_doc_block_core( codechat_server: CodeChatEditorServerLog, driver: WebDriver, @@ -568,19 +568,19 @@ async fn test_arrow_key_navigation_multiline_doc_block_core( let ide_version = 0.0; // A paragraph line, pre-wrapped at the same width the Server's own word // wrap (`WORD_WRAP_COLUMN` minus this doc block's delimiter-plus-space - // width, in `processing.rs`) produces for this doc block's indent ("") - // and delimiter ("#") -- confirmed by feeding this exact paragraph - // through `doc_block_html_to_markdown` directly, which wraps it into four - // lines of "four" repeated 15 times each (74 characters). Repeating that + // width, in `processing.rs`) produces for this doc block's indent ("") and + // delimiter ("#") -- confirmed by feeding this exact paragraph through + // `doc_block_html_to_markdown` directly, which wraps it into four lines of + // "four" repeated 15 times each (74 characters). Repeating that // already-wrapped line four times below reproduces the Server's own wrap // points exactly, so its HTML-to-Markdown re-wrap (done to locate the // caret) doesn't invent any new line breaks. let wrapped_line = std::iter::repeat_n("four", 15) .collect::>() .join(" "); - // Source lines, and the CodeMirror line each becomes on the Client: - // "a" -> 1, "b" -> 2, "# 3" + "#" + four "# " lines (one - // merged six-line doc block, indent "") -> 3-8, "c" -> 9. + // Source lines, and the CodeMirror line each becomes on the Client: "a" -> + // 1, "b" -> 2, "# 3" + "#" + four "# \" lines (one merged + // six-line doc block, indent "") -> 3-8, "c" -> 9. let orig_text = format!( "a\nb\n# 3\n#\n# {wrapped_line}\n# {wrapped_line}\n# {wrapped_line}\n# {wrapped_line}\nc\n" ); @@ -629,8 +629,8 @@ async fn test_arrow_key_navigation_multiline_doc_block_core( // `Home` so the cursor sits at the exact line start `docBlockNavKeymap`'s // `ArrowUp` handler looks for. // - // Confirm the click genuinely lands on code line "c" (i.e. `Line(9)`), - // not inside the preceding doc block. + // Confirm the click genuinely lands on code line "c" (i.e. `Line(9)`), not + // inside the preceding doc block. let c_line = driver .find(By::XPath("//*[contains(@class, 'cm-line')][text()='c']")) .await @@ -639,9 +639,9 @@ async fn test_arrow_key_navigation_multiline_doc_block_core( end_of_line(&c_line, "").await.unwrap(); assert_cursor_line(&codechat_server, &mut client_id, &path_str, 9).await; - // `ArrowUp` from code line "c" should enter the doc block above it with - // the cursor at the block's *last* line (8), matching the "entering from - // below lands at the end" rule documented on `docBlockNavKeymap` and + // `ArrowUp` from code line "c" should enter the doc block above it with the + // cursor at the block's *last* line (8), matching the "entering from below + // lands at the end" rule documented on `docBlockNavKeymap` and // `DocBlockPlugin`'s `focusin` handler. driver .action_chain() @@ -679,12 +679,12 @@ async fn test_arrow_key_navigation_multiline_doc_block_core( // `Line(8)` only proves the caret is somewhere on the paragraph's *last* // source line -- it can't distinguish that line's start from its end. - // Independently confirm the DOM caret placement itself: per the - // "entering from below lands at the end" rule (see `DocBlockPlugin`'s - // `focusin` handler in `CodeMirror-integration.mts`, which does - // `range.selectNodeContents(contents); range.collapse(!at_end)`), the - // caret should sit at the very end of the doc block's text -- after the - // last "four" -- not at its start. + // Independently confirm the DOM caret placement itself: per the "entering + // from below lands at the end" rule (see `DocBlockPlugin`'s `focusin` + // handler in `CodeMirror-integration.mts`, which does + // `range.selectNodeContents(contents); range.collapse(!at_end)`), the caret + // should sit at the very end of the doc block's text -- after the last + // "four" -- not at its start. let is_caret_at_end: bool = driver .execute( "const contents = document.activeElement.closest('.CodeChat-doc-contents'); diff --git a/server/tests/overall_5.rs b/server/tests/overall_5.rs index 8bb65039..af57dcbe 100644 --- a/server/tests/overall_5.rs +++ b/server/tests/overall_5.rs @@ -60,11 +60,11 @@ make_test!( test_edit_preserves_cursor_scroll_in_large_doc_block_core ); -// Regression/stress test for the scroll-preservation logic when editing long doc blocks. This test builds a large document -- a big -// (100-paragraph) doc block, followed by 100 alternating one-character -// code/doc block pairs -- edits a paragraph in the middle of the big doc -// block, and checks that both the reported cursor and scroll position are -// unchanged afterward. +// Regression/stress test for the scroll-preservation logic when editing long +// doc blocks. This test builds a large document -- a big (100-paragraph) doc +// block, followed by 100 alternating one-character code/doc block pairs -- +// edits a paragraph in the middle of the big doc block, and checks that both +// the reported cursor and scroll position are unchanged afterward. async fn test_edit_preserves_cursor_scroll_in_large_doc_block_core( codechat_server: CodeChatEditorServerLog, driver: WebDriver, @@ -77,9 +77,11 @@ async fn test_edit_preserves_cursor_scroll_in_large_doc_block_core( // A one-line doc block (plain `//` comment). let mut orig_text = "// L1\n/// Pt\n/// ==\n///\n".to_string(); - // A 100-paragraph doc block (rustdoc-style `///` comments). - // Since the delimiter (`///`) differs from the preceding block's (`//`), - // these lines don't merge into the first doc block. This also produces an empty line at the end of these paragraphs, which will cause a server re-translation when it cleans this up. + // A 100-paragraph doc block (rustdoc-style `///` comments). Since the + // delimiter (`///`) differs from the preceding block's (`//`), these lines + // don't merge into the first doc block. This also produces an empty line at + // the end of these paragraphs, which will cause a server re-translation + // when it cleans this up. for i in 0..100 { orig_text += &format!("/// P{i}\n///\n"); } @@ -142,9 +144,9 @@ async fn test_edit_preserves_cursor_scroll_in_large_doc_block_core( let tinymce_contents = driver.find(By::Id("TinyMCE-inst")).await.unwrap(); tinymce_contents.send_keys("x").await.unwrap(); - // A cursor-only update (carrying no `contents`) may precede the text - // update carrying the edit; accept and skip any of those, then inspect - // the first update that does carry contents. + // A cursor-only update (carrying no `contents`) may precede the text update + // carrying the edit; accept and skip any of those, then inspect the first + // update that does carry contents. let msg = loop { let msg = codechat_server.get_message_timeout(TIMEOUT).await.unwrap(); assert_eq!(msg.id, client_id); @@ -158,9 +160,8 @@ async fn test_edit_preserves_cursor_scroll_in_large_doc_block_core( codechat_server.send_result(client_id, None).await.unwrap(); client_id += MESSAGE_ID_INCREMENT; }; - // use std::time::Duration; - // use tokio::time::sleep; - // sleep(Duration::from_hours(1)).await; + // use std::time::Duration; use tokio::time::sleep; + // sleep(Duration::from\_hours(1)).await; let client_version = get_version(&msg); assert_eq!( @@ -183,7 +184,9 @@ async fn test_edit_preserves_cursor_scroll_in_large_doc_block_core( to: Some(622), insert: "/// P50x\n".to_string() }, - // The server removes the empty line after the last paragraph in the big doc block. This also causes a re-translation. + // The server removes the empty line after the last + // paragraph in the big doc block. This also causes + // a re-translation. StringDiff { from: 1210, to: Some(1214), @@ -201,9 +204,9 @@ async fn test_edit_preserves_cursor_scroll_in_large_doc_block_core( codechat_server.send_result(client_id, None).await.unwrap(); client_id += MESSAGE_ID_INCREMENT; - // Editing a doc block prompts the Server to send the Client a - // re-translated version of the document; the Client's acknowledgement - // comes back here as a `Result(Ok)` carrying the Server's ID. + // Editing a doc block prompts the Server to send the Client a re-translated + // version of the document; the Client's acknowledgement comes back here as + // a `Result(Ok)` carrying the Server's ID. assert_eq!( codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), EditorMessage { @@ -213,8 +216,8 @@ async fn test_edit_preserves_cursor_scroll_in_large_doc_block_core( ); // The re-translation settles the cursor, producing a final cursor/scroll - // update from the Client. Verify neither the cursor nor the scroll - // position moved as a result of the edit. + // update from the Client. Verify neither the cursor nor the scroll position + // moved as a result of the edit. let msg_after = codechat_server.get_message_timeout(TIMEOUT).await.unwrap(); assert_eq!(msg_after.id, client_id); let UpdateMessageContents { @@ -244,13 +247,13 @@ async fn test_edit_preserves_cursor_scroll_in_large_doc_block_core( // Regression test to ensure left arrow allows placing the cursor at the // beginning of a code block, then moves back to the end of the preceding doc // block on another left arrow press. The preceding doc block here spans two -// source lines, to catch a manually-observed bug (currently failing) where -// that second left-arrow press lands the cursor at the doc block's -// *beginning* instead of its *end*. Also checks (currently failing) that -// pressing Home at the end of the code block keeps the cursor on that line -// rather than moving up into the preceding doc block, and that pressing Home -// a second time (a no-op, since the cursor is already at the start of the -// line) also keeps the cursor on the current line. +// source lines, to catch a manually-observed bug (currently failing) where that +// second left-arrow press lands the cursor at the doc block's *beginning* +// instead of its *end*. Also checks (currently failing) that pressing Home at +// the end of the code block keeps the cursor on that line rather than moving up +// into the preceding doc block, and that pressing Home a second time (a no-op, +// since the cursor is already at the start of the line) also keeps the cursor +// on the current line. make_test!( test_cursor_home_from_code_after_doc_block, test_cursor_home_from_code_after_doc_block_core @@ -281,7 +284,10 @@ async fn test_cursor_home_from_code_after_doc_block_core( let mut client_id = INITIAL_CLIENT_MESSAGE_ID; // Click on the two-character code block ("cc"), which focuses CodeMirror - // and reports the cursor at line 3. The click is in the middle of the element, which places the cursor at the end of the line (given that the width of the screen is much larger than the width of a two-character line.) + // and reports the cursor at line 3. The click is in the middle of the + // element, which places the cursor at the end of the line (given that the + // width of the screen is much larger than the width of a two-character + // line.) let code_line = driver .find(By::XPath("//*[contains(@class, 'cm-line')][text()='cc']")) .await @@ -304,8 +310,8 @@ async fn test_cursor_home_from_code_after_doc_block_core( client_id += MESSAGE_ID_INCREMENT; // The cursor is at the end of the two "c"s. The first `Left` press should - // simply move the cursor to the middle of the two "c"s, staying on the current - // line rather than jumping into the preceding doc block. + // simply move the cursor to the middle of the two "c"s, staying on the + // current line rather than jumping into the preceding doc block. code_line.send_keys(Key::Left).await.unwrap(); assert_eq!( codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), @@ -323,9 +329,9 @@ async fn test_cursor_home_from_code_after_doc_block_core( codechat_server.send_result(client_id, None).await.unwrap(); client_id += MESSAGE_ID_INCREMENT; - // The cursor is at middle the two "c"s. The next `Left` press should - // move the cursor to the beginning of the two "c"s, staying on the current - // line rather than jumping into the preceding doc block. + // The cursor is at middle the two "c"s. The next `Left` press should move + // the cursor to the beginning of the two "c"s, staying on the current line + // rather than jumping into the preceding doc block. code_line.send_keys(Key::Left).await.unwrap(); assert_eq!( codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), @@ -344,10 +350,9 @@ async fn test_cursor_home_from_code_after_doc_block_core( client_id += MESSAGE_ID_INCREMENT; // The cursor is now at the start of code line "cc". A final `Left` press - // should enter the preceding two-line doc block, with the caret landing - // at the block's *end* (per the "entering from below lands at the end" - // rule documented on `docBlockNavKeymap`'s `ArrowLeft` handler), not its - // start. + // should enter the preceding two-line doc block, with the caret landing at + // the block's *end* (per the "entering from below lands at the end" rule + // documented on `docBlockNavKeymap`'s `ArrowLeft` handler), not its start. code_line.send_keys(Key::Left).await.unwrap(); assert_eq!( codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), @@ -367,10 +372,10 @@ async fn test_cursor_home_from_code_after_doc_block_core( // `Line(2)` only proves the caret is somewhere on the doc block's last // source line -- it can't distinguish that line's start from its end. - // Independently confirm the DOM caret placement itself, mirroring the - // check in `test_arrow_key_navigation_multiline_doc_block_core` - // (`overall_4.rs`): the caret should sit at the very end of the doc - // block's text -- after "b" -- not at its start. + // Independently confirm the DOM caret placement itself, mirroring the check + // in `test_arrow_key_navigation_multiline_doc_block_core` (`overall_4.rs`): + // the caret should sit at the very end of the doc block's text -- after "b" + // -- not at its start. let is_caret_at_end: bool = driver .execute( "const contents = document.activeElement.closest('.CodeChat-doc-contents'); @@ -437,9 +442,9 @@ async fn test_cursor_home_from_code_after_doc_block_core( codechat_server.send_result(client_id, None).await.unwrap(); client_id += MESSAGE_ID_INCREMENT; - // Press `Home` a second time. The cursor is already at the beginning of - // the line, so this should be a no-op that keeps the cursor on the - // current line -- not a jump up into the preceding doc block. + // Press `Home` a second time. The cursor is already at the beginning of the + // line, so this should be a no-op that keeps the cursor on the current line + // -- not a jump up into the preceding doc block. beginning_of_line(&code_line, "").await.unwrap(); assert_eq!( codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), diff --git a/server/tests/overall_common/mod.rs b/server/tests/overall_common/mod.rs index f12938d6..be812320 100644 --- a/server/tests/overall_common/mod.rs +++ b/server/tests/overall_common/mod.rs @@ -80,10 +80,10 @@ use test_utils::cast; // this wrapper holds the `CodeChatEditorServer` together with a `WebDriver` // handle and drains the buffer on every call the test framework makes. Each // delegated method forwards to the inner server and, around that call, polls -// the browser log via [`forward_browser_logs`]. +// the browser log via \[`forward_browser_logs`\]. // -// The wrapper exposes the same method names as `CodeChatEditorServer`, so -// test bodies use it transparently. +// The wrapper exposes the same method names as `CodeChatEditorServer`, so test +// bodies use it transparently. pub struct CodeChatEditorServerLog { inner: CodeChatEditorServer, // A `WebDriver` handle used only to read the browser log. `WebDriver` is @@ -103,9 +103,9 @@ impl CodeChatEditorServerLog { forward_browser_logs(&self.driver).await } - // The following methods mirror `CodeChatEditorServer`'s API. Each polls - // the browser log so console output is emitted close to when it occurred, - // then delegates to the inner server. + // The following methods mirror `CodeChatEditorServer`'s API. Each polls the + // browser log so console output is emitted close to when it occurred, then + // delegates to the inner server. pub async fn get_message_timeout(&self, timeout: Duration) -> Option { let msg = self.inner.get_message_timeout(timeout).await; // Waiting for a message is when the browser is most active, so poll @@ -124,8 +124,8 @@ impl CodeChatEditorServerLog { self.inner.send_message_current_file(url).await } - // Used by some test targets but not others; each test binary compiles - // this module separately, so it's dead code in the others. + // Used by some test targets but not others; each test binary compiles this + // module separately, so it's dead code in the others. #[allow(dead_code)] pub async fn send_message_update_plain( &self, @@ -337,9 +337,9 @@ pub async fn harness< let test_result = f(codechat_server, driver_clone.clone(), test_dir).await; // Drain any JavaScript console output captured during the test and - // forward it to Rust logging, then propagate the test's result. Do - // this even when the test failed, since the console output often - // explains the failure. + // forward it to Rust logging, then propagate the test's result. Do this + // even when the test failed, since the console output often explains + // the failure. forward_browser_logs(&driver_clone).await; test_result?; @@ -363,12 +363,12 @@ pub async fn harness< } /// Decode a `BrowserLogEntry::message` produced by a `console.*` call. -/// chromedriver formats these as -/// ` : `, where the serialized -/// message is the JSON encoding of each argument passed to `console.*` -/// (strings included, hence the embedded quotes/escapes). Strip the -/// ` :` prefix and decode the remainder, falling -/// back to the raw message if it doesn't match the expected shape. +/// chromedriver formats these as ` : `, where the serialized message is the JSON encoding of each +/// argument passed to `console.*` (strings included, hence the embedded +/// quotes/escapes). Strip the ` :` prefix and decode +/// the remainder, falling back to the raw message if it doesn't match the +/// expected shape. fn decode_console_message(message: &str) -> String { // Split off the ` :` prefix: the first two // whitespace-separated fields. @@ -379,10 +379,10 @@ fn decode_console_message(message: &str) -> String { return message.to_string(); }; - // The serialized message is a whitespace-separated sequence of - // JSON-encoded values (one per `console.*` argument). Decode each, - // rendering strings without their surrounding quotes and falling back to - // the raw text for anything that isn't valid JSON. + // The serialized message is a whitespace-separated sequence of JSON-encoded + // values (one per `console.*` argument). Decode each, rendering strings + // without their surrounding quotes and falling back to the raw text for + // anything that isn't valid JSON. let mut decoded_parts = Vec::new(); let mut deserializer = serde_json::Deserializer::from_str(serialized).into_iter::(); for value in &mut deserializer { @@ -448,13 +448,13 @@ macro_rules! make_test { }; // Same as above, but for a test that's currently known to fail (a - // regression test pinning down an unfixed bug). `harness` converts a - // panic into a `Result::Err` (see its `catch_unwind` use, needed to shut - // the WebDriver down cleanly), so the failure never becomes a live - // unwind -- meaning `#[should_panic]` can't detect it. `#[ignore]` is - // the alternative: the test is skipped by default (so the suite stays - // green) but still compiles, and can be run explicitly with `cargo test - // -- --ignored` to check whether the bug has been fixed yet. + // regression test pinning down an unfixed bug). `harness` converts a panic + // into a `Result::Err` (see its `catch_unwind` use, needed to shut the + // WebDriver down cleanly), so the failure never becomes a live unwind -- + // meaning `#[should_panic]` can't detect it. `#[ignore]` is the + // alternative: the test is skipped by default (so the suite stays green) + // but still compiles, and can be run explicitly with `cargo test -- + // --ignored` to check whether the bug has been fixed yet. ($test_name: ident, $test_core_name: ident, ignore = $reason: literal) => { #[tokio::test] #[tracing::instrument] @@ -555,17 +555,16 @@ pub async fn goto_line( // JavaScript `keydown`/`keyup` DOM events. ChromeDriver's `send_keys` only // injects DOM-level key events, so on macOS these shortcuts are silently // swallowed before they ever produce the OS-level cursor jump. This is a -// longstanding WebDriver/Selenium limitation, not a bug in this project -- -// see [Command key modifier doesn't work on Mac -// OS](https://github.com/SeleniumHQ/selenium/issues/1290). +// longstanding WebDriver/Selenium limitation, not a bug in this project -- see +// [Command key modifier doesn't work on Mac OS](https://github.com/SeleniumHQ/selenium/issues/1290). // // `Ctrl+Home`/`Ctrl+End` do work via `send_keys` on Windows/Linux for a plain // CodeMirror line, but empirically do *not* reliably reach the true -// beginning/end inside a TinyMCE `contenteditable` doc block (observed -// landing partway through the block instead of at the first/last paragraph, -// even on Windows) -- presumably TinyMCE's own keyboard-shortcut handling -// intercepts or only partially handles them. So rather than branch by OS, -// [`beginning_of_document`] and [`end_of_document`] always use the +// beginning/end inside a TinyMCE `contenteditable` doc block (observed landing +// partway through the block instead of at the first/last paragraph, even on +// Windows) -- presumably TinyMCE's own keyboard-shortcut handling intercepts or +// only partially handles them. So rather than branch by OS, +// \[`beginning_of_document`\] and \[`end_of_document`\] always use the // repeated-arrow-key approach below, which works uniformly on every platform // and in both CodeMirror and TinyMCE elements. // @@ -587,9 +586,9 @@ pub async fn beginning_of_line( element.send_keys(Key::Home + keys_after).await } -// Move the cursor to the end of the current line, then send `keys_after` -// (which may be empty). Plain `End` (no modifier) is handled by the browser -// itself on every platform, so no OS-specific workaround is needed here. +// Move the cursor to the end of the current line, then send `keys_after` (which +// may be empty). Plain `End` (no modifier) is handled by the browser itself on +// every platform, so no OS-specific workaround is needed here. #[allow(dead_code)] #[tracing::instrument(skip(element))] pub async fn end_of_line( @@ -600,9 +599,9 @@ pub async fn end_of_line( } // Move the cursor to the beginning of the document, then send `keys_after` -// (which may be empty). See the module-level comment above for why this -// presses `Up` enough times to reach line 1 from anywhere within a test -// fixture's (small) document, rather than using `Ctrl+Home`/`Cmd+Up`. +// (which may be empty). See the module-level comment above for why this presses +// `Up` enough times to reach line 1 from anywhere within a test fixture's +// (small) document, rather than using `Ctrl+Home`/`Cmd+Up`. #[allow(dead_code)] #[tracing::instrument(skip(element))] pub async fn beginning_of_document( @@ -611,17 +610,17 @@ pub async fn beginning_of_document( ) -> Result<(), WebDriverError> { // Test fixtures used by this suite are well under this many lines; // repeating `Up` past the first line is a no-op, so an overshoot is - // harmless. Sent as a single `send_keys` call, along with `keys_after`, - // so this produces one cursor update rather than one per repeated key. + // harmless. Sent as a single `send_keys` call, along with `keys_after`, so + // this produces one cursor update rather than one per repeated key. let keys: TypingData = repeated_key(Key::Up, MAX_TEST_DOCUMENT_LINES) + keys_after; element.send_keys(keys).await } -// Move the cursor to the end of the document, then send `keys_after` (which -// may be empty). See the module-level comment above for why this presses -// `Down` enough times to reach the last line from anywhere within a test -// fixture's (small) document (then `End` to reach the end of that line), -// rather than using `Ctrl+End`/`Cmd+Down`. +// Move the cursor to the end of the document, then send `keys_after` (which may +// be empty). See the module-level comment above for why this presses `Down` +// enough times to reach the last line from anywhere within a test fixture's +// (small) document (then `End` to reach the end of that line), rather than +// using `Ctrl+End`/`Cmd+Down`. #[allow(dead_code)] #[tracing::instrument(skip(element))] pub async fn end_of_document( @@ -633,8 +632,8 @@ pub async fn end_of_document( } // Build a `TypingData` consisting of `key` repeated `count` times, for the -// macOS repeated-arrow-key workaround in [`beginning_of_document`] and -// [`end_of_document`]. +// macOS repeated-arrow-key workaround in \[`beginning_of_document`\] and +// \[`end_of_document`\]. fn repeated_key(key: Key, count: u32) -> TypingData { std::iter::repeat_n(key.value(), count as usize) .collect::() @@ -643,7 +642,7 @@ fn repeated_key(key: Key, count: u32) -> TypingData { // An upper bound on the number of lines in any document used by this test // suite's fixtures, used by the macOS `Up`/`Down`-repeating workaround in -// [`beginning_of_document`] and [`end_of_document`] above. +// \[`beginning_of_document`\] and \[`end_of_document`\] above. const MAX_TEST_DOCUMENT_LINES: u32 = 100; pub async fn perform_loadfile( From 6ba4c440e74372e3f14cdf841f2ff67577942d7b Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Sat, 11 Jul 2026 10:23:41 +0500 Subject: [PATCH 09/11] Freeze for release. --- CHANGELOG.md | 5 + builder/Cargo.lock | 16 +-- client/package.json5 | 6 +- client/pnpm-lock.yaml | 50 ++++---- extensions/VSCode/Cargo.lock | 44 +++---- extensions/VSCode/Cargo.toml | 2 +- extensions/VSCode/package.json | 6 +- extensions/VSCode/pnpm-lock.yaml | 198 +++++++++++++++---------------- server/Cargo.lock | 52 ++++---- server/Cargo.toml | 2 +- test_utils/Cargo.lock | 12 +- 11 files changed, 199 insertions(+), 194 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee27a1cf..7b876bbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,11 @@ Changelog [Github master](https://github.com/bjones1/CodeChat_Editor) ----------------------------------------------------------- +* No changes. + +Version 0.1.61 -- 2026-Jul-11 +----------------------------- + * Fix unexpected scrolling when server updates are applied to a doc block that exceeds the screen height. * Fix errors due to incorrect TypeScript typecasts. diff --git a/builder/Cargo.lock b/builder/Cargo.lock index 3d7fd6db..d1f8ea3d 100644 --- a/builder/Cargo.lock +++ b/builder/Cargo.lock @@ -279,9 +279,9 @@ checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "jiff" -version = "0.2.31" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccfe6121cbe750cf81efa362d85c0bde7ea298ec43092d3a193baca59cdbd634" +checksum = "961d16382652bfdd8c6f68b223b26a8c93e0d475c672f414411db31c6c5c900e" dependencies = [ "defmt", "jiff-static", @@ -293,9 +293,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.31" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e165e897f662d428f3cd3828a919dbe067c2d42bb1031eede74ef9d27ecdedd2" +checksum = "d0879bd39df99c4c5e2c6615ccc026391a423dde10532c573e6086eb94a802cc" dependencies = [ "proc-macro2", "quote", @@ -437,9 +437,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.4" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +checksum = "2a0e75113e14dc5acb068cd0786884f214f1312650a3d36d269f5c4f3cdee8a2" dependencies = [ "aho-corasick", "memchr", @@ -449,9 +449,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "1f388202e4b80542a0921078cc23b6333bcf1409c1e3f86404cae4766a6131db" dependencies = [ "aho-corasick", "memchr", diff --git a/client/package.json5 b/client/package.json5 index f64ef88c..abfc1de4 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.60', + version: '0.1.61', dependencies: { '@codemirror/commands': '^6.10.4', '@codemirror/lang-cpp': '^6.0.3', @@ -80,7 +80,7 @@ '@types/dom-navigation': '^1.0.7', '@types/js-beautify': '^1.14.3', '@types/mocha': '^10.0.10', - '@types/node': '^24.13.2', + '@types/node': '^24.13.3', '@types/toastify-js': '^1.12.4', '@typescript-eslint/eslint-plugin': '^8.63.0', '@typescript-eslint/parser': '^8.63.0', @@ -93,7 +93,7 @@ globals: '^17.7.0', mocha: '^11.7.6', 'npm-check-updates': '^22.2.9', - prettier: '^3.9.4', + prettier: '^3.9.5', typescript: '^6.0.3', 'typescript-eslint': '^8.63.0', }, diff --git a/client/pnpm-lock.yaml b/client/pnpm-lock.yaml index c371cf7c..fd553337 100644 --- a/client/pnpm-lock.yaml +++ b/client/pnpm-lock.yaml @@ -109,8 +109,8 @@ importers: specifier: ^10.0.10 version: 10.0.10 '@types/node': - specifier: ^24.13.2 - version: 24.13.2 + specifier: ^24.13.3 + version: 24.13.3 '@types/toastify-js': specifier: ^1.12.4 version: 1.12.4 @@ -137,7 +137,7 @@ importers: version: 2.32.0(@typescript-eslint/parser@8.63.0(eslint@10.6.0)(typescript@6.0.3))(eslint@10.6.0) eslint-plugin-prettier: specifier: ^5.5.6 - version: 5.5.6(eslint-config-prettier@10.1.8(eslint@10.6.0))(eslint@10.6.0)(prettier@3.9.4) + version: 5.5.6(eslint-config-prettier@10.1.8(eslint@10.6.0))(eslint@10.6.0)(prettier@3.9.5) globals: specifier: ^17.7.0 version: 17.7.0 @@ -148,8 +148,8 @@ importers: specifier: ^22.2.9 version: 22.2.9 prettier: - specifier: ^3.9.4 - version: 3.9.4 + specifier: ^3.9.5 + version: 3.9.5 typescript: specifier: ^6.0.3 version: 6.0.3 @@ -500,8 +500,8 @@ packages: '@lezer/lr@1.4.10': resolution: {integrity: sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==} - '@lezer/markdown@1.6.4': - resolution: {integrity: sha512-N0SxazMj4k65DBfaf1azqtMZd6u7MqluP84/NZnB/io8Td9aleFmAhz9hcbvSfsxT5tdYlJ5qgv5aMJGY4zEtA==} + '@lezer/markdown@1.7.1': + resolution: {integrity: sha512-MEBZeFSBxgteUjEC3Wxg2Dwld5/JxRKG267L3bMFdibm8KjqSdiJYBeFw1Nt1CM8+zKMpSIEHblY8FD9z38sJQ==} '@lezer/php@1.0.5': resolution: {integrity: sha512-W7asp9DhM6q0W6DYNwIkLSKOvxlXRrif+UXBMxzsJUuqmhE7oVU+gS3THO4S/Puh7Xzgm858UNaFi6dxTP8dJA==} @@ -736,8 +736,8 @@ packages: '@types/mocha@10.0.10': resolution: {integrity: sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==} - '@types/node@24.13.2': - resolution: {integrity: sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==} + '@types/node@24.13.3': + resolution: {integrity: sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==} '@types/toastify-js@1.12.4': resolution: {integrity: sha512-zfZHU4tKffPCnZRe7pjv/eFKzTVHozKewFCKaCjZ4gFinKgJRz/t0bkZiMCXJxPhv/ZoeDGNOeRD09R0kQZ/nw==} @@ -882,11 +882,11 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} - brace-expansion@1.1.15: - resolution: {integrity: sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==} + brace-expansion@1.1.16: + resolution: {integrity: sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==} - brace-expansion@2.1.1: - resolution: {integrity: sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==} + brace-expansion@2.1.2: + resolution: {integrity: sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==} brace-expansion@5.0.7: resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==} @@ -1827,8 +1827,8 @@ packages: resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==} engines: {node: '>=6.0.0'} - prettier@3.9.4: - resolution: {integrity: sha512-yWG/o/4oJfo036EKAfK6ACAoDOfHeRHx4tuxkfBZiauURiaSmYwlpOr5LQqKtIkRD2z1PLteme2WoxEnj4tHTg==} + prettier@3.9.5: + resolution: {integrity: sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==} engines: {node: '>=14'} hasBin: true @@ -2223,7 +2223,7 @@ snapshots: '@codemirror/state': 6.7.1 '@codemirror/view': 6.43.6 '@lezer/common': 1.5.2 - '@lezer/markdown': 1.6.4 + '@lezer/markdown': 1.7.1 '@codemirror/lang-php@6.0.2': dependencies: @@ -2520,7 +2520,7 @@ snapshots: dependencies: '@lezer/common': 1.5.2 - '@lezer/markdown@1.6.4': + '@lezer/markdown@1.7.1': dependencies: '@lezer/common': 1.5.2 '@lezer/highlight': 1.2.3 @@ -2758,7 +2758,7 @@ snapshots: '@types/mocha@10.0.10': {} - '@types/node@24.13.2': + '@types/node@24.13.3': dependencies: undici-types: 7.18.2 @@ -2950,12 +2950,12 @@ snapshots: balanced-match@4.0.4: {} - brace-expansion@1.1.15: + brace-expansion@1.1.16: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@2.1.1: + brace-expansion@2.1.2: dependencies: balanced-match: 1.0.2 @@ -3473,10 +3473,10 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-prettier@5.5.6(eslint-config-prettier@10.1.8(eslint@10.6.0))(eslint@10.6.0)(prettier@3.9.4): + eslint-plugin-prettier@5.5.6(eslint-config-prettier@10.1.8(eslint@10.6.0))(eslint@10.6.0)(prettier@3.9.5): dependencies: eslint: 10.6.0 - prettier: 3.9.4 + prettier: 3.9.5 prettier-linter-helpers: 1.0.1 synckit: 0.11.13 optionalDependencies: @@ -3913,11 +3913,11 @@ snapshots: minimatch@3.1.5: dependencies: - brace-expansion: 1.1.15 + brace-expansion: 1.1.16 minimatch@9.0.9: dependencies: - brace-expansion: 2.1.1 + brace-expansion: 2.1.2 minimist@1.2.8: {} @@ -4063,7 +4063,7 @@ snapshots: dependencies: fast-diff: 1.3.0 - prettier@3.9.4: {} + prettier@3.9.5: {} punycode@2.3.1: {} diff --git a/extensions/VSCode/Cargo.lock b/extensions/VSCode/Cargo.lock index 06f4234c..2f4bbfb7 100644 --- a/extensions/VSCode/Cargo.lock +++ b/extensions/VSCode/Cargo.lock @@ -549,9 +549,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" dependencies = [ "serde", ] @@ -673,7 +673,7 @@ dependencies = [ [[package]] name = "codechat-editor-server" -version = "0.1.60" +version = "0.1.61" dependencies = [ "actix-files", "actix-http", @@ -731,7 +731,7 @@ dependencies = [ [[package]] name = "codechat-editor-vscode-extension" -version = "0.1.60" +version = "0.1.61" dependencies = [ "codechat-editor-server", "log", @@ -1666,9 +1666,9 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe112b004901c62c2faa11f4f75e9864e0cc5af8da71c9115d184a3aa888749f" +checksum = "2adf14691c72bcfc1058740436a35bdd3ae9c07d1a941ef00b749e9ea16aefa7" dependencies = [ "crossbeam-deque", "globset", @@ -1719,9 +1719,9 @@ dependencies = [ [[package]] name = "inotify" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd854a95a4ac672fed8c054136039fd32c22cf039ff09ead7280afe920486483" +checksum = "153be1941a183ec9ccd095ddbe17a8b8d435ef6c76e9e02451b933c3999af2c8" dependencies = [ "bitflags", "inotify-sys", @@ -1839,9 +1839,9 @@ dependencies = [ [[package]] name = "json-escape-simd" -version = "3.1.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a18a0eb3c0a783620d2ae8cdda9590aae18b1608964ee9c7c43162562786424" +checksum = "c22a2041e3874a055a4eb03ea2395aaccdefa84ce75b31d542d72a741c3c6ad3" [[package]] name = "kqueue" @@ -3342,9 +3342,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.4" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +checksum = "2a0e75113e14dc5acb068cd0786884f214f1312650a3d36d269f5c4f3cdee8a2" dependencies = [ "aho-corasick", "memchr", @@ -3354,9 +3354,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "1f388202e4b80542a0921078cc23b6333bcf1409c1e3f86404cae4766a6131db" dependencies = [ "aho-corasick", "memchr", @@ -3882,9 +3882,9 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" dependencies = [ "cfg-if", ] @@ -3931,9 +3931,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" dependencies = [ "tinyvec_macros", ] @@ -4757,18 +4757,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.53" +version = "0.8.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75726053136156d419e285b9b7eddaaea9e3fea6ce32eed44a89901f0bd98de1" +checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.53" +version = "0.8.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4714fd92cf900833d49538023a9b3915155210801d1c1169eba513b2addefd71" +checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" dependencies = [ "proc-macro2", "quote", diff --git a/extensions/VSCode/Cargo.toml b/extensions/VSCode/Cargo.toml index 2e3f3481..7b581c61 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.60" +version = "0.1.61" [lib] crate-type = ["cdylib"] diff --git a/extensions/VSCode/package.json b/extensions/VSCode/package.json index fd37de7d..286f244a 100644 --- a/extensions/VSCode/package.json +++ b/extensions/VSCode/package.json @@ -41,7 +41,7 @@ "type": "git", "url": "https://github.com/bjones1/CodeChat_Editor" }, - "version": "0.1.60", + "version": "0.1.61", "activationEvents": [ "onCommand:extension.codeChatEditorActivate", "onCommand:extension.codeChatEditorDeactivate" @@ -86,7 +86,7 @@ "@napi-rs/cli": "^3.7.2", "@tybys/wasm-util": "^0.10.3", "@types/escape-html": "^1.0.4", - "@types/node": "^24.13.2", + "@types/node": "^24.13.3", "@types/vscode": "1.61.0", "@typescript-eslint/eslint-plugin": "^8.63.0", "@typescript-eslint/parser": "^8.63.0", @@ -99,7 +99,7 @@ "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^5.5.6", "npm-run-all2": "^9.0.2", - "prettier": "^3.9.4", + "prettier": "^3.9.5", "typescript": "^6.0.3", "typescript-eslint": "^8.63.0" }, diff --git a/extensions/VSCode/pnpm-lock.yaml b/extensions/VSCode/pnpm-lock.yaml index c50071e7..de6c9a22 100644 --- a/extensions/VSCode/pnpm-lock.yaml +++ b/extensions/VSCode/pnpm-lock.yaml @@ -23,7 +23,7 @@ importers: version: 10.0.1(eslint@10.6.0) '@napi-rs/cli': specifier: ^3.7.2 - version: 3.7.2(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.13.2) + version: 3.7.2(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.13.3) '@tybys/wasm-util': specifier: ^0.10.3 version: 0.10.3 @@ -31,8 +31,8 @@ importers: specifier: ^1.0.4 version: 1.0.4 '@types/node': - specifier: ^24.13.2 - version: 24.13.2 + specifier: ^24.13.3 + version: 24.13.3 '@types/vscode': specifier: 1.61.0 version: 1.61.0 @@ -65,13 +65,13 @@ importers: version: 11.1.0(eslint@10.6.0) eslint-plugin-prettier: specifier: ^5.5.6 - version: 5.5.6(eslint-config-prettier@10.1.8(eslint@10.6.0))(eslint@10.6.0)(prettier@3.9.4) + version: 5.5.6(eslint-config-prettier@10.1.8(eslint@10.6.0))(eslint@10.6.0)(prettier@3.9.5) npm-run-all2: specifier: ^9.0.2 version: 9.0.2 prettier: - specifier: ^3.9.4 - version: 3.9.4 + specifier: ^3.9.5 + version: 3.9.5 typescript: specifier: ^6.0.3 version: 6.0.3 @@ -123,16 +123,16 @@ packages: resolution: {integrity: sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA==} engines: {node: '>=20.0.0'} - '@azure/msal-browser@5.16.0': - resolution: {integrity: sha512-Wc75FGnQgYpsm5jsOqn1H8AXsh8vXruA6vwip1nhjrJxwby7juxKAIVLr7csepmHiwdZGr6EwI5BlSc3PizEtQ==} + '@azure/msal-browser@5.17.0': + resolution: {integrity: sha512-/yTnW2TCk9Mh+2b/NOaHAN+MryUNxzRTaJD/YtrqOA9bpBWfTXn/iyReRbaLrK/btBo3stEzLyEvuWp2NZ5DuA==} engines: {node: '>=0.8.0'} - '@azure/msal-common@16.11.0': - resolution: {integrity: sha512-UikJOtMwkFpZNzTH6Dqk8UTUPbow15zH3e0UjGYZy69lYENW/S05gMLhbxI2eonz66uALhIljvhsSMEb6+O30g==} + '@azure/msal-common@16.11.1': + resolution: {integrity: sha512-yPohvMwWLv1XnaWnIUyKUh8CvcVChCGqG/VluGwfGmaAfrZTNt5yQ+sIs462Sgw6+e2K83KGmMJ860p73ZSCrw==} engines: {node: '>=0.8.0'} - '@azure/msal-node@5.3.1': - resolution: {integrity: sha512-sqqv3L1UOI4KDXonNtbxPYUgbSWVXqxvmmb6BUw9n4P/UXgG+cVur3dLWQN4Cz7qQ+UJROCCxMXlksm7gIq0Sw==} + '@azure/msal-node@5.4.0': + resolution: {integrity: sha512-6EZEParwHRlnSSIikw8FNAnAzwmh71uhveUXdPNFeZFviJ9SH+rwFiurhjzXqICYTrpm3E+dj693QOwfPbJXAQ==} engines: {node: '>=20'} '@babel/code-frame@7.29.7': @@ -1007,8 +1007,8 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - '@types/node@24.13.2': - resolution: {integrity: sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==} + '@types/node@24.13.3': + resolution: {integrity: sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -1246,8 +1246,8 @@ packages: boundary@2.0.0: resolution: {integrity: sha512-rJKn5ooC9u8q13IMCrW0RSp31pxBCHE3y9V/tp3TdWSLf8Em3p6Di4NBpfzbJge9YjjFEsD0RtFEjtvHL5VyEA==} - brace-expansion@1.1.15: - resolution: {integrity: sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==} + brace-expansion@1.1.16: + resolution: {integrity: sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==} brace-expansion@5.0.7: resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==} @@ -2103,8 +2103,8 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.5.1: - resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==} + lru-cache@11.5.2: + resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} engines: {node: 20 || >=22} lru-cache@6.0.0: @@ -2357,8 +2357,8 @@ packages: resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==} engines: {node: '>=6.0.0'} - prettier@3.9.4: - resolution: {integrity: sha512-yWG/o/4oJfo036EKAfK6ACAoDOfHeRHx4tuxkfBZiauURiaSmYwlpOr5LQqKtIkRD2z1PLteme2WoxEnj4tHTg==} + prettier@3.9.5: + resolution: {integrity: sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==} engines: {node: '>=14'} hasBin: true @@ -2884,8 +2884,8 @@ snapshots: '@azure/core-tracing': 1.3.1 '@azure/core-util': 1.13.1 '@azure/logger': 1.3.0 - '@azure/msal-browser': 5.16.0 - '@azure/msal-node': 5.3.1 + '@azure/msal-browser': 5.17.0 + '@azure/msal-node': 5.4.0 open: 10.2.0 tslib: 2.8.1 transitivePeerDependencies: @@ -2898,15 +2898,15 @@ snapshots: transitivePeerDependencies: - supports-color - '@azure/msal-browser@5.16.0': + '@azure/msal-browser@5.17.0': dependencies: - '@azure/msal-common': 16.11.0 + '@azure/msal-common': 16.11.1 - '@azure/msal-common@16.11.0': {} + '@azure/msal-common@16.11.1': {} - '@azure/msal-node@5.3.1': + '@azure/msal-node@5.4.0': dependencies: - '@azure/msal-common': 16.11.0 + '@azure/msal-common': 16.11.1 jsonwebtoken: 9.0.3 '@babel/code-frame@7.29.7': @@ -3060,126 +3060,126 @@ snapshots: '@inquirer/ansi@2.0.7': {} - '@inquirer/checkbox@5.2.1(@types/node@24.13.2)': + '@inquirer/checkbox@5.2.1(@types/node@24.13.3)': dependencies: '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@24.13.2) + '@inquirer/core': 11.2.1(@types/node@24.13.3) '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@24.13.2) + '@inquirer/type': 4.0.7(@types/node@24.13.3) optionalDependencies: - '@types/node': 24.13.2 + '@types/node': 24.13.3 - '@inquirer/confirm@6.1.1(@types/node@24.13.2)': + '@inquirer/confirm@6.1.1(@types/node@24.13.3)': dependencies: - '@inquirer/core': 11.2.1(@types/node@24.13.2) - '@inquirer/type': 4.0.7(@types/node@24.13.2) + '@inquirer/core': 11.2.1(@types/node@24.13.3) + '@inquirer/type': 4.0.7(@types/node@24.13.3) optionalDependencies: - '@types/node': 24.13.2 + '@types/node': 24.13.3 - '@inquirer/core@11.2.1(@types/node@24.13.2)': + '@inquirer/core@11.2.1(@types/node@24.13.3)': dependencies: '@inquirer/ansi': 2.0.7 '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@24.13.2) + '@inquirer/type': 4.0.7(@types/node@24.13.3) cli-width: 4.1.0 fast-wrap-ansi: 0.2.2 mute-stream: 3.0.0 signal-exit: 4.1.0 optionalDependencies: - '@types/node': 24.13.2 + '@types/node': 24.13.3 - '@inquirer/editor@5.2.2(@types/node@24.13.2)': + '@inquirer/editor@5.2.2(@types/node@24.13.3)': dependencies: - '@inquirer/core': 11.2.1(@types/node@24.13.2) - '@inquirer/external-editor': 3.0.3(@types/node@24.13.2) - '@inquirer/type': 4.0.7(@types/node@24.13.2) + '@inquirer/core': 11.2.1(@types/node@24.13.3) + '@inquirer/external-editor': 3.0.3(@types/node@24.13.3) + '@inquirer/type': 4.0.7(@types/node@24.13.3) optionalDependencies: - '@types/node': 24.13.2 + '@types/node': 24.13.3 - '@inquirer/expand@5.1.1(@types/node@24.13.2)': + '@inquirer/expand@5.1.1(@types/node@24.13.3)': dependencies: - '@inquirer/core': 11.2.1(@types/node@24.13.2) - '@inquirer/type': 4.0.7(@types/node@24.13.2) + '@inquirer/core': 11.2.1(@types/node@24.13.3) + '@inquirer/type': 4.0.7(@types/node@24.13.3) optionalDependencies: - '@types/node': 24.13.2 + '@types/node': 24.13.3 - '@inquirer/external-editor@3.0.3(@types/node@24.13.2)': + '@inquirer/external-editor@3.0.3(@types/node@24.13.3)': dependencies: chardet: 2.2.0 iconv-lite: 0.7.3 optionalDependencies: - '@types/node': 24.13.2 + '@types/node': 24.13.3 '@inquirer/figures@2.0.7': {} - '@inquirer/input@5.1.2(@types/node@24.13.2)': + '@inquirer/input@5.1.2(@types/node@24.13.3)': dependencies: - '@inquirer/core': 11.2.1(@types/node@24.13.2) - '@inquirer/type': 4.0.7(@types/node@24.13.2) + '@inquirer/core': 11.2.1(@types/node@24.13.3) + '@inquirer/type': 4.0.7(@types/node@24.13.3) optionalDependencies: - '@types/node': 24.13.2 + '@types/node': 24.13.3 - '@inquirer/number@4.1.1(@types/node@24.13.2)': + '@inquirer/number@4.1.1(@types/node@24.13.3)': dependencies: - '@inquirer/core': 11.2.1(@types/node@24.13.2) - '@inquirer/type': 4.0.7(@types/node@24.13.2) + '@inquirer/core': 11.2.1(@types/node@24.13.3) + '@inquirer/type': 4.0.7(@types/node@24.13.3) optionalDependencies: - '@types/node': 24.13.2 + '@types/node': 24.13.3 - '@inquirer/password@5.1.1(@types/node@24.13.2)': + '@inquirer/password@5.1.1(@types/node@24.13.3)': dependencies: '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@24.13.2) - '@inquirer/type': 4.0.7(@types/node@24.13.2) + '@inquirer/core': 11.2.1(@types/node@24.13.3) + '@inquirer/type': 4.0.7(@types/node@24.13.3) optionalDependencies: - '@types/node': 24.13.2 - - '@inquirer/prompts@8.5.2(@types/node@24.13.2)': - dependencies: - '@inquirer/checkbox': 5.2.1(@types/node@24.13.2) - '@inquirer/confirm': 6.1.1(@types/node@24.13.2) - '@inquirer/editor': 5.2.2(@types/node@24.13.2) - '@inquirer/expand': 5.1.1(@types/node@24.13.2) - '@inquirer/input': 5.1.2(@types/node@24.13.2) - '@inquirer/number': 4.1.1(@types/node@24.13.2) - '@inquirer/password': 5.1.1(@types/node@24.13.2) - '@inquirer/rawlist': 5.3.1(@types/node@24.13.2) - '@inquirer/search': 4.2.1(@types/node@24.13.2) - '@inquirer/select': 5.2.1(@types/node@24.13.2) + '@types/node': 24.13.3 + + '@inquirer/prompts@8.5.2(@types/node@24.13.3)': + dependencies: + '@inquirer/checkbox': 5.2.1(@types/node@24.13.3) + '@inquirer/confirm': 6.1.1(@types/node@24.13.3) + '@inquirer/editor': 5.2.2(@types/node@24.13.3) + '@inquirer/expand': 5.1.1(@types/node@24.13.3) + '@inquirer/input': 5.1.2(@types/node@24.13.3) + '@inquirer/number': 4.1.1(@types/node@24.13.3) + '@inquirer/password': 5.1.1(@types/node@24.13.3) + '@inquirer/rawlist': 5.3.1(@types/node@24.13.3) + '@inquirer/search': 4.2.1(@types/node@24.13.3) + '@inquirer/select': 5.2.1(@types/node@24.13.3) optionalDependencies: - '@types/node': 24.13.2 + '@types/node': 24.13.3 - '@inquirer/rawlist@5.3.1(@types/node@24.13.2)': + '@inquirer/rawlist@5.3.1(@types/node@24.13.3)': dependencies: - '@inquirer/core': 11.2.1(@types/node@24.13.2) - '@inquirer/type': 4.0.7(@types/node@24.13.2) + '@inquirer/core': 11.2.1(@types/node@24.13.3) + '@inquirer/type': 4.0.7(@types/node@24.13.3) optionalDependencies: - '@types/node': 24.13.2 + '@types/node': 24.13.3 - '@inquirer/search@4.2.1(@types/node@24.13.2)': + '@inquirer/search@4.2.1(@types/node@24.13.3)': dependencies: - '@inquirer/core': 11.2.1(@types/node@24.13.2) + '@inquirer/core': 11.2.1(@types/node@24.13.3) '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@24.13.2) + '@inquirer/type': 4.0.7(@types/node@24.13.3) optionalDependencies: - '@types/node': 24.13.2 + '@types/node': 24.13.3 - '@inquirer/select@5.2.1(@types/node@24.13.2)': + '@inquirer/select@5.2.1(@types/node@24.13.3)': dependencies: '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@24.13.2) + '@inquirer/core': 11.2.1(@types/node@24.13.3) '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@24.13.2) + '@inquirer/type': 4.0.7(@types/node@24.13.3) optionalDependencies: - '@types/node': 24.13.2 + '@types/node': 24.13.3 - '@inquirer/type@4.0.7(@types/node@24.13.2)': + '@inquirer/type@4.0.7(@types/node@24.13.3)': optionalDependencies: - '@types/node': 24.13.2 + '@types/node': 24.13.3 - '@napi-rs/cli@3.7.2(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.13.2)': + '@napi-rs/cli@3.7.2(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.13.3)': dependencies: - '@inquirer/prompts': 8.5.2(@types/node@24.13.2) + '@inquirer/prompts': 8.5.2(@types/node@24.13.3) '@napi-rs/cross-toolchain': 1.0.3(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) '@napi-rs/wasm-tools': 1.0.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) '@octokit/rest': 22.0.1 @@ -3641,7 +3641,7 @@ snapshots: '@types/json5@0.0.29': {} - '@types/node@24.13.2': + '@types/node@24.13.3': dependencies: undici-types: 7.18.2 @@ -3952,7 +3952,7 @@ snapshots: boundary@2.0.0: {} - brace-expansion@1.1.15: + brace-expansion@1.1.16: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 @@ -4392,10 +4392,10 @@ snapshots: resolve: 1.22.12 semver: 6.3.1 - eslint-plugin-prettier@5.5.6(eslint-config-prettier@10.1.8(eslint@10.6.0))(eslint@10.6.0)(prettier@3.9.4): + eslint-plugin-prettier@5.5.6(eslint-config-prettier@10.1.8(eslint@10.6.0))(eslint@10.6.0)(prettier@3.9.5): dependencies: eslint: 10.6.0 - prettier: 3.9.4 + prettier: 3.9.5 prettier-linter-helpers: 1.0.1 synckit: 0.11.13 optionalDependencies: @@ -4947,7 +4947,7 @@ snapshots: lru-cache@10.4.3: {} - lru-cache@11.5.1: {} + lru-cache@11.5.2: {} lru-cache@6.0.0: dependencies: @@ -4992,7 +4992,7 @@ snapshots: minimatch@3.1.5: dependencies: - brace-expansion: 1.1.15 + brace-expansion: 1.1.16 minimist@1.2.8: {} @@ -5168,7 +5168,7 @@ snapshots: path-scurry@2.0.2: dependencies: - lru-cache: 11.5.1 + lru-cache: 11.5.2 minipass: 7.1.3 path-type@6.0.0: {} @@ -5211,7 +5211,7 @@ snapshots: dependencies: fast-diff: 1.3.0 - prettier@3.9.4: {} + prettier@3.9.5: {} pump@3.0.4: dependencies: diff --git a/server/Cargo.lock b/server/Cargo.lock index 026bcaad..c402182f 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -603,9 +603,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" dependencies = [ "serde", ] @@ -742,7 +742,7 @@ dependencies = [ [[package]] name = "codechat-editor-server" -version = "0.1.60" +version = "0.1.61" dependencies = [ "actix-files", "actix-http", @@ -1935,9 +1935,9 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe112b004901c62c2faa11f4f75e9864e0cc5af8da71c9115d184a3aa888749f" +checksum = "2adf14691c72bcfc1058740436a35bdd3ae9c07d1a941ef00b749e9ea16aefa7" dependencies = [ "crossbeam-deque", "globset", @@ -1988,9 +1988,9 @@ dependencies = [ [[package]] name = "inotify" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd854a95a4ac672fed8c054136039fd32c22cf039ff09ead7280afe920486483" +checksum = "153be1941a183ec9ccd095ddbe17a8b8d435ef6c76e9e02451b933c3999af2c8" dependencies = [ "bitflags", "inotify-sys", @@ -2114,9 +2114,9 @@ dependencies = [ [[package]] name = "json-escape-simd" -version = "3.1.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a18a0eb3c0a783620d2ae8cdda9590aae18b1608964ee9c7c43162562786424" +checksum = "c22a2041e3874a055a4eb03ea2395aaccdefa84ce75b31d542d72a741c3c6ad3" [[package]] name = "konst" @@ -3672,9 +3672,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.4" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +checksum = "2a0e75113e14dc5acb068cd0786884f214f1312650a3d36d269f5c4f3cdee8a2" dependencies = [ "aho-corasick", "memchr", @@ -3684,9 +3684,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "1f388202e4b80542a0921078cc23b6333bcf1409c1e3f86404cae4766a6131db" dependencies = [ "aho-corasick", "memchr", @@ -4084,9 +4084,9 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" dependencies = [ "cfg-if", "cpufeatures 0.2.17", @@ -4476,9 +4476,9 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" dependencies = [ "cfg-if", ] @@ -4525,9 +4525,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" dependencies = [ "tinyvec_macros", ] @@ -4787,7 +4787,7 @@ dependencies = [ "httparse", "log", "rand 0.9.4", - "sha1 0.10.6", + "sha1 0.10.7", "thiserror", ] @@ -5540,18 +5540,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.53" +version = "0.8.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75726053136156d419e285b9b7eddaaea9e3fea6ce32eed44a89901f0bd98de1" +checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.53" +version = "0.8.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4714fd92cf900833d49538023a9b3915155210801d1c1169eba513b2addefd71" +checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" dependencies = [ "proc-macro2", "quote", @@ -5634,9 +5634,9 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5431d5661c32445236631278f27946e444ddafe4684cac70b185272d4f9c52d5" +checksum = "b142a20ec14a91d5bc708c1dc21b080c550113d8aa77afa29635673a65dd02c5" [[package]] name = "zmij" diff --git a/server/Cargo.toml b/server/Cargo.toml index e759774f..f0aa59e0 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.60" +version = "0.1.61" # This library allows other packages to use core CodeChat Editor features. [lib] diff --git a/test_utils/Cargo.lock b/test_utils/Cargo.lock index af8324f3..b4462133 100644 --- a/test_utils/Cargo.lock +++ b/test_utils/Cargo.lock @@ -130,9 +130,9 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe112b004901c62c2faa11f4f75e9864e0cc5af8da71c9115d184a3aa888749f" +checksum = "2adf14691c72bcfc1058740436a35bdd3ae9c07d1a941ef00b749e9ea16aefa7" dependencies = [ "crossbeam-deque", "globset", @@ -221,9 +221,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.4" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +checksum = "2a0e75113e14dc5acb068cd0786884f214f1312650a3d36d269f5c4f3cdee8a2" dependencies = [ "aho-corasick", "memchr", @@ -233,9 +233,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "1f388202e4b80542a0921078cc23b6333bcf1409c1e3f86404cae4766a6131db" dependencies = [ "aho-corasick", "memchr", From 2c1685fe514f1900cff107b8554c8288709e504d Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Sat, 11 Jul 2026 10:55:51 +0500 Subject: [PATCH 10/11] Fix: clippy lints. --- builder/src/main.rs | 10 ++++++++-- server/src/ide/vscode/tests.rs | 8 ++++---- server/src/lexer.rs | 8 ++++---- server/src/processing.rs | 20 ++++++++++---------- server/src/webserver.rs | 2 +- 5 files changed, 27 insertions(+), 21 deletions(-) diff --git a/builder/src/main.rs b/builder/src/main.rs index c0623838..cc4284d0 100644 --- a/builder/src/main.rs +++ b/builder/src/main.rs @@ -248,7 +248,7 @@ fn quick_copy_dir>(src: P, dest: P, files: Option

) -> io::Resu } // Print the command, in case this produces and error or takes a while. - println!("{:#?}", ©_process); + println!("{:#?}", copy_process); // Check for errors. let exit_code = copy_process @@ -542,7 +542,13 @@ fn run_until_fail() -> io::Result<()> { unsafe { env::set_var("RUST_BACKTRACE", "1"); } - let tests = ["overall_1", "overall_2", "overall_3", "overall_4"]; + let tests = [ + "overall_1", + "overall_2", + "overall_3", + "overall_4", + "overall_5", + ]; let mut iteration = 0; loop { iteration += 1; diff --git a/server/src/ide/vscode/tests.rs b/server/src/ide/vscode/tests.rs index fab98e5b..8d29298d 100644 --- a/server/src/ide/vscode/tests.rs +++ b/server/src/ide/vscode/tests.rs @@ -608,7 +608,7 @@ async fn test_vscode_ide_websocket7() { message: EditorMessageContents::CurrentFile( format!( "http://localhost:{IP_PORT}/vsc/fs/{connection_id}/{}", - &file_path.to_slash().unwrap(), + file_path.to_slash().unwrap(), ), None, ), @@ -899,7 +899,7 @@ async fn test_vscode_ide_websocket4() { message: EditorMessageContents::CurrentFile( format!( "http://localhost:{IP_PORT}/vsc/fs/{connection_id}/{}", - &file_path.to_slash().unwrap() + file_path.to_slash().unwrap() ), None, ), @@ -1164,7 +1164,7 @@ async fn test_vscode_ide_websocket4a() { message: EditorMessageContents::CurrentFile( format!( "http://localhost:{IP_PORT}/vsc/fs/{connection_id}/{}", - &file_path.to_slash().unwrap() + file_path.to_slash().unwrap() ), None, ), @@ -1266,7 +1266,7 @@ async fn test_vscode_ide_websocket4b() { message: EditorMessageContents::CurrentFile( format!( "http://localhost:{IP_PORT}/vsc/fs/{connection_id}/{}", - &file_path.to_slash().unwrap() + file_path.to_slash().unwrap() ), None, ), diff --git a/server/src/lexer.rs b/server/src/lexer.rs index 232b40ff..faa7aa1f 100644 --- a/server/src/lexer.rs +++ b/server/src/lexer.rs @@ -487,7 +487,7 @@ fn build_lexer_regex( // Look for either the delimiter or a newline to terminate the // string. - (false, NewlineSupport::None) => Regex::new(&format!("{}|\n", &escaped_delimiter)), + (false, NewlineSupport::None) => Regex::new(&format!("{}|\n", escaped_delimiter)), } .unwrap(); regex_builder( @@ -1071,7 +1071,7 @@ pub fn source_lexer( source_code_unlexed_index + opening_delimiter.len(); trace!( "Found a nested opening block comment delimiter. Nesting depth: {}", - &nesting_depth + nesting_depth ); continue; } else { @@ -1093,7 +1093,7 @@ pub fn source_lexer( + closing_delimiter_match.len(); trace!( "Found a non-innermost closing block comment delimiter. Nesting depth: {}", - &nesting_depth + nesting_depth ); continue; } @@ -1356,7 +1356,7 @@ pub fn source_lexer( // print the doc block trace!( "Appending a doc block with indent '{}', delimiter '{}', and contents '{}'.", - &comment_line_prefix, matching_group_str, contents + comment_line_prefix, matching_group_str, contents ); // advance `current_code_block_index` to diff --git a/server/src/processing.rs b/server/src/processing.rs index 1f595fce..dab63c57 100644 --- a/server/src/processing.rs +++ b/server/src/processing.rs @@ -1071,15 +1071,15 @@ pub fn source_to_codechat_for_web_string( let is_project = path_to_toc.is_some(); Ok(( - match source_to_codechat_for_web( - file_contents, - &ext.to_string(), - version, - is_toc, - is_project, - ) { - Err(err) => return Err(err), - Ok(translation_results) => match translation_results { + { + let translation_results = source_to_codechat_for_web( + file_contents, + &ext.to_string(), + version, + is_toc, + is_project, + )?; + match translation_results { TranslationResults::CodeChat(codechat_for_web) => { if is_toc { // For the table of contents sidebar, which is pure @@ -1094,7 +1094,7 @@ pub fn source_to_codechat_for_web_string( } } TranslationResults::Unknown => TranslationResultsString::Unknown, - }, + } }, path_to_toc, )) diff --git a/server/src/webserver.rs b/server/src/webserver.rs index 3034d583..e3c76a06 100644 --- a/server/src/webserver.rs +++ b/server/src/webserver.rs @@ -703,7 +703,7 @@ pub async fn filesystem_endpoint( let Some(processing_tx) = processing_queue_tx.get(&connection_id) else { let msg = format!( "Error: no processing task queue for connection id {}.", - &connection_id + connection_id ); error!("{msg}"); return http_not_found(&msg); From 54245a04cc9bb88023d0e5472230f7f60aef6ad1 Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Sat, 11 Jul 2026 13:40:37 +0500 Subject: [PATCH 11/11] Fix: clippy lints on Linux only. --- builder/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builder/src/main.rs b/builder/src/main.rs index cc4284d0..7f1a489d 100644 --- a/builder/src/main.rs +++ b/builder/src/main.rs @@ -240,8 +240,8 @@ fn quick_copy_dir>(src: P, dest: P, files: Option

) -> io::Resu "-c", format!( "rsync --archive --delete {} {}", - &src_combined.to_str().unwrap(), - &dest.to_str().unwrap() + src_combined.to_str().unwrap(), + dest.to_str().unwrap() ) .as_str(), ]);