| 1 | use std::sync::{Arc, Mutex}; |
| 2 | |
| 3 | pub(super) fn take_delta_from_buffer( |
| 4 | buffer: &Arc<Mutex<Vec<u8>>>, |
| 5 | cursor: &mut usize, |
| 6 | ) -> (Vec<u8>, usize) { |
| 7 | let guard = buffer.lock().unwrap_or_else(|e| e.into_inner()); |
| 8 | let total = guard.len(); |
| 9 | let start = (*cursor).min(total); |
| 10 | // Clone only the unread portion (the delta), not the entire accumulated buffer. |
| 11 | // Long-running processes can produce megabytes of output; cloning the full |
| 12 | // buffer on every poll held the ShellManager mutex for O(total_bytes) time. |
| 13 | let delta = guard[start..].to_vec(); |
| 14 | *cursor = total; |
| 15 | (delta, total) |
| 16 | } |
| 17 | |
| 18 | /// Read only the tail of a byte buffer and return (total_len, tail_string). |
| 19 | /// |
| 20 | /// Avoids cloning the full buffer when only a trailing excerpt is needed |
| 21 | /// (e.g. for the job-panel display). `max_tail_chars` is in Unicode scalar |
| 22 | /// values; we read at most `max_tail_chars * 4` bytes from the end to account |
| 23 | /// for multi-byte UTF-8 sequences. |
| 24 | pub(super) fn tail_from_buffer( |
| 25 | buffer: &Arc<Mutex<Vec<u8>>>, |
| 26 | max_tail_chars: usize, |
| 27 | ) -> (usize, String) { |
| 28 | let guard = buffer.lock().unwrap_or_else(|e| e.into_inner()); |
| 29 | let total = guard.len(); |
| 30 | // Over-estimate byte count (4 bytes per char worst case for UTF-8). |
| 31 | let mut tail_start = total.saturating_sub(max_tail_chars.saturating_mul(4)); |
| 32 | // Snap forward to the next valid UTF-8 codepoint boundary so we don't |
| 33 | // pass a slice beginning with continuation bytes (0x80-0xBF) to |
| 34 | // from_utf8_lossy, which would emit a leading U+FFFD replacement char. |
| 35 | while tail_start < total && (guard[tail_start] & 0xC0) == 0x80 { |
| 36 | tail_start += 1; |
| 37 | } |
| 38 | let tail_str = String::from_utf8_lossy(&guard[tail_start..]).into_owned(); |
| 39 | (total, tail_text(&tail_str, max_tail_chars)) |
| 40 | } |
| 41 | |
| 42 | fn tail_text(text: &str, max_chars: usize) -> String { |
| 43 | if text.chars().count() <= max_chars { |
| 44 | return text.to_string(); |
| 45 | } |
| 46 | let tail = text |
| 47 | .chars() |
| 48 | .rev() |
| 49 | .take(max_chars) |
| 50 | .collect::<Vec<_>>() |
| 51 | .into_iter() |
| 52 | .rev() |
| 53 | .collect::<String>(); |
| 54 | format!("...{tail}") |
| 55 | } |
| 56 |