Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Text::update #44

Merged
merged 1 commit into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ repository = "https://github.com/automerge/autosurgeon"
license = "MIT"

[workspace.dependencies]
automerge = "0.5.0"
automerge-test = "0.4.0"
automerge = "0.5"
automerge-test = "0.4"
31 changes: 31 additions & 0 deletions autosurgeon-derive/tests/text.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use autosurgeon::Text;
use autosurgeon::{Hydrate, Reconcile};

#[derive(Hydrate, Reconcile)]
struct TextDoc {
content: Text,
}

#[test]
fn diff_generates_splices() {
let start = TextDoc {
content: Text::with_value("some value"),
};

let mut doc = automerge::AutoCommit::new();
autosurgeon::reconcile(&mut doc, &start).unwrap();
let mut doc2 = doc.fork();

let mut start2 = autosurgeon::hydrate::<_, TextDoc>(&doc).unwrap();
start2.content.update("some day");
autosurgeon::reconcile(&mut doc, &start2).unwrap();

let mut start3 = autosurgeon::hydrate::<_, TextDoc>(&doc2).unwrap();
start3.content.update("another value");
autosurgeon::reconcile(&mut doc2, &start3).unwrap();

doc.merge(&mut doc2).unwrap();

let start3 = autosurgeon::hydrate::<_, TextDoc>(&doc).unwrap();
assert_eq!(start3.content.as_str(), "another day");
}
2 changes: 1 addition & 1 deletion autosurgeon/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ license = { workspace = true }
[dependencies]
automerge = { workspace = true }
autosurgeon-derive = { path = "../autosurgeon-derive", version = "0.8.0" }
similar = "2.2.1"
similar = { version = "2.2.1", features = ["unicode"] }
thiserror = "1.0.37"
uuid = { version = "1.2.2", optional = true }

Expand Down
73 changes: 73 additions & 0 deletions autosurgeon/src/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,79 @@ impl Text {
}
}

/// Update the value of the text field by diffing it with a new string
///
/// This is useful if you can't capture the edits to a text field as they happen (i.e. the
/// insertion and deletion events) but instead you just get given the new value of the field.
/// This method will diff the new value with the current value and convert the diff into a set
/// of edits which are applied to the text field. This will produce more confusing merge
/// results than capturing the edits directly, but sometimes it's all you can do.
///
/// ## Example
///
/// ```rust
/// # use autosurgeon::{Hydrate, Reconcile, Text};
/// #[derive(Hydrate, Reconcile)]
/// struct TextDoc {
/// content: Text,
/// }
///
/// let start = TextDoc {
/// content: Text::with_value("some value"),
/// };
///
/// // Create the initial document
/// let mut doc = automerge::AutoCommit::new();
/// autosurgeon::reconcile(&mut doc, &start).unwrap();
///
/// // Fork the document so we can make concurrent changes
/// let mut doc2 = doc.fork();
///
/// // On one fork replace 'value' with 'day'
/// let mut start2 = autosurgeon::hydrate::<_, TextDoc>(&doc).unwrap();
/// // Note the use of `update` to replace the entire content instead of `splice`
/// start2.content.update("some day");
/// autosurgeon::reconcile(&mut doc, &start2).unwrap();
///
/// // On the other fork replace 'some' with 'another'
/// let mut start3 = autosurgeon::hydrate::<_, TextDoc>(&doc2).unwrap();
/// start3.content.update("another value");
/// autosurgeon::reconcile(&mut doc2, &start3).unwrap();
///
/// // Merge the two forks
/// doc.merge(&mut doc2).unwrap();
///
/// // The result is 'another day'
/// let start3 = autosurgeon::hydrate::<_, TextDoc>(&doc).unwrap();
/// assert_eq!(start3.content.as_str(), "another day");
/// ```
pub fn update<S: AsRef<str>>(&mut self, new_value: S) {
match &mut self.0 {
State::Fresh(v) => *v = new_value.as_ref().to_string(),
State::Rehydrated { value, .. } => {
let mut idx = 0;
let old = value.clone();
for change in similar::TextDiff::from_graphemes(old.as_str(), new_value.as_ref())
.iter_all_changes()
{
match change.tag() {
similar::ChangeTag::Delete => {
let len = change.value().len();
self.splice(idx, len as isize, "");
}
similar::ChangeTag::Insert => {
self.splice(idx, 0, change.value());
idx += change.value().len();
}
similar::ChangeTag::Equal => {
idx += change.value().len();
}
}
}
}
}
}

pub fn as_str(&self) -> &str {
match &self.0 {
State::Fresh(v) => v,
Expand Down
Loading