summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authori love my dog <50598611+ms-jpq@users.noreply.github.com>2024-03-23 03:25:39 -0700
committerGitHub <noreply@github.com>2024-03-23 03:25:39 -0700
commit3c17cc5651d28477a44904a1d180937a2a3bde50 (patch)
tree52ef0b74212935c101c73a40e06c11e2daca9db9
parent34e6629013270321ed6037c1f680e9d125a0460c (diff)
parentcdd77a853b9bcd6a247db178f36d67af7448d01c (diff)
Merge pull request #313 from ms-jpq/devv0.4.26
less alloc
-rw-r--r--Cargo.lock2
-rw-r--r--Cargo.toml2
-rw-r--r--RELEASE_NOTES.md6
-rw-r--r--src/displace.rs41
-rw-r--r--src/fs_pipe.rs54
-rw-r--r--src/fzf.rs4
-rw-r--r--src/input.rs28
-rw-r--r--src/main.rs13
-rw-r--r--src/udiff.rs73
-rw-r--r--src/udiff_spec.rs35
10 files changed, 136 insertions, 122 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 143e9a0..4b844c3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -467,7 +467,7 @@ dependencies = [
[[package]]
name = "sad"
-version = "0.4.25"
+version = "0.4.26"
dependencies = [
"aho-corasick",
"ansi_term",
diff --git a/Cargo.toml b/Cargo.toml
index 0adca1a..8d94a0c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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()
diff --git a/src/fzf.rs b/src/fzf.rs
index e76ecdf..2a6c08d 100644
--- a/src/fzf.rs
+++ b/src/fzf.rs
@@ -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 &regexes {
- 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| {