diff options
author | i love my dog <50598611+ms-jpq@users.noreply.github.com> | 2024-03-23 03:25:39 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-03-23 03:25:39 -0700 |
commit | 3c17cc5651d28477a44904a1d180937a2a3bde50 (patch) | |
tree | 52ef0b74212935c101c73a40e06c11e2daca9db9 | |
parent | 34e6629013270321ed6037c1f680e9d125a0460c (diff) | |
parent | cdd77a853b9bcd6a247db178f36d67af7448d01c (diff) |
Merge pull request #313 from ms-jpq/devv0.4.26
less alloc
-rw-r--r-- | Cargo.lock | 2 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | RELEASE_NOTES.md | 6 | ||||
-rw-r--r-- | src/displace.rs | 41 | ||||
-rw-r--r-- | src/fs_pipe.rs | 54 | ||||
-rw-r--r-- | src/fzf.rs | 4 | ||||
-rw-r--r-- | src/input.rs | 28 | ||||
-rw-r--r-- | src/main.rs | 13 | ||||
-rw-r--r-- | src/udiff.rs | 73 | ||||
-rw-r--r-- | src/udiff_spec.rs | 35 |
10 files changed, 136 insertions, 122 deletions
@@ -467,7 +467,7 @@ dependencies = [ [[package]] name = "sad" -version = "0.4.25" +version = "0.4.26" dependencies = [ "aho-corasick", "ansi_term", @@ -3,7 +3,7 @@ authors = ["git@bigly.dog"] description = "Space Age seD | https://github.com/ms-jpq/sad" edition = "2021" name = "sad" -version = "0.4.25" +version = "0.4.26" [dependencies] diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 4386ea7..291d9f9 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,6 +1,10 @@ ## Good news -v0.4.24 +v0.4.26 + +- Reduce allocations + +- Preview multi-selects - Fewer dependencies diff --git a/src/displace.rs b/src/displace.rs index d354e84..919fa07 100644 --- a/src/displace.rs +++ b/src/displace.rs @@ -2,12 +2,12 @@ use { super::{ argparse::{Action, Engine, Options}, fs_pipe::{slurp, spit}, - input::LineIn, + input::RowIn, types::Die, udiff::{apply_patches, patches, pure_diffs, udiff}, }, ansi_term::Colour, - std::{ffi::OsString, path::PathBuf, sync::Arc}, + std::{borrow::ToOwned, ffi::OsString, path::PathBuf}, }; impl Engine { @@ -19,7 +19,7 @@ impl Engine { } } -impl LineIn { +impl RowIn { const fn path(&self) -> &PathBuf { match self { Self::Entire(path) | Self::Piecewise(path, _) => path, @@ -27,9 +27,9 @@ impl LineIn { } } -pub async fn displace(opts: &Arc<Options>, input: LineIn) -> Result<OsString, Die> { +pub async fn displace(opts: &Options, input: RowIn) -> Result<OsString, Die> { let path = input.path().clone(); - let name = opts + let mut name = opts .cwd .as_ref() .and_then(|cwd| path.strip_prefix(cwd).ok()) @@ -39,29 +39,34 @@ pub async fn displace(opts: &Arc<Options>, input: LineIn) -> Result<OsString, Di let slurped = slurp(&path).await?; let before = slurped.content; - let after = opts.engine.replace(&before); + let replaced = { + let b = before.clone().into_iter().collect::<String>(); + opts.engine.replace(&b) + }; + let after = replaced + .split_inclusive('\n') + .map(ToOwned::to_owned) + .collect::<Vec<_>>(); if *before == after { Ok(OsString::new()) } else { let print = match (&opts.action, input) { - (Action::Preview, LineIn::Entire(_)) => udiff(None, opts.unified, &name, &before, &after), - (Action::Preview, LineIn::Piecewise(_, ranges)) => { + (Action::Preview, RowIn::Entire(_)) => udiff(None, opts.unified, &name, &before, &after), + (Action::Preview, RowIn::Piecewise(_, ranges)) => { udiff(Some(&ranges), opts.unified, &name, &before, &after) } - (Action::Commit, LineIn::Entire(_)) => { - spit(&path, &slurped.meta, &after).await?; - let mut out = name; - out.push("\n"); - out + (Action::Commit, RowIn::Entire(_)) => { + spit(&path, &slurped.meta, after).await?; + name.push("\n"); + name } - (Action::Commit, LineIn::Piecewise(_, ranges)) => { + (Action::Commit, RowIn::Piecewise(_, ranges)) => { let patches = patches(opts.unified, &before, &after); let after = apply_patches(patches, &ranges, &before); - spit(&path, &slurped.meta, &after).await?; - let mut out = name; - out.push("\n"); - out + spit(&path, &slurped.meta, after).await?; + name.push("\n"); + name } (Action::FzfPreview(_, _), _) => { let ranges = pure_diffs(opts.unified, &before, &after); diff --git a/src/fs_pipe.rs b/src/fs_pipe.rs index 96812b7..b7f9ca4 100644 --- a/src/fs_pipe.rs +++ b/src/fs_pipe.rs @@ -1,20 +1,20 @@ use { super::types::Die, - std::{borrow::ToOwned, fs::Metadata, io::ErrorKind, path::Path}, + std::{borrow::ToOwned, fs::Metadata, path::Path}, tokio::{ fs::{rename, File, OpenOptions}, - io::{AsyncReadExt, AsyncWriteExt, BufWriter}, + io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter}, }, uuid::Uuid, }; pub struct Slurpee { pub meta: Metadata, - pub content: String, + pub content: Vec<String>, } pub async fn slurp(path: &Path) -> Result<Slurpee, Die> { - let mut fd = File::open(path) + let fd = File::open(path) .await .map_err(|e| Die::IO(path.to_owned(), e.kind()))?; @@ -23,21 +23,37 @@ pub async fn slurp(path: &Path) -> Result<Slurpee, Die> { .await .map_err(|e| Die::IO(path.to_owned(), e.kind()))?; - let content = if meta.is_file() { - let mut s = String::new(); - match fd.read_to_string(&mut s).await { - Ok(_) => s, - Err(err) if err.kind() == ErrorKind::InvalidData => s, - Err(err) => return Err(Die::IO(path.to_owned(), err.kind())), + let mut slurm = Slurpee { + meta, + content: Vec::new(), + }; + if slurm.meta.is_file() { + let mut reader = BufReader::new(fd); + loop { + let mut buf = Vec::new(); + match reader.read_until(b'\n', &mut buf).await { + Err(err) => return Err(Die::IO(path.to_owned(), err.kind())), + Ok(0) => break, + Ok(_) => { + if let Ok(s) = String::from_utf8(buf) { + slurm.content.push(s); + } else { + slurm.content.clear(); + return Ok(slurm); + } + } + } } - } else { - String::new() }; - Ok(Slurpee { meta, content }) + Ok(slurm) } -pub async fn spit(canonical: &Path, meta: &Metadata, text: &str) -> Result<(), Die> { +pub async fn spit( + canonical: &Path, + meta: &Metadata, + text: Vec<impl AsRef<[u8]> + Send>, +) -> Result<(), Die> { let uuid = Uuid::new_v4().as_simple().to_string(); let mut file_name = canonical .file_name() @@ -58,10 +74,12 @@ pub async fn spit(canonical: &Path, meta: &Metadata, text: &str) -> Result<(), D .map_err(|e| Die::IO(tmp.clone(), e.kind()))?; let mut writer = BufWriter::new(fd); - writer - .write_all(text.as_bytes()) - .await - .map_err(|e| Die::IO(tmp.clone(), e.kind()))?; + for t in text { + writer + .write_all(t.as_ref()) + .await + .map_err(|e| Die::IO(tmp.clone(), e.kind()))?; + } writer .flush() @@ -83,8 +83,8 @@ pub fn stream_fzf_proc( args: arguments, env: fzf_env, }; - stream_subproc(cmd, stream).then(|line| async { - match line { + stream_subproc(cmd, stream).then(|row| async { + match row { Ok(o) => Ok(o), Err(Die::BadExit(_, 130)) => Err(Die::Interrupt), e => { diff --git a/src/input.rs b/src/input.rs index c5a5c95..20cfc5e 100644 --- a/src/input.rs +++ b/src/input.rs @@ -22,20 +22,20 @@ use { }; #[derive(Debug)] -pub enum LineIn { +pub enum RowIn { Entire(PathBuf), Piecewise(PathBuf, HashSet<DiffRange>), } #[derive(Debug)] -struct DiffLine(PathBuf, DiffRange); +struct DiffRow(PathBuf, DiffRange); -fn p_line(line: &str) -> Result<DiffLine, Die> { +fn p_row(row: &str) -> Result<DiffRow, Die> { let f = || Die::ArgumentError(String::new()); let ff = |_| f(); let preg = "\n\n\n\n@@ -(\\d+),(\\d+) \\+(\\d+),(\\d+) @@$"; let re = Regex::new(preg).map_err(Die::RegexError)?; - let captures = re.captures(line).ok_or_else(f)?; + let captures = re.captures(row).ok_or_else(f)?; let before_start = captures .get(1) @@ -66,11 +66,11 @@ fn p_line(line: &str) -> Result<DiffLine, Die> { before: (before_start - 1, before_inc), after: (after_start - 1, after_inc), }; - let path = PathBuf::from(String::from(re.replace(line, ""))); - Ok(DiffLine(path, range)) + let path = PathBuf::from(String::from(re.replace(row, ""))); + Ok(DiffRow(path, range)) } -async fn stream_patch(patches: &Path) -> impl Stream<Item = Result<LineIn, Die>> { +async fn stream_patch(patches: &Path) -> impl Stream<Item = Result<RowIn, Die>> { let patches = patches.to_owned(); let fd = match File::open(&patches).await { @@ -99,12 +99,12 @@ async fn stream_patch(patches: &Path) -> impl Stream<Item = Result<LineIn, Die>> let ranges = s.3; s.2 = PathBuf::new(); s.3 = HashSet::new(); - Ok(Some((Some(LineIn::Piecewise(path, ranges)), s))) + Ok(Some((Some(RowIn::Piecewise(path, ranges)), s))) } Some(buf) => { - let line = + let row = String::from_utf8(buf).map_err(|_| Die::IO(s.1.clone(), ErrorKind::InvalidData))?; - let parsed = p_line(&line)?; + let parsed = p_row(&row)?; if parsed.0 == s.2 { s.3.insert(parsed.1); Ok(Some((None, s))) @@ -117,7 +117,7 @@ async fn stream_patch(patches: &Path) -> impl Stream<Item = Result<LineIn, Die>> if ranges.is_empty() { Ok(Some((None, s))) } else { - Ok(Some((Some(LineIn::Piecewise(path, ranges)), s))) + Ok(Some((Some(RowIn::Piecewise(path, ranges)), s))) } } } @@ -147,7 +147,7 @@ fn u8_pathbuf(v8: Vec<u8>) -> PathBuf { } } -fn stream_stdin(use_nul: bool) -> impl Stream<Item = Result<LineIn, Die>> { +fn stream_stdin(use_nul: bool) -> impl Stream<Item = Result<RowIn, Die>> { if io::stdin().is_terminal() { let err = Die::ArgumentError("/dev/stdin connected to tty".to_owned()); return Either::Left(once(ready(Err(err)))); @@ -171,7 +171,7 @@ fn stream_stdin(use_nul: bool) -> impl Stream<Item = Result<LineIn, Die>> { Err(e) => Err(Die::IO(path, e.kind())), Ok(canonical) => Ok(Some({ if s.1.insert(canonical.clone()) { - (Some(LineIn::Entire(canonical)), s) + (Some(RowIn::Entire(canonical)), s) } else { (None, s) } @@ -184,7 +184,7 @@ fn stream_stdin(use_nul: bool) -> impl Stream<Item = Result<LineIn, Die>> { Either::Right(stream.try_filter_map(|x| ready(Ok(x)))) } -pub async fn stream_in(mode: &Mode, args: &Arguments) -> impl Stream<Item = Result<LineIn, Die>> { +pub async fn stream_in(mode: &Mode, args: &Arguments) -> impl Stream<Item = Result<RowIn, Die>> { match mode { Mode::Initial => Either::Left(stream_stdin(args.read0)), Mode::Preview(path) | Mode::Patch(path) => Either::Right(stream_patch(path).await), diff --git a/src/main.rs b/src/main.rs index a2321df..f47cf80 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,7 +32,6 @@ use { path::PathBuf, pin::pin, process::{ExitCode, Termination}, - sync::Arc, thread::available_parallelism, }, subprocess::{stream_into, stream_subproc}, @@ -67,7 +66,7 @@ async fn consume(stream: impl Stream<Item = Result<(), Die>> + Send) -> Result<( }); let out = select( stream - .filter_map(|row| async { row.err() }) + .filter_map(|row| ready(row.err())) .chain(once(ready(Die::Eof))), int, ); @@ -82,16 +81,12 @@ async fn run(threads: usize) -> Result<(), Die> { let (mode, args) = parse_args(); let input_stream = stream_in(&mode, &args).await; let opts = parse_opts(mode, args)?; - let options = Arc::new(opts); - let opts = options.clone(); + let trans_stream = input_stream - .map_ok(move |input| { - let opts = options.clone(); - async move { displace(&opts, input).await } - }) + .map_ok(|input| displace(&opts, input)) .try_buffer_unordered(threads); - let out_stream = stream_sink(&opts, trans_stream.boxed()); + consume(out_stream).await } diff --git a/src/udiff.rs b/src/udiff.rs index acc879c..212ce0e 100644 --- a/src/udiff.rs +++ b/src/udiff.rs @@ -1,5 +1,5 @@ use { - difflib::{sequencematcher::Opcode, sequencematcher::SequenceMatcher}, + difflib::sequencematcher::{Opcode, SequenceMatcher}, std::{ collections::HashSet, ffi::{OsStr, OsString}, @@ -34,11 +34,14 @@ impl Display for DiffRange { } } -pub fn pure_diffs(unified: usize, before: &str, after: &str) -> Vec<DiffRange> { - let before = before.split_inclusive('\n').collect::<Vec<_>>(); - let after = after.split_inclusive('\n').collect::<Vec<_>>(); +pub struct Patch<S> { + range: DiffRange, + new_lines: Vec<S>, +} + +pub fn pure_diffs(unified: usize, before: &[String], after: &[String]) -> Vec<DiffRange> { let mut ret = Vec::new(); - let mut matcher = SequenceMatcher::new(&before, &after); + let mut matcher = SequenceMatcher::new(before, after); for group in &matcher.get_grouped_opcodes(unified) { let range = DiffRange::new(group).expect("algo failure"); ret.push(range); @@ -46,30 +49,26 @@ pub fn pure_diffs(unified: usize, before: &str, after: &str) -> Vec<DiffRange> { ret } -pub struct Patch { - range: DiffRange, - new_lines: Vec<String>, -} - -pub fn patches(unified: usize, before: &str, after: &str) -> Vec<Patch> { - let before = before.split_inclusive('\n').collect::<Vec<_>>(); - let after = after.split_inclusive('\n').collect::<Vec<_>>(); - +pub fn patches<'a>( + unified: usize, + before: &'a [String], + after: &'a [String], +) -> Vec<Patch<&'a str>> { let mut ret = Vec::new(); - let mut matcher = SequenceMatcher::new(&before, &after); + let mut matcher = SequenceMatcher::new(before, after); for group in &matcher.get_grouped_opcodes(unified) { let mut new_lines = Vec::new(); for code in group { if code.tag == "equal" { for line in before.iter().take(code.first_end).skip(code.first_start) { - new_lines.push((*line).to_owned()); + new_lines.push(line.as_str()); } continue; } if code.tag == "replace" || code.tag == "insert" { for line in after.iter().take(code.second_end).skip(code.second_start) { - new_lines.push((*line).to_owned()); + new_lines.push(line); } } } @@ -82,9 +81,12 @@ pub fn patches(unified: usize, before: &str, after: &str) -> Vec<Patch> { ret } -pub fn apply_patches(patches: Vec<Patch>, ranges: &HashSet<DiffRange>, before: &str) -> String { - let before = before.split_inclusive('\n').collect::<Vec<_>>(); - let mut ret = String::new(); +pub fn apply_patches<'a>( + patches: Vec<Patch<&'a str>>, + ranges: &HashSet<DiffRange>, + before: &'a [String], +) -> Vec<&'a str> { + let mut ret = Vec::new(); let mut prev = 0; for diff in patches { @@ -93,28 +95,22 @@ pub fn apply_patches(patches: Vec<Patch>, ranges: &HashSet<DiffRange>, before: & for i in prev..before_start { before .get(i) - .map(|b| ret.push_str(b)) + .map(|b| ret.push(b.as_str())) .expect("algo failure"); } if ranges.contains(&diff.range) { for line in &diff.new_lines { - ret.push_str(line); + ret.push(line); } } else { for i in before_start..before_end { - before - .get(i) - .map(|b| ret.push_str(b)) - .expect("algo failure"); + before.get(i).map(|b| ret.push(b)).expect("algo failure"); } } prev = before_end; } for i in prev..before.len() { - before - .get(i) - .map(|b| ret.push_str(b)) - .expect("algo failure"); + before.get(i).map(|b| ret.push(b)).expect("algo failure"); } ret } @@ -123,12 +119,9 @@ pub fn udiff( ranges: Option<&HashSet<DiffRange>>, unified: usize, name: &OsStr, - before: &str, - after: &str, + before: &[String], + after: &[String], ) -> OsString { - let before = before.split_inclusive('\n').collect::<Vec<_>>(); - let after = after.split_inclusive('\n').collect::<Vec<_>>(); - let mut ret = OsString::new(); ret.push("diff --git "); @@ -145,10 +138,10 @@ pub fn udiff( ret.push(name); ret.push("\n"); - let mut matcher = SequenceMatcher::new(&before, &after); + let mut matcher = SequenceMatcher::new(before, after); for group in &matcher.get_grouped_opcodes(unified) { let range = DiffRange::new(group).expect("algo failure"); - if let Some(ranges) = &ranges { + if let Some(ranges) = ranges { if !ranges.contains(&range) { continue; } @@ -160,20 +153,20 @@ pub fn udiff( if code.tag == "equal" { for line in before.iter().take(code.first_end).skip(code.first_start) { ret.push(" "); - ret.push(*line); + ret.push(line); } continue; } if code.tag == "replace" || code.tag == "delete" { for line in before.iter().take(code.first_end).skip(code.first_start) { ret.push("-"); - ret.push(*line); + ret.push(line); } } if code.tag == "replace" || code.tag == "insert" { for line in after.iter().take(code.second_end).skip(code.second_start) { ret.push("+"); - ret.push(*line); + ret.push(line); } } } diff --git a/src/udiff_spec.rs b/src/udiff_spec.rs index ccf2323..2f7f60b 100644 --- a/src/udiff_spec.rs +++ b/src/udiff_spec.rs @@ -43,15 +43,25 @@ mod spec { .collect::<_>() } - fn diffs() -> Vec<(String, String)> { + fn diffs() -> Vec<(Vec<String>, Vec<String>)> { let texts = read_files(); let regexes = regexes(); let mut acc = Vec::new(); for text in texts { for re in ®exes { - let before = text.clone(); - let after = re.0.replace_all(text.as_str(), re.1.as_str()); - acc.push((before, after.to_string())); + let before = text + .clone() + .split_inclusive('\n') + .map(String::from) + .collect::<Vec<_>>(); + let after = re + .0 + .replace_all(text.as_str(), re.1.as_str()) + .to_string() + .split_inclusive('\n') + .map(String::from) + .collect::<Vec<_>>(); + acc.push((before, after)); } } acc @@ -66,17 +76,8 @@ mod spec { let ps = patches(unified, &before, &after); let patched = apply_patches(ps, &rangeset, &before); - - let canon = after - .split_inclusive('\n') - .map(String::from) - .collect::<Vec<_>>(); - let imp = patched - .split_inclusive('\n') - .map(String::from) - .collect::<Vec<_>>(); - assert_eq!(imp, canon); - assert_eq!(patched, after); + let imp = patched.into_iter().map(String::from).collect::<Vec<_>>(); + assert_eq!(imp, after); } } @@ -84,9 +85,7 @@ mod spec { fn unified() { let diffs = diffs(); for (unified, (before, after)) in diffs.into_iter().enumerate() { - let bb = before.split_inclusive('\n').collect::<Vec<_>>(); - let aa = after.split_inclusive('\n').collect::<Vec<_>>(); - let canon = unified_diff(&bb, &aa, "", "", "", "", unified) + let canon = unified_diff(&before, &after, "", "", "", "", unified) .iter() .skip(2) .map(|s| { |