//! The lines of an insert (spec ยง7.4) held as one buffer and where each //! line ends, shared by the change and the file graph it builds: a tree of //! forty million lines is then a few allocations per insert, not one per //! line, and a graph's atoms point into their insert's buffer rather than //! copying it. use std::sync::Arc; #[derive(Default, PartialEq, Eq, Hash)] struct Inner { /// Shared with the file it was read from, when there was one. buf: Arc>, /// The end offset of each line in `buf`. ends: Vec, } /// An insert's lines. Cloning is cheap: the buffer is shared. #[derive(Clone, Default, PartialEq, Eq, Hash)] pub struct Lines(Arc); impl Lines { /// Lines from their bytes, each keeping its terminator; a final /// unterminated line is its own line (the rule of /// [`crate::diff::split_lines`]). pub fn split(content: &[u8]) -> Lines { Lines::split_shared(Arc::new(content.to_vec())) } /// [`Lines::split`] of a buffer held elsewhere too (a file's content), /// shared rather than copied. pub fn split_shared(buf: Arc>) -> Lines { let content = &buf[..]; let mut ends = Vec::new(); for (i, &c) in content.iter().enumerate() { if c == b'\n' { ends.push(i as u64 + 1); } } if ends.last().copied().unwrap_or(0) as usize != content.len() { ends.push(content.len() as u64); } Lines(Arc::new(Inner { buf, ends })) } pub fn len(&self) -> usize { self.0.ends.len() } pub fn is_empty(&self) -> bool { self.0.ends.is_empty() } /// Line `i`, with its terminator. pub fn get(&self, i: usize) -> Option<&[u8]> { let end = *self.0.ends.get(i)? as usize; let start = if i == 0 { 0 } else { self.0.ends[i - 1] as usize }; Some(&self.0.buf[start..end]) } pub fn iter(&self) -> impl ExactSizeIterator + Clone + '_ { (0..self.len()).map(move |i| self.get(i).unwrap()) } /// Every line, joined: the bytes they were split from. pub fn bytes(&self) -> &[u8] { &self.0.buf } /// Lines `range` as lines of their own. pub fn slice(&self, range: std::ops::Range) -> Lines { self.iter().skip(range.start).take(range.len()).collect() } /// The lines as owned vectors, for code that edits them one by one. pub fn to_vec(&self) -> Vec> { self.iter().map(<[u8]>::to_vec).collect() } } impl std::ops::Index for Lines { type Output = [u8]; fn index(&self, i: usize) -> &[u8] { self.get(i).expect("line index out of range") } } impl<'a> FromIterator<&'a [u8]> for Lines { fn from_iter>(iter: I) -> Lines { let (mut buf, mut ends) = (Vec::new(), Vec::new()); for l in iter { buf.extend_from_slice(l); ends.push(buf.len() as u64); } Lines(Arc::new(Inner { buf: Arc::new(buf), ends, })) } } impl FromIterator> for Lines { fn from_iter>>(iter: I) -> Lines { let (mut buf, mut ends) = (Vec::new(), Vec::new()); for l in iter { buf.extend_from_slice(&l); ends.push(buf.len() as u64); } Lines(Arc::new(Inner { buf: Arc::new(buf), ends, })) } } impl From>> for Lines { fn from(v: Vec>) -> Lines { v.into_iter().collect() } } impl std::fmt::Debug for Lines { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_list() .entries(self.iter().map(String::from_utf8_lossy)) .finish() } } /// One line of a [`Lines`], held by a file graph's atom. #[derive(Clone, Debug, PartialEq, Eq)] pub struct LineRef { lines: Lines, index: u32, } impl LineRef { pub fn new(lines: Lines, index: u32) -> LineRef { LineRef { lines, index } } pub fn bytes(&self) -> &[u8] { &self.lines[self.index as usize] } } #[cfg(test)] mod tests { use super::*; #[test] fn lines_split_index_and_join_as_the_line_rule_says() { for text in [&b""[..], b"a\n", b"a\nb", b"a\n\nb\n", b"\n", b"no end"] { let l = Lines::split(text); let want = crate::diff::split_lines(text); assert_eq!(l.len(), want.len(), "{text:?}"); assert_eq!(l.iter().collect::>(), want); assert_eq!(l.bytes(), text); let v: Lines = want.iter().copied().collect(); assert_eq!(v, l); assert_eq!(Lines::from(l.to_vec()), l); } let l = Lines::split(b"a\nb\nc\n"); assert_eq!(&l[1], b"b\n"); assert_eq!( l.slice(1..3).to_vec(), vec![b"b\n".to_vec(), b"c\n".to_vec()] ); assert_eq!(LineRef::new(l.clone(), 2).bytes(), b"c\n"); } }