From 75aafcd350784895baffd2b75e5b24c662fb861e Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Fri, 5 Dec 2025 12:41:00 -0600 Subject: [PATCH 1/3] Fix: avoid sending unnecessary updates. Fixes failure in Client to move cursor. --- client/src/CodeMirror-integration.mts | 6 ++--- extensions/VSCode/src/extension.ts | 38 +++++++++++++-------------- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/client/src/CodeMirror-integration.mts b/client/src/CodeMirror-integration.mts index 4314e48..ea51294 100644 --- a/client/src/CodeMirror-integration.mts +++ b/client/src/CodeMirror-integration.mts @@ -464,7 +464,6 @@ class DocBlockWidget extends WidgetType { // If this change was produced by a user edit, then the DOM was already // updated. Stop here. if (this.is_user_change) { - console.log("user change -- skipping DOM update."); return true; } (dom.childNodes[0] as HTMLDivElement).innerHTML = this.indent; @@ -676,7 +675,6 @@ const on_dirty = ( // Only run this after typesetting is done. window.MathJax.whenReady(async () => { - console.log("Starting update for user change."); on_dirty_scheduled = false; // Find the doc block parent div. const target = (event_target as HTMLDivElement).closest( @@ -1098,10 +1096,10 @@ export const CodeMirror_load = async ( setup: (editor: Editor) => { // See the // [docs](https://www.tiny.cloud/docs/tinymce/latest/events/#editor-core-events). - editor.on("Dirty", (event: any) => { + editor.on("input", (event: any) => { // Get the div TinyMCE stores edits in. TODO: find // documentation for `event.target.bodyElement`. - const target_or_false = event.target?.bodyElement; + const target_or_false = event.target as HTMLElement; if (target_or_false == null) { return false; } diff --git a/extensions/VSCode/src/extension.ts b/extensions/VSCode/src/extension.ts index 5ad1f40..1e2bc9b 100644 --- a/extensions/VSCode/src/extension.ts +++ b/extensions/VSCode/src/extension.ts @@ -163,7 +163,14 @@ export const activate = (context: vscode.ExtensionContext) => { ignore_active_editor_change = false; return; } - send_update(false); + // Skip an update if we've already sent a `CurrentFile` for this editor. + if ( + current_editor === + vscode.window.activeTextEditor + ) { + return; + } + send_update(true); }), ); @@ -174,6 +181,9 @@ export const activate = (context: vscode.ExtensionContext) => { ignore_selection_change = false; return; } + console_log( + "CodeChat Editor extension: sending updated cursor/scroll position.", + ); send_update(false); }, ), @@ -246,19 +256,6 @@ export const activate = (context: vscode.ExtensionContext) => { webview_panel = undefined; await stop_client(); }); - - // Render when the webview panel is shown. - webview_panel.onDidChangeViewState( - ( - _event: vscode.WebviewPanelOnDidChangeViewStateEvent, - ) => { - // Only render if the webview was activated; - // this event also occurs when it's deactivated. - if (webview_panel?.active) { - send_update(true); - } - }, - ); } } @@ -367,6 +364,9 @@ export const activate = (context: vscode.ExtensionContext) => { await sendResult(id, "OutOfSync"); // Send an `Update` with the full text to // re-sync the Client. + console_log( + "CodeChat Editor extension: sending update because Client is out of sync.", + ); send_update(true); break; } @@ -395,10 +395,10 @@ export const activate = (context: vscode.ExtensionContext) => { } } } - vscode.workspace.applyEdit(wse).then(() => { - ignore_text_document_change = false; - ignore_selection_change = false; - }); + await vscode.workspace.applyEdit(wse); + ignore_text_document_change = false; + ignore_selection_change = false; + // Now that we've updated our text, update the // associated version as well. version = current_update.contents.version; @@ -425,7 +425,6 @@ export const activate = (context: vscode.ExtensionContext) => { // viewport, but a bit below it. TextEditorRevealType.AtTop, ); - ignore_selection_change = false; } let cursor_line = current_update.cursor_position; @@ -443,7 +442,6 @@ export const activate = (context: vscode.ExtensionContext) => { cursor_position, ), ]; - ignore_selection_change = false; } await sendResult(id); break; From 650c04bfef32f648b27c06b0bafb0d08cb998da8 Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Fri, 5 Dec 2025 15:33:31 -0600 Subject: [PATCH 2/3] Fix: add tests for cursor movement. --- .../overall/overall_core/test_4/test.py | 3 + server/tests/overall_core/mod.rs | 221 +++++++++++++++--- 2 files changed, 188 insertions(+), 36 deletions(-) create mode 100644 server/tests/fixtures/overall/overall_core/test_4/test.py diff --git a/server/tests/fixtures/overall/overall_core/test_4/test.py b/server/tests/fixtures/overall/overall_core/test_4/test.py new file mode 100644 index 0000000..23103ab --- /dev/null +++ b/server/tests/fixtures/overall/overall_core/test_4/test.py @@ -0,0 +1,3 @@ +# The contents of this file don't matter -- tests will supply the content, +# instead of loading it from disk. However, it does need to exist for +# `canonicalize` to find the correct path to this file. diff --git a/server/tests/overall_core/mod.rs b/server/tests/overall_core/mod.rs index d3ec76d..d644e8a 100644 --- a/server/tests/overall_core/mod.rs +++ b/server/tests/overall_core/mod.rs @@ -257,6 +257,54 @@ fn get_version(msg: &EditorMessage) -> f64 { .version } +async fn goto_line( + codechat_server: &CodeChatEditorServer, + driver_ref: &WebDriver, + client_id: &mut f64, + path_str: &str, + line: u32, +) -> Result<(), Box> { + let code_line_css = ".CodeChat-CodeMirror .cm-line"; + let code_line = driver_ref.find(By::Css(code_line_css)).await.unwrap(); + code_line + .send_keys( + Key::Alt + + if cfg!(target_os = "macos") { + Key::Command + } else { + Key::Control + } + + "g", + ) + .await + .unwrap(); + // Enter a line in the dialog that pops up. + driver_ref + .find(By::Css("input.cm-textfield")) + .await + .unwrap() + .send_keys(line.to_string() + Key::Enter) + .await + .unwrap(); + // The cursor movement produces a cursor/scroll position update after an + // autosave delay. + assert_eq!( + codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), + EditorMessage { + id: *client_id, + message: EditorMessageContents::Update(UpdateMessageContents { + file_path: path_str.to_string(), + contents: None, + cursor_position: Some(line), + scroll_position: Some(1.0) + }) + } + ); + codechat_server.send_result(*client_id, None).await.unwrap(); + *client_id += MESSAGE_ID_INCREMENT; + + Ok(()) +} // Tests // ----------------------------------------------------------------------------- // @@ -1183,46 +1231,13 @@ async fn test_client_updates_core( } ); - // Insert a character to check the insertion point. - let code_line_css = ".CodeChat-CodeMirror .cm-line"; - let code_line = driver_ref.find(By::Css(code_line_css)).await.unwrap(); - code_line - .send_keys( - Key::Alt - + if cfg!(target_os = "macos") { - Key::Command - } else { - Key::Control - } - + "g", - ) + goto_line(&codechat_server, driver_ref, &mut client_id, &path_str, 4) .await .unwrap(); - // Enter a line in the dialog that pops up. - driver_ref - .find(By::Css("input.cm-textfield")) - .await - .unwrap() - .send_keys("4" + Key::Enter) - .await - .unwrap(); - // The cursor movement produces a cursor/scroll position update after an - // autosave delay. - assert_eq!( - codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), - EditorMessage { - id: client_id, - message: EditorMessageContents::Update(UpdateMessageContents { - file_path: path_str.clone(), - contents: None, - cursor_position: Some(4), - scroll_position: Some(1.0) - }) - } - ); - client_id += MESSAGE_ID_INCREMENT; // Add an indented comment. + let code_line_css = ".CodeChat-CodeMirror .cm-line"; + let code_line = driver_ref.find(By::Css(code_line_css)).await.unwrap(); code_line.send_keys(Key::Home + "# ").await.unwrap(); // This should edit the (new) third line of the file after word wrap: `def // foo():`. @@ -1258,3 +1273,137 @@ async fn test_client_updates_core( Ok(()) } + +mod test4 { + use super::*; + harness!(test_4_core); +} + +#[tokio::test] +async fn test_4() -> Result<(), Box> { + test4::harness(test_4_core, prep_test_dir!()).await +} + +// Some of the thirtyfour calls are marked as deprecated, though they aren't +// marked that way in the Selenium docs. +#[allow(deprecated)] +async fn test_4_core( + codechat_server: CodeChatEditorServer, + driver_ref: &WebDriver, + test_dir: PathBuf, +) -> Result<(), WebDriverError> { + let mut expected_messages = ExpectedMessages::new(); + let path = canonicalize(test_dir.join("test.py")).unwrap(); + let path_str = path.to_str().unwrap().to_string(); + let current_file_id = codechat_server + .send_message_current_file(path_str.clone()) + .await + .unwrap(); + // The ordering of these messages isn't fixed -- one can come first, or the + // other. + expected_messages.insert(EditorMessage { + id: current_file_id, + message: EditorMessageContents::Result(Ok(ResultOkTypes::Void)), + }); + let mut server_id = 6.0; + expected_messages.insert(EditorMessage { + id: server_id, + message: EditorMessageContents::LoadFile(path.clone()), + }); + expected_messages + .assert_all_messages(&codechat_server, TIMEOUT) + .await; + + // Respond to the load request. + let ide_version = 0.0; + codechat_server + .send_result_loadfile( + server_id, + Some(( + indoc!( + " + # 1 + 2 + # 3 + 4 + # 5 + " + ) + .to_string(), + ide_version, + )), + ) + .await + .unwrap(); + server_id += MESSAGE_ID_INCREMENT; + + // The loadfile produces a message to the client, which comes back here. We + // don't need to acknowledge it. + assert_eq!( + codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), + EditorMessage { + id: server_id, + message: EditorMessageContents::Result(Ok(ResultOkTypes::Void)) + } + ); + + // Target the iframe containing the Client. + let codechat_iframe = driver_ref.find(By::Css("#CodeChat-iframe")).await.unwrap(); + driver_ref + .switch_to() + .frame_element(&codechat_iframe) + .await + .unwrap(); + + // Switch from one doc block to another. It should produce an update with only cursor/scroll info (no contents). + let mut client_id = INITIAL_CLIENT_MESSAGE_ID; + let doc_blocks = driver_ref.find_all(By::Css(".CodeChat-doc")).await.unwrap(); + doc_blocks[0].click().await.unwrap(); + assert_eq!( + codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), + EditorMessage { + id: client_id, + message: EditorMessageContents::Update(UpdateMessageContents { + file_path: path_str.clone(), + contents: None, + cursor_position: Some(1), + scroll_position: Some(1.0) + }) + } + ); + codechat_server.send_result(client_id, None).await.unwrap(); + client_id += MESSAGE_ID_INCREMENT; + + doc_blocks[1].click().await.unwrap(); + assert_eq!( + codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), + EditorMessage { + id: client_id, + message: EditorMessageContents::Update(UpdateMessageContents { + file_path: path_str.clone(), + contents: None, + cursor_position: Some(3), + scroll_position: Some(1.0) + }) + } + ); + codechat_server.send_result(client_id, None).await.unwrap(); + client_id += MESSAGE_ID_INCREMENT; + + doc_blocks[2].click().await.unwrap(); + assert_eq!( + codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), + EditorMessage { + id: client_id, + message: EditorMessageContents::Update(UpdateMessageContents { + file_path: path_str.clone(), + contents: None, + cursor_position: Some(5), + scroll_position: Some(1.0) + }) + } + ); + codechat_server.send_result(client_id, None).await.unwrap(); + + Ok(()) +} From 5372fde1e0d8daf172b8bc1da66018afdfdfd677 Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Fri, 5 Dec 2025 16:29:13 -0600 Subject: [PATCH 3/3] Freeze for release. --- CHANGELOG.md | 6 ++++++ client/package.json5 | 2 +- extensions/VSCode/Cargo.lock | 12 ++++++------ extensions/VSCode/Cargo.toml | 2 +- extensions/VSCode/package.json | 2 +- server/Cargo.lock | 17 ++++------------- server/Cargo.toml | 2 +- 7 files changed, 20 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e928a36..c234087 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,12 @@ Changelog * No changes. +Version 0.1.43 -- 2025-Dec-05 +-------------------------------------------------------------------------------- + +* Fix cursor movement errors in the Client. +* Prevent unnecessary saves of Client data to the IDE. + Version 0.1.42 -- 2025-Dec-04 -------------------------------------------------------------------------------- diff --git a/client/package.json5 b/client/package.json5 index de5e959..ea73dc5 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.42', + version: '0.1.43', dependencies: { '@codemirror/commands': '^6.10.0', '@codemirror/lang-cpp': '^6.0.3', diff --git a/extensions/VSCode/Cargo.lock b/extensions/VSCode/Cargo.lock index 032b5b0..8477fdb 100644 --- a/extensions/VSCode/Cargo.lock +++ b/extensions/VSCode/Cargo.lock @@ -522,7 +522,7 @@ checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "codechat-editor-server" -version = "0.1.42" +version = "0.1.43" dependencies = [ "actix-files", "actix-http", @@ -570,7 +570,7 @@ dependencies = [ [[package]] name = "codechat-editor-vscode-extension" -version = "0.1.42" +version = "0.1.43" dependencies = [ "codechat-editor-server", "log", @@ -668,9 +668,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb230974aaf0aca4d71665bed0aca156cf43b764fcb9583b69c6c3e686f35e72" +checksum = "424e0138278faeb2b401f174ad17e715c829512d74f3d1e81eb43365c2e0590e" dependencies = [ "ctor-proc-macro", "dtor", @@ -846,9 +846,9 @@ checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "flate2" -version = "1.1.5" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +checksum = "a2152dbcb980c05735e2a651d96011320a949eb31a0c8b38b72645ce97dec676" dependencies = [ "crc32fast", "miniz_oxide", diff --git a/extensions/VSCode/Cargo.toml b/extensions/VSCode/Cargo.toml index ad3e423..a2cd644 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.42" +version = "0.1.43" [lib] crate-type = ["cdylib"] diff --git a/extensions/VSCode/package.json b/extensions/VSCode/package.json index 4aa1e83..b81701c 100644 --- a/extensions/VSCode/package.json +++ b/extensions/VSCode/package.json @@ -40,7 +40,7 @@ "type": "git", "url": "https://github.com/bjones1/CodeChat_Editor" }, - "version": "0.1.42", + "version": "0.1.43", "activationEvents": [ "onCommand:extension.codeChatEditorActivate", "onCommand:extension.codeChatEditorDeactivate" diff --git a/server/Cargo.lock b/server/Cargo.lock index a5b3dc9..48f5529 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -746,7 +746,7 @@ checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "codechat-editor-server" -version = "0.1.42" +version = "0.1.43" dependencies = [ "actix-files", "actix-http", @@ -1282,13 +1282,13 @@ checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "flate2" -version = "1.1.5" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +checksum = "a2152dbcb980c05735e2a651d96011320a949eb31a0c8b38b72645ce97dec676" dependencies = [ "crc32fast", - "libz-rs-sys", "miniz_oxide", + "zlib-rs", ] [[package]] @@ -2112,15 +2112,6 @@ dependencies = [ "redox_syscall", ] -[[package]] -name = "libz-rs-sys" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b484ba8d4f775eeca644c452a56650e544bf7e617f1d170fe7298122ead5222" -dependencies = [ - "zlib-rs", -] - [[package]] name = "linux-raw-sys" version = "0.11.0" diff --git a/server/Cargo.toml b/server/Cargo.toml index 561ceb6..6402b39 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.42" +version = "0.1.43" # This library allows other packages to use core CodeChat Editor features. [lib]