Skip to content

Commit 44994a8

Browse files
committed
wip: translate update
1 parent 0ac7ff1 commit 44994a8

File tree

1 file changed

+131
-41
lines changed

1 file changed

+131
-41
lines changed

server/src/translation.rs

Lines changed: 131 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -209,10 +209,13 @@ use std::{collections::HashMap, ffi::OsStr, fmt::Debug, path::PathBuf};
209209
// ### Third-party
210210
use lazy_static::lazy_static;
211211
use log::{debug, error, warn};
212+
use rand::random;
212213
use regex::Regex;
213214
use tokio::sync::mpsc::{Receiver, Sender};
214215
use tokio::{fs::File, select, sync::mpsc};
215216

217+
use crate::lexer::supported_languages::MARKDOWN_MODE;
218+
use crate::processing::CodeMirrorDocBlockVec;
216219
// ### Local
217220
use crate::{
218221
processing::{
@@ -234,7 +237,7 @@ use crate::{
234237
// -------
235238
//
236239
// The max length of a message to show in the console.
237-
const MAX_MESSAGE_LENGTH: usize = 300;
240+
const MAX_MESSAGE_LENGTH: usize = 3000;
238241

239242
lazy_static! {
240243
/// A regex to determine the type of the first EOL. See 'PROCESSINGS`.
@@ -872,8 +875,10 @@ impl TranslationTask {
872875
};
873876
// Send a diff if possible.
874877
let client_contents = if self.sent_full {
875-
self.wrap_translation(
876-
&ccfw,
878+
self.diff_code_mirror(
879+
ccfw.metadata.clone(),
880+
self.version,
881+
ccfw.version,
877882
code_mirror_translated,
878883
)
879884
} else {
@@ -958,29 +963,35 @@ impl TranslationTask {
958963
true
959964
}
960965

961-
/// Given contents translated from `ccfw` to `code_mirror_translated`, return a `CodeChatForWeb` with these translated contents, using a diff.
962-
fn wrap_translation(
966+
/// Return a `CodeChatForWeb` struct containing a diff between `self.code_mirror_doc` / `self.code_mirror_doc_blocks` and `code_mirror_translated`.
967+
fn diff_code_mirror(
963968
&self,
964-
ccfw: &CodeChatForWeb,
965-
code_mirror_translated: &CodeMirror,
969+
// The `metadata` and `version` fields will be copied from this to the returned `CodeChatForWeb` struct.
970+
metadata: SourceFileMetadata,
971+
// The version number of the previous (before) data. Typically, `self.version`.
972+
before_version: f64,
973+
// The version number for the resulting return struct.
974+
version: f64,
975+
// This provides the after data for the diff; before data comes from `self.code_mirror` / `self.code_mirror_doc`.
976+
code_mirror_after: &CodeMirror,
966977
) -> CodeChatForWeb {
967978
assert!(self.sent_full);
968-
let doc_diff = diff_str(&self.code_mirror_doc, &code_mirror_translated.doc);
979+
let doc_diff = diff_str(&self.code_mirror_doc, &code_mirror_after.doc);
969980
let Some(ref cmdb) = self.code_mirror_doc_blocks else {
970981
panic!("Should have diff of doc blocks!");
971982
};
972-
let doc_blocks_diff = diff_code_mirror_doc_blocks(cmdb, &code_mirror_translated.doc_blocks);
983+
let doc_blocks_diff = diff_code_mirror_doc_blocks(cmdb, &code_mirror_after.doc_blocks);
973984
CodeChatForWeb {
974985
// Clone needed here, so we can copy it
975986
// later.
976-
metadata: ccfw.metadata.clone(),
987+
metadata,
977988
source: CodeMirrorDiffable::Diff(CodeMirrorDiff {
978989
doc: doc_diff,
979990
doc_blocks: doc_blocks_diff,
980-
// The diff was made between the current version (`version`) and the new version (`contents.version`).
981-
version: self.version,
991+
// The diff was made between the before version (this) and the after version (`ccfw.version`).
992+
version: before_version,
982993
}),
983-
version: ccfw.version,
994+
version,
984995
}
985996
}
986997

@@ -1001,32 +1012,64 @@ impl TranslationTask {
10011012
None => None,
10021013
Some(cfw) => match codechat_for_web_to_source(&cfw) {
10031014
Ok(new_source_code) => {
1004-
// Translate back to the Client to see if there are any changes after this conversion.
1005-
if let Ok(ccfws) = source_to_codechat_for_web_string(
1006-
&new_source_code,
1007-
&clean_file_path,
1008-
cfw.version,
1009-
false,
1010-
) && let TranslationResultsString::CodeChat(ref ccfw) = ccfws.0
1015+
// Update the stored CodeMirror data structures with what we just received. This must be updated before we can translate back to check for changes (the next step).
1016+
let CodeMirrorDiffable::Plain(code_mirror) = cfw.source else {
1017+
// TODO: support diffable!
1018+
panic!("Diff not supported.");
1019+
};
1020+
let debug_cm = code_mirror.clone();
1021+
self.code_mirror_doc = code_mirror.doc;
1022+
self.code_mirror_doc_blocks = Some(code_mirror.doc_blocks);
1023+
// We may need to change this version if we send a diff back to the Client.
1024+
let mut cfw_version = cfw.version;
1025+
1026+
// Translate back to the Client to see if there are any changes after this conversion. Only check CodeChat documents, not Markdown docs.
1027+
if cfw.metadata.mode != MARKDOWN_MODE
1028+
&& let Ok(ccfws) = source_to_codechat_for_web_string(
1029+
&new_source_code,
1030+
&clean_file_path,
1031+
cfw.version,
1032+
false,
1033+
)
1034+
&& let TranslationResultsString::CodeChat(ccfw) = ccfws.0
10111035
&& let CodeMirrorDiffable::Plain(ref code_mirror_translated) =
10121036
ccfw.source
10131037
&& self.sent_full
10141038
{
1015-
// Compute the diff.
1016-
let ccfw_client_diff =
1017-
self.wrap_translation(ccfw, code_mirror_translated);
1018-
let CodeMirrorDiffable::Diff(client_contents) =
1019-
ccfw_client_diff.source
1020-
else {
1021-
panic!("Expected diff.");
1022-
};
1023-
if !client_contents.doc.is_empty()
1024-
|| !client_contents.doc_blocks.is_empty()
1039+
// Determine if the re-translation includes changes (such as line wrapping in doc blocks which changes line numbering, creation of a new doc block from previous code block text, or updates from future document intelligence such as renamed headings, etc.) For doc blocks that haven't been edited by TinyMCE, this is easy; equality is sufficient. Doc blocks that have been edited are a different case: TinyMCE removes newlines, causing a lot of "changes" to re-insert these. Therefore, use the following approach:
1040+
//
1041+
// 1. Compare the `doc` values. If they differ, then the the Client needs an update.
1042+
// 2. Compare each code block using simple equality. If this fails, compare the doc block text excluding newlines. If still different, then the Client needs an update.
1043+
if code_mirror_translated.doc != self.code_mirror_doc
1044+
|| !doc_block_compare(
1045+
&code_mirror_translated.doc_blocks,
1046+
self.code_mirror_doc_blocks.as_ref().unwrap(),
1047+
)
10251048
{
1026-
// Translating back to the client produced a non-empty diff. Send this to the client.
1049+
cfw_version = random();
1050+
// The Client needs an update.
1051+
let client_contents = self.diff_code_mirror(
1052+
cfw.metadata.clone(),
1053+
cfw.version,
1054+
cfw_version,
1055+
code_mirror_translated,
1056+
);
10271057
println!(
1028-
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
1058+
"Sending client re-translation update:\nBefore:\n{debug_cm:#?}\nAfter:\n{code_mirror_translated:#?}\nDiff:\n{client_contents:#?}"
10291059
);
1060+
queue_send_func!(self.to_client_tx.send(EditorMessage {
1061+
id: self.id,
1062+
message: EditorMessageContents::Update(
1063+
UpdateMessageContents {
1064+
file_path: update_message_contents.file_path,
1065+
contents: Some(client_contents),
1066+
// Don't change the current position, since the Client editing position should be left undisturbed.
1067+
cursor_position: None,
1068+
scroll_position: None
1069+
}
1070+
)
1071+
}));
1072+
self.id += MESSAGE_ID_INCREMENT;
10301073
}
10311074
};
10321075
// Correct EOL endings for use with the
@@ -1043,7 +1086,7 @@ impl TranslationTask {
10431086
doc_blocks: vec![],
10441087
version: self.version,
10451088
}),
1046-
version: cfw.version,
1089+
version: cfw_version,
10471090
})
10481091
} else {
10491092
Some(CodeChatForWeb {
@@ -1054,17 +1097,11 @@ impl TranslationTask {
10541097
doc: new_source_code_eol.clone(),
10551098
doc_blocks: vec![],
10561099
}),
1057-
version: cfw.version,
1100+
version: cfw_version,
10581101
})
10591102
};
1060-
self.version = cfw.version;
1103+
self.version = cfw_version;
10611104
self.source_code = new_source_code_eol;
1062-
let CodeMirrorDiffable::Plain(cmd) = cfw.source else {
1063-
// TODO: support diffable!
1064-
panic!("Diff not supported.");
1065-
};
1066-
self.code_mirror_doc = cmd.doc;
1067-
self.code_mirror_doc_blocks = Some(cmd.doc_blocks);
10681105
ccfw
10691106
}
10701107
Err(message) => {
@@ -1101,6 +1138,33 @@ fn eol_convert(s: String, eol_type: &EolType) -> String {
11011138
}
11021139
}
11031140

1141+
// Given a vector of two doc blocks, compare them, ignoring newlines.
1142+
fn doc_block_compare(a: &CodeMirrorDocBlockVec, b: &CodeMirrorDocBlockVec) -> bool {
1143+
if a.len() != b.len() {
1144+
return false;
1145+
}
1146+
1147+
a.iter().zip(b).all(|el| {
1148+
let a = el.0;
1149+
let b = el.1;
1150+
a.from == b.from
1151+
&& a.to == b.to
1152+
&& a.indent == b.indent
1153+
&& a.delimiter == b.delimiter
1154+
&& (a.contents == b.contents
1155+
// TinyMCE replaces newlines inside paragraphs with a space; for a crude comparison, translate all newlines back to spaces, then ignore leading/trailing newlines.
1156+
|| map_newlines_to_spaces(&a.contents).eq(map_newlines_to_spaces(&b.contents)))
1157+
})
1158+
}
1159+
1160+
fn map_newlines_to_spaces<'a>(
1161+
s: &'a str,
1162+
) -> std::iter::Map<std::str::Chars<'a>, impl FnMut(char) -> char> {
1163+
s.trim()
1164+
.chars()
1165+
.map(|c: char| if c == '\n' { ' ' } else { c })
1166+
}
1167+
11041168
// Provide a simple debug function that prints only the first
11051169
// `MAX_MESSAGE_LENGTH` characters of the provided value.
11061170
fn debug_shorten<T: Debug>(val: T) -> String {
@@ -1116,3 +1180,29 @@ fn debug_shorten<T: Debug>(val: T) -> String {
11161180
"".to_string()
11171181
}
11181182
}
1183+
1184+
// Tests
1185+
// -----
1186+
#[cfg(test)]
1187+
mod tests {
1188+
use crate::{processing::CodeMirrorDocBlock, translation::doc_block_compare};
1189+
1190+
#[test]
1191+
fn test_x1() {
1192+
let before = vec![CodeMirrorDocBlock {
1193+
from: 0,
1194+
to: 20,
1195+
indent: "".to_string(),
1196+
delimiter: "//".to_string(),
1197+
contents: "<p>Copyright (C) 2025 Bryan A. Jones.</p>\n<p>This file is part of the CodeChat Editor. The CodeChat Editor is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.</p>\n<p>The CodeChat Editor is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.</p>\n<p>You should have received a copy of the GNU General Public License along with the CodeChat Editor. If not, see <a href=\"http://www.gnu.org/licenses\">http://www.gnu.org/licenses</a>.</p>\n<h1><code>debug_enable.mts</code> -- Configure debug features</h1>\n<p>True to enable additional debug logging.</p>".to_string(),
1198+
}];
1199+
let after = vec![CodeMirrorDocBlock {
1200+
from: 0,
1201+
to: 20,
1202+
indent: "".to_string(),
1203+
delimiter: "//".to_string(),
1204+
contents: "<p>Copyright (C) 2025 Bryan A. Jones.</p>\n<p>This file is part of the CodeChat Editor. The CodeChat Editor is free\nsoftware: you can redistribute it and/or modify it under the terms of the GNU\nGeneral Public License as published by the Free Software Foundation, either\nversion 3 of the License, or (at your option) any later version.</p>\n<p>The CodeChat Editor is distributed in the hope that it will be useful, but\nWITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\nFITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more\ndetails.</p>\n<p>You should have received a copy of the GNU General Public License along with\nthe CodeChat Editor. If not, see\n<a href=\"http://www.gnu.org/licenses\">http://www.gnu.org/licenses</a>.</p>\n<h1><code>debug_enable.mts</code> -- Configure debug features</h1>\n<p>True to enable additional debug logging.</p>\n".to_string(),
1205+
}];
1206+
assert!(doc_block_compare(&before, &after));
1207+
}
1208+
}

0 commit comments

Comments
 (0)