Expose to JS

This commit is contained in:
Andras Schmelczer 2025-10-26 21:44:43 +00:00
commit 1b46e5d237
9 changed files with 95 additions and 17 deletions

38
Cargo.lock generated
View file

@ -124,6 +124,12 @@ version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "memchr"
version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]] [[package]]
name = "memory_units" name = "memory_units"
version = "0.4.0" version = "0.4.0"
@ -182,6 +188,7 @@ dependencies = [
"insta", "insta",
"pretty_assertions", "pretty_assertions",
"serde", "serde",
"serde_json",
"serde_yaml", "serde_yaml",
"test-case", "test-case",
"wasm-bindgen", "wasm-bindgen",
@ -212,24 +219,47 @@ dependencies = [
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.219" version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.219" version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn",
] ]
[[package]]
name = "serde_json"
version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
"serde_core",
]
[[package]] [[package]]
name = "serde_yaml" name = "serde_yaml"
version = "0.9.34+deprecated" version = "0.9.34+deprecated"

View file

@ -24,6 +24,7 @@ path = "examples/merge-file.rs"
serde = { version = "1.0.219", optional = true, features = ["derive"] } serde = { version = "1.0.219", optional = true, features = ["derive"] }
wasm-bindgen = { version = "0.2.99", optional = true } wasm-bindgen = { version = "0.2.99", optional = true }
serde_json = { version = "1.0.145", optional = true }
# The `console_error_panic_hook` crate provides better debugging of panics by # The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires # logging them with `console.error`. This is great for development, but requires
@ -36,9 +37,9 @@ wee_alloc = { version = "0.4.2", optional = true }
[features] [features]
default = [] default = []
serde = [ "dep:serde" ] serde = [ "dep:serde" ]
wasm = [ "dep:wasm-bindgen", "dep:wee_alloc" ] wasm = [ "dep:wasm-bindgen", "dep:wee_alloc", "dep:serde_json", "serde" ]
console_error_panic_hook = [ "dep:console_error_panic_hook" ] console_error_panic_hook = [ "dep:console_error_panic_hook" ]
all = [ "serde", "wasm", "console_error_panic_hook" ] all = [ "wasm", "console_error_panic_hook" ]
[dev-dependencies] [dev-dependencies]
insta = "1.42.2" insta = "1.42.2"

View file

@ -5,6 +5,7 @@ import {
SpanWithHistory as wasmSpanWithHistory, SpanWithHistory as wasmSpanWithHistory,
reconcileWithHistory as wasmReconcileWithHistory, reconcileWithHistory as wasmReconcileWithHistory,
isBinary as wasmIsBinary, isBinary as wasmIsBinary,
getCompactDiff as wasmGetCompactDiff,
initSync, initSync,
} from 'reconcile-text'; } from 'reconcile-text';
@ -179,6 +180,26 @@ export function reconcile(
return jsResult; return jsResult;
} }
export function getCompactDiff(
original: string,
changed: string | TextWithOptionalCursors,
tokenizer: BuiltinTokenizer = 'Word'
): string {
init();
if (!BUILTIN_TOKENIZERS.includes(tokenizer)) {
throw new Error(UNSUPPORTED_TOKENIZER_ERROR);
}
const changedWasm = toWasmTextWithCursors(changed);
const result = wasmGetCompactDiff(original, changedWasm, tokenizer);
changedWasm.free();
return result;
}
/** /**
* Merges three versions of text and returns detailed provenance information. * Merges three versions of text and returns detailed provenance information.
* *

View file

@ -170,7 +170,7 @@
//! &changes.into() //! &changes.into()
//! ); //! );
//! //!
//! let serialized = serde_yaml::to_string(&result.serialise_as_change_set()).unwrap(); //! let serialized = serde_yaml::to_string(&result.to_change_set()).unwrap();
//! assert_eq!( //! assert_eq!(
//! serialized, //! serialized,
//! concat!( //! concat!(

View file

@ -350,7 +350,7 @@ where
/// This is useful for sending changes over the network if there's /// This is useful for sending changes over the network if there's
/// a clear consensus on the original text. /// a clear consensus on the original text.
#[must_use] #[must_use]
pub fn serialise_as_change_set(&self) -> ChangeSet { pub fn to_change_set(&self) -> ChangeSet {
ChangeSet::new( ChangeSet::new(
SimpleOperation::from_operations(&self.operations), SimpleOperation::from_operations(&self.operations),
self.cursors.clone(), self.cursors.clone(),
@ -428,7 +428,7 @@ mod tests {
let original = "Merging text is hard!"; let original = "Merging text is hard!";
let changes = "Merging text is easy with reconcile!"; let changes = "Merging text is easy with reconcile!";
let result = EditedText::from_strings(original, &changes.into()); let result = EditedText::from_strings(original, &changes.into());
let serialized = serde_yaml::to_string(&result.serialise_as_change_set()).unwrap(); let serialized = serde_yaml::to_string(&result.to_change_set()).unwrap();
let expected = concat!( let expected = concat!(
"operations:\n", "operations:\n",
@ -448,7 +448,7 @@ mod tests {
let edited_text = EditedText::from_strings(original, &updated.into()); let edited_text = EditedText::from_strings(original, &updated.into());
let change_set = edited_text.serialise_as_change_set(); let change_set = edited_text.to_change_set();
let deserialized_edited_text = let deserialized_edited_text =
EditedText::from_change_set(original, change_set, &*BuiltinTokenizer::Word); EditedText::from_change_set(original, change_set, &*BuiltinTokenizer::Word);

View file

@ -147,7 +147,7 @@ impl<'de> Deserialize<'de> for SimpleOperation {
type Value = SimpleOperation; type Value = SimpleOperation;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("an integer between -2^64 and 2^63 or a string") formatter.write_str("an integer between -2^63 and 2^64-1 or a string")
} }
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E> fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>

View file

@ -87,6 +87,25 @@ pub fn generic_reconcile(
} }
} }
/// WASM wrapper around getting a compact diff representation as a JSON string
///
/// # Panics
///
/// If serialization to JSON fails which should not happen
#[wasm_bindgen(js_name = getCompactDiff)]
#[must_use]
pub fn get_compact_diff(
parent: &str,
changed: &TextWithCursors,
tokenizer: BuiltinTokenizer,
) -> String {
set_panic_hook();
let edited_text = crate::EditedText::from_strings_with_tokenizer(parent, changed, &*tokenizer);
let change_set = edited_text.to_change_set();
serde_json::to_string(&change_set).expect("Failed to serialize change set")
}
/// Heuristically determine if the given data is a binary or a text file's /// Heuristically determine if the given data is a binary or a text file's
/// content. /// content.
#[wasm_bindgen(js_name = isBinary)] #[wasm_bindgen(js_name = isBinary)]

View file

@ -46,12 +46,11 @@ fn test_document_one_way_with_cursors_and_serialisation() {
&*BuiltinTokenizer::Word, &*BuiltinTokenizer::Word,
); );
let serialised_left = serde_yaml::from_str( let serialised_left =
&serde_yaml::to_string(&left_operations.serialise_as_change_set()).unwrap(), serde_yaml::from_str(&serde_yaml::to_string(&left_operations.to_change_set()).unwrap())
) .unwrap();
.unwrap();
let serialised_right = serde_yaml::from_str( let serialised_right = serde_yaml::from_str(
&serde_yaml::to_string(&right_operations.serialise_as_change_set()).unwrap(), &serde_yaml::to_string(&right_operations.to_change_set()).unwrap(),
) )
.unwrap(); .unwrap();

View file

@ -46,7 +46,7 @@ fn test_merge_text_with_cursors() {
} }
#[wasm_bindgen_test(unsupported = test)] #[wasm_bindgen_test(unsupported = test)]
fn merge_binary() { fn test_merge_binary() {
let left = [0, 1, 2]; let left = [0, 1, 2];
let right = [3, 4, 5]; let right = [3, 4, 5];
assert_eq!( assert_eq!(
@ -62,6 +62,14 @@ fn test_is_binary() {
assert!(!is_binary(b"hello")); assert!(!is_binary(b"hello"));
} }
#[wasm_bindgen_test(unsupported = test)]
fn test_get_compact_diff() {
let parent = "hello ";
let changed = "world";
let result = get_compact_diff(parent, &changed.into(), BuiltinTokenizer::Word);
assert_eq!(result, "{\"operations\":[-6,\"world\"],\"cursors\":[]}");
}
#[wasm_bindgen_test(unsupported = test)] #[wasm_bindgen_test(unsupported = test)]
fn test_is_binary_empty() { fn test_is_binary_empty() {
assert!(!is_binary(b"")); assert!(!is_binary(b""));