reconcile-text 0.12.1

Intelligent 3-way text merging with automated conflict resolution
Documentation
use std::{fmt, str::Chars};

/// A helper for building a string sequentially from an original string via
/// insertions, deletions, and copies. All operations use character counts,
/// safe for UTF-8. Methods must be called in-order.
pub struct StringBuilder<'a> {
    original: Chars<'a>,
    buffer: String,

    #[cfg(debug_assertions)]
    remaining: String,
}

impl fmt::Debug for StringBuilder<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut debug_struct = f.debug_struct("StringBuilder");
        debug_struct.field("buffer", &self.buffer);

        #[cfg(debug_assertions)]
        debug_struct.field("remaining", &self.remaining);

        debug_struct.finish_non_exhaustive()
    }
}

impl StringBuilder<'_> {
    pub fn new(original: &str) -> StringBuilder<'_> {
        StringBuilder {
            original: original.chars(),
            buffer: String::with_capacity(original.len()),

            #[cfg(debug_assertions)]
            remaining: original.to_owned(),
        }
    }

    /// Insert a string at the end of the built buffer
    pub fn insert(&mut self, text: &str) {
        self.buffer.push_str(text);
    }

    /// Skip copying `length` characters from the original string to the built
    /// buffer
    pub fn delete(&mut self, length: usize) {
        if length == 0 {
            return;
        }

        self.original.nth(length - 1);

        #[cfg(debug_assertions)]
        {
            self.remaining = self.remaining.chars().skip(length).collect();
        }
    }

    /// Copy `length` characters from the original string to the built buffer
    pub fn retain(&mut self, length: usize) {
        self.buffer.extend(self.original.by_ref().take(length));

        #[cfg(debug_assertions)]
        {
            self.remaining = self.remaining.chars().skip(length).collect();
        }
    }

    /// Returns the currently built buffer and clears it to allow consuming
    /// the result incrementally.
    pub fn take(&mut self) -> String {
        std::mem::take(&mut self.buffer)
    }

    /// Get a slice of the remaining original string. The slice starts from
    /// where the next delete/retain operation would start and is of length
    /// `length`.
    #[cfg(debug_assertions)]
    pub fn get_slice_from_remaining(&self, length: usize) -> String {
        let result = self.remaining.chars().take(length).collect::<String>();

        debug_assert_eq!(result.chars().count(), length, "Range out of bounds");

        result
    }
}

#[cfg(test)]
mod tests {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn test_string_builder() {
        let original = "aaa bbb ccc";
        let mut builder = StringBuilder::new(original);

        builder.insert("ddd");
        builder.delete(3);
        builder.retain(8);
        builder.insert(" eee");

        assert_eq!(builder.take(), "ddd bbb ccc eee");

        let original = "abcde";
        let mut builder = StringBuilder::new(original);

        builder.retain(1);
        builder.delete(3);
        builder.retain(1);

        assert_eq!(builder.take(), "ae");
    }

    #[test]
    fn test_empty_original() {
        let original = "";
        let mut builder = StringBuilder::new(original);

        builder.insert("test");
        assert_eq!(builder.take(), "test");
    }

    #[test]
    fn test_unicode_characters() {
        let original = "こんにちは";
        let mut builder = StringBuilder::new(original);

        builder.retain(3);
        builder.insert("世界, "); // Insert "World, "
        builder.retain(2);

        assert_eq!(builder.take(), "こんに世界, ちは");
    }

    #[test]
    fn test_get_slice() {
        let original = "abcdef";
        let builder = StringBuilder::new(original);

        // Test getting a slice of the original string
        assert_eq!(builder.get_slice_from_remaining(3), "abc");

        // Test getting a slice that includes both buffer and remaining original
        let mut builder = StringBuilder::new(original);
        builder.retain(2); // "ab" in buffer
        assert_eq!(builder.get_slice_from_remaining(2), "cd");
    }

    #[test]
    fn test_retain_all() {
        let original = "Hello, world!";
        let mut builder = StringBuilder::new(original);

        builder.retain(original.len());
        assert_eq!(builder.take(), original);
    }

    #[test]
    fn test_delete_all() {
        let original = "Hello";
        let mut builder = StringBuilder::new(original);

        builder.delete(original.len());
        builder.insert("Hi");
        assert_eq!(builder.take(), "Hi");
    }
}