my fork of dmp
| -rw-r--r-- | CHANGELOG.md | 9 | ||||
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | README.md | 9 | ||||
| -rw-r--r-- | src/dmp.rs | 35 | ||||
| -rw-r--r-- | tests/test.rs | 145 |
5 files changed, 193 insertions, 7 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index e96c82d..23d0fa0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # CHANGELOG.md +## 0.5.0 +Features: + + - `diff_x_index( .. )` API contributed by @acarl005 + +Fix: + + - Fix for [Issue](https://github.com/AnubhabB/diff-match-patch-rs/issues/14) by @aschamp-figma + ## 0.4.1 Minor performance & cleanup to Diff: @@ -1,6 +1,6 @@ [package] name = "diff-match-patch-rs" -version = "0.4.1" +version = "0.5.0" edition = "2021" authors = ["Anubhab Bandyopadhyay"] homepage = "https://docs.rs/diff-match-patch-rs" @@ -39,7 +39,7 @@ Benchmarks are maintained [diff-match-patch-bench repository](https://github.com ```toml [dependencies] -diff-match-patch-rs = "0.4.1" +diff-match-patch-rs = "0.5.0" ``` ### `Effitient` mode @@ -166,7 +166,7 @@ fn main() -> Result<(), Error> { ### `Match` - fuzzy match of pattern in Text ```rust -use diff_match_patch_rs::{DiffMatchPatch, Compat, Error, PatchInput}; +use diff_match_patch_rs::{DiffMatchPatch, Compat, Error, PatchInput, Efficient}; // This is the source text const TXT: &str = "I am the very model of a modern Major-General, I've information on vegetable, animal, and mineral, 🚀👏👀"; @@ -175,12 +175,13 @@ const TXT: &str = "I am the very model of a modern Major-General, I've informati const PATTERN: &str = " that berry "; // Returns `location` of match if found, `None` if not found -fn main() -> Option<usize> { +fn main() { let dmp = DiffMatchPatch::new(); // works with both `Efficient` and `Compat` modes // `5` here is an approx location to find `nearby` matches - dmp.match_main::<Efficient>(TXT, PATTERN, 5) // this should return Some(4) + let match_at = dmp.match_main::<Efficient>(TXT, PATTERN, 5); // this should return Some(4) + println!("Matched at: {match_at:?}"); } ``` @@ -709,7 +709,7 @@ impl DiffMatchPatch { // Do all setup before casting to isize let mut v; let (v_offset, v_len, v1, v2, old_len, new_len) = { - let v_offset = (old.len() + new.len() + 1) / 2; + let v_offset = (old.len() + new.len()).div_ceil(2); let v_len = v_offset * 2; v = vec![-1 as Int; v_len * 2]; @@ -1300,7 +1300,7 @@ impl DiffMatchPatch { if p_prev < diffs.len() && del_idx_end <= delete.len() { diffs[p_prev].1 = delete[..del_idx_end].to_vec(); } - if p_next < diffs.len() && overlap_1 < insert.len() { + if p_next < diffs.len() && overlap_1 <= insert.len() { diffs[p_next].1 = insert[overlap_1..].to_vec(); } pointer += 1; @@ -4459,6 +4459,37 @@ mod tests { ]; DiffMatchPatch::cleanup_semantic(&mut diffs); assert_eq!(test, diffs); + + // overlap that fully eliminates an delete + let mut diffs = vec![ + Diff::delete(&"abcd".chars().collect::<Vec<_>>()[..]), + Diff::insert(&"abcd1212".chars().collect::<Vec<_>>()[..]), + Diff::equal(&"wxyz".chars().collect::<Vec<_>>()[..]), + ]; + let test = vec![ + Diff::delete(&"".chars().collect::<Vec<_>>()[..]), + Diff::equal(&"abcd".chars().collect::<Vec<_>>()[..]), + Diff::insert(&"1212".chars().collect::<Vec<_>>()[..]), + Diff::equal(&"wxyz".chars().collect::<Vec<_>>()[..]), + ]; + DiffMatchPatch::cleanup_semantic(&mut diffs); + assert_eq!(test, diffs); + + // overlap that fully eliminates an insert + let mut diffs = vec![ + Diff::delete(&"abcd1212".chars().collect::<Vec<_>>()[..]), + Diff::insert(&"1212".chars().collect::<Vec<_>>()[..]), + Diff::equal(&"wxyz".chars().collect::<Vec<_>>()[..]), + ]; + let test = vec![ + Diff::delete(&"abcd".chars().collect::<Vec<_>>()[..]), + Diff::equal(&"1212".chars().collect::<Vec<_>>()[..]), + Diff::insert(&"".chars().collect::<Vec<_>>()[..]), + Diff::equal(&"wxyz".chars().collect::<Vec<_>>()[..]), + ]; + DiffMatchPatch::cleanup_semantic(&mut diffs); + assert_eq!(test, diffs); + } #[test] diff --git a/tests/test.rs b/tests/test.rs index 5e96a5f..35cd52d 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -1357,3 +1357,148 @@ fn test_match_main() { ) ); } + +// Test for Issue: https://github.com/AnubhabB/diff-match-patch-rs/issues/14 +#[test] +fn test_patch_repeats_todo_import() -> Result<(), Error> { + let dmp = DiffMatchPatch::default(); + + // Create source and target strings + let source = r##"( +import { useState, useEffect } from "react"; +import { v4 as uuidv4 } from "uuid"; +import { TodoInput } from "./components/TodoInput"; +import { TodoList, Todo } from "./components/TodoList"; +import { TodoFilter, FilterStatus } from "./components/TodoFilter"; +import { Card, CardHeader, CardTitle, CardContent } from "./components/ui/card"; + +// Mock implementation of uuid since we don't have access to the actual package +const mockUuid = (): string => { + return Math.random().toString(36).substring(2, 15) + + Math.random().toString(36).substring(2, 15); +}; + +export default function App() { + const [todos, setTodos] = useState<Todo[]>(() => { + // Try to load todos from localStorage on initial render + const savedTodos = localStorage.getItem("todos"); + return savedTodos ? JSON.parse(savedTodos) : [ + { id: mockUui)"##; + + let target = r##"( +import { useState, useEffect } from "react"; +import { TodoInput } from "./components/TodoInput"; +import { TodoList, Todo } from "./components/TodoList"; +import { TodoFilter, FilterStatus } from "./components/TodoFilter"; +import { Card, CardHeader, CardTitle, CardContent } from "./components/ui/card"; + +// Mock implementation of uuid since we don't have access to the actual package +const mockUuid = (): string => { + return Math.random().toString(36).substring(2, 15) + + Math.random().toString(36).substring(2, 15); +}; + +export default function App() { + const [todos, setTodos] = useState<Todo[]>(() => { + // Try to load todos from localStorage on initial render + const savedTodos = localStorage.getItem("todos"); + return savedTodos ? JSON.parse(savedTodos) : [ + { id: mockUuid(), text: "Learn React", completed: true }, + { id: mockUuid(), text: "Build a todo app", completed: false }, + { id: mockUuid(), text: "Deploy to production", completed: false }, + ]; + }); + + const [activeFilter, setActiveFilter] = useState<FilterStatus>("all"); + + // Save todos to localStorage whenever they change + useEffect(() => { + localStorage.setItem("todos", JSON.stringify(todos)); + }, [todos]); + + // Calculate counts for the filter component + const todoCount = { + all: todos.length, + active: todos.filter(todo => !todo.completed).length, + completed: todos.filter(todo => todo.completed).length, + }; + + // Add a new todo + const handleAddTodo = (text: string) => { + setTodos([ + ...todos, + { + id: mockUuid(), + text, + completed: false, + }, + ]); + }; + + // Toggle todo completion status + const handleToggleComplete = (id: string) => { + setTodos( + todos.map(todo => + todo.id === id ? { ...todo, completed: !todo.completed } : todo + ) + ); + }; + + // Delete a todo + const handleDeleteTodo = (id: string) => { + setTodos(todos.filter(todo => todo.id !== id)); + }; + + return ( + <div className="min-h-screen bg-background flex items-center justify-center p-4"> + <div className="w-full max-w-md"> + <Card className="shadow-lg"> + <CardHeader className="pb-3"> + <CardTitle className="text-xl font-semibold text-center">Todo List</CardTitle> + </CardHeader> + <CardContent> + <TodoInput onAddTodo={handleAddTodo} /> + + <TodoList + todos={todos} + activeFilter={activeFilter} + onToggleComplete={handleToggleComplete} + onDelete={handleDeleteTodo} + /> + + {todos.length > 0 && ( + <TodoFilter + activeFilter={activeFilter} + onFilterChange={setActiveFilter} + todoCount={todoCount} + /> + )} + </CardContent> + </Card> + </div> + </div> + ); +} +)"##; + + // Create patches to transform source into target + let patches = dmp.patch_make(PatchInput::Texts::<Efficient>(source, target))?; + + // Apply the patches to the source string + let (patched, results) = dmp.patch_apply(&patches, source)?; + + // Verify all patches were applied successfully + assert!(results.iter().all(|&result| result)); + + // Verify the patched string equals the target + assert_eq!(target, patched); + + // Also test with Compat mode + let patches_compat = dmp.patch_make(PatchInput::Texts::<Compat>(source, target))?; + let (patched_compat, results_compat) = dmp.patch_apply(&patches_compat, source)?; + + assert!(results_compat.iter().all(|&result| result)); + assert_eq!(target, patched_compat); + + Ok(()) +} |