ansi_diff/
lib.rs

1#![doc=include_str!("../readme.md")]
2use lazy_static::lazy_static as lstatic;
3type P = u32;
4
5const CLEAR_LINE: [u32;4] = [0x1b, 0x5b, 0x30, 0x4b];
6const NEWLINE: [u32;1] = [0x0a];
7
8/// Split a string by ansi codes.
9pub fn ansi_split(input: &str) -> Vec<String> {
10  lstatic! {
11    static ref IS_ANSI: regex::Regex = regex::Regex::new(
12      r#"(?x)
13        [\u001b\u009b][\[\]()\#;]*(?:(?:(?:[A-Za-z\d]*(?:;[A-Za-z\d]*)*)?\u0007)
14        | (?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PRZcf-ntqry=><~]))
15      "#,
16    ).unwrap();
17  };
18  let mut ptr = 0;
19  let mut result = vec![];
20  let ibytes = input.bytes().collect::<Vec<u8>>();
21  for c in IS_ANSI.captures_iter(input) {
22    let m = c.get(0).unwrap();
23    let part = m.as_str().to_string();
24    let offset = m.start();
25    if ptr != offset && ptr < offset {
26      result.push(String::from_utf8(ibytes[ptr..offset].to_vec()).unwrap());
27    }
28    if ptr == offset && !result.is_empty() {
29      result.last_mut().unwrap().push_str(&part);
30    } else {
31      if offset == 0 { result.push(String::default()) }
32      result.push(part);
33    }
34    ptr = m.end();
35  }
36  if ptr < ibytes.len() {
37    result.push(String::from_utf8(ibytes[ptr..].to_vec()).unwrap());
38  }
39  if result.is_empty() { return vec![input.to_string()] }
40  result
41}
42
43#[derive(Debug,Clone)]
44pub struct Diff {
45  x: P,
46  y: P,
47  width: P,
48  height: P,
49  buffer: String,
50  out: Vec<String>,
51  lines: Vec<Line>,
52}
53
54impl Diff {
55  /// Create a new Diff instance with a terminal `size` in characters: `(columns,lines)`.
56  pub fn new(size: (P,P)) -> Self {
57    Diff {
58      x: 0,
59      y: 0,
60      width: size.0,
61      height: size.1,
62      buffer: String::default(),
63      out: vec![],
64      lines: vec![],
65    }
66  }
67  /// Update the terminal `size` in characters: `(columns,lines)`.
68  pub fn resize(&mut self, size: (P,P)) {
69    self.width = size.0;
70    self.height = size.1;
71    let buf = self.buffer.clone();
72    self.update(&buf);
73    match self.lines.last() {
74      Some(last) => {
75        self.x = last.remainder;
76        self.y = last.y + last.height;
77      },
78      None => {
79        self.x = 0;
80        self.y = 0;
81      }
82    }
83  }
84  /// Set the output text to `buffer`. Returns the ansi output diff.
85  pub fn update(&mut self, buffer: &str) -> String {
86    self.buffer = buffer.to_string();
87    let next_lines = Line::split(buffer, self.width);
88    let min = next_lines.len().min(self.lines.len());
89    self.out = vec![];
90    let mut scrub = false;
91    for i in 0..min {
92      let a = next_lines.get(i).unwrap();
93      let (b_y, b_length, b_height) = {
94        let b = self.lines.get(i).unwrap();
95        if a == b { continue }
96        if !scrub && self.x != self.width && Self::inline_diff(&a,&b) {
97          let left = a.diff_left(b) as usize;
98          let right = a.diff_right(b) as usize;
99          let slice = &a.raw[left .. (a.raw.len() - right).max(left)];
100          if left + right > 4 && left + slice.len() < self.width as usize - 1 {
101            self.move_to(left as P, a.y);
102            self.push(&String::from_iter(slice));
103            self.x += slice.len() as P;
104            continue
105          }
106        }
107        (b.y, b.length, b.height)
108      };
109      self.move_to(0, a.y);
110      self.write(a);
111      if a.y != b_y || a.height != b_height { scrub = true }
112      if b_length > a.length || scrub { self.push(&to_str(CLEAR_LINE)) }
113      if a.newline { self.newline() }
114    }
115
116    for line in &next_lines[min..] {
117      self.move_to(0, line.y);
118      self.write(line);
119      if scrub { self.push(&to_str(CLEAR_LINE)) }
120      if line.newline { self.newline() }
121    }
122
123    let prev_last = self.lines.last();
124    let next_last = next_lines.last();
125    let clear = match (prev_last, next_last) {
126      (Some(plast),None) => Some(plast.y + plast.height),
127      (Some(plast),Some(nlast)) => {
128        if nlast.y + nlast.height < plast.y + plast.height {
129          Some(plast.y + plast.height)
130        } else {
131          None
132        }
133      },
134      (None,_) => None,
135    };
136    if let Some(n) = clear {
137      self.clear_down(n);
138    }
139
140    // todo: opts.move_to
141    if let Some(last) = next_last {
142      self.move_to(last.remainder, last.y + last.height);
143    }
144
145    self.lines = next_lines;
146    self.out.join("")
147  }
148  fn inline_diff(a: &Line, b: &Line) -> bool {
149    a.length == b.length
150      && a.parts.len() == 1 && b.parts.len() == 1
151      && a.y == b.y
152      && a.newline && b.newline
153      && a.width == b.width
154  }
155  fn move_to(&mut self, x: P, y: P) {
156    if x > self.x { self.push(&Self::move_right(x - self.x)) }
157    else if x < self.x { self.push(&Self::move_left(self.x - x)) }
158    if y > self.y { self.push(&Self::move_down(y - self.y)) }
159    else if y < self.y { self.push(&Self::move_up(self.y - y)) }
160    self.x = x;
161    self.y = y;
162  }
163  fn push(&mut self, buf: &str) {
164    self.out.push(buf.to_string());
165  }
166  fn newline(&mut self) {
167    self.push(&to_str(NEWLINE));
168    self.x = 0;
169    self.y += 1;
170  }
171  fn clear_down(&mut self, y: P) {
172    let mut x = self.x;
173    let mut i = self.y;
174    while i <= y {
175      self.move_to(x, i);
176      self.push(&to_str(CLEAR_LINE));
177      x = 0;
178      i += 1;
179    }
180  }
181  fn move_up(n: P) -> String { code1(&[0x1b, 0x5b], n, &[0x41]) }
182  fn move_down(n: P) -> String { code1(&[0x1b, 0x5b], n, &[0x42]) }
183  fn move_right(n: P) -> String { code1(&[0x1b, 0x5b], n, &[0x43]) }
184  fn move_left(n: P) -> String { code1(&[0x1b, 0x5b], n, &[0x44]) }
185  fn write(&mut self, line: &Line) {
186    self.out.push(line.to_string());
187    self.x = line.remainder;
188    self.y += line.height;
189  }
190}
191
192impl ToString for Diff {
193  fn to_string(&self) -> String {
194    self.buffer.clone()
195  }
196}
197
198#[derive(Debug,Clone)]
199struct Line {
200  y: P,
201  width: P,
202  height: P,
203  parts: Vec<String>,
204  length: usize,
205  raw: Vec<char>,
206  newline: bool,
207  remainder: P,
208}
209
210impl Line {
211  pub fn new(s: &str, y: P, nl: bool, term_width: P) -> Self {
212    let parts = ansi_split(s);
213    let length = Self::parts_len(&parts);
214    let mut height = length as P / term_width;
215    let mut remainder = length as P - (height * term_width);
216    if height > 0 && remainder == 0 {
217      height -= 1;
218      remainder = term_width;
219    }
220    Self {
221      y,
222      width: term_width,
223      parts,
224      length,
225      raw: s.chars().collect(),
226      newline: nl,
227      height,
228      remainder,
229    }
230  }
231  pub fn diff_left(&self, other: &Self) -> P {
232    let mut left = 0_usize;
233    while left < self.length {
234      if self.raw.get(left) != other.raw.get(left) { break }
235      left += 1;
236    }
237    left as P
238  }
239  pub fn diff_right(&self, other: &Self) -> P {
240    let mut right = 0;
241    while right < self.length {
242      let r = self.length - right - 1;
243      if self.raw.get(r) != other.raw.get(r) { break }
244      right += 1;
245    }
246    right as P
247  }
248  pub fn split(input: &str, term_width: P) -> Vec<Self> {
249    let mut y = 0;
250    let lines = input.split('\n').collect::<Vec<&str>>();
251    let len = lines.len();
252    lines.iter().enumerate().map(move |(i,line)| {
253      let line = Line::new(line, y, i < len - 1, term_width);
254      y += line.height + (if line.newline { 1 } else { 0 });
255      line
256    }).collect()
257  }
258  fn parts_len(parts: &[String]) -> usize {
259    let mut sum = 0;
260    let mut i = 0;
261    while i < parts.len() {
262      sum += parts.get(i).unwrap().len();
263      i += 2;
264    }
265    sum
266  }
267}
268
269impl ToString for Line {
270  fn to_string(&self) -> String {
271    String::from_iter(self.raw.iter())
272  }
273}
274
275impl PartialEq for Line {
276  fn eq(&self, other: &Self) -> bool {
277    self.y == other.y && self.width == other.width
278      && self.raw == other.raw && self.newline == other.newline
279  }
280}
281
282fn to_str<const N: usize>(xs: [u32;N]) -> String {
283  let chars = xs.iter().map(|c| char::from_u32(*c).unwrap()).collect::<Vec<char>>();
284  String::from_iter(&chars)
285}
286
287fn code1(pre: &[u32], n: P, post: &[u32]) -> String {
288  let sn = format!["{}", n];
289  let spre = String::from_iter(pre.iter().map(|c| char::from_u32(*c).unwrap()));
290  let spost = String::from_iter(post.iter().map(|c| char::from_u32(*c).unwrap()));
291  spre + &sn + &spost
292}