Skip to content

Commit

Permalink
Fix reverse import order for zsh and nu
Browse files Browse the repository at this point in the history
  • Loading branch information
ajesipow committed Aug 24, 2024
1 parent 72a562a commit 3cca45b
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 29 deletions.
53 changes: 50 additions & 3 deletions crates/atuin-client/src/import/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ use std::fs::File;
use std::io::Read;
use std::path::PathBuf;

use crate::history::History;
use async_trait::async_trait;
use eyre::{bail, Result};
use memchr::Memchr;

use crate::history::History;

pub mod bash;
pub mod fish;
pub mod nu;
Expand All @@ -32,18 +31,21 @@ pub trait Loader: Sync + Send {
async fn push(&mut self, hist: History) -> eyre::Result<()>;
}

fn unix_byte_lines(input: &[u8]) -> impl Iterator<Item = &[u8]> {
fn unix_byte_lines(input: &[u8]) -> impl DoubleEndedIterator<Item = &[u8]> {
UnixByteLines {
iter: memchr::memchr_iter(b'\n', input),
bytes: input,
i: 0,
// Index for iterating in reverse order, set to the last element
i_back: input.len().checked_sub(1).unwrap_or(0),
}
}

struct UnixByteLines<'a> {
iter: Memchr<'a>,
bytes: &'a [u8],
i: usize,
i_back: usize,
}

impl<'a> Iterator for UnixByteLines<'a> {
Expand All @@ -64,6 +66,34 @@ impl<'a> Iterator for UnixByteLines<'a> {
}
}

impl<'a> DoubleEndedIterator for UnixByteLines<'a> {
fn next_back(&mut self) -> Option<Self::Item> {
let needle_idx = match self.iter.next_back() {
Some(v) => {
if v == self.i_back {
// The first newline at the very end of the input sequence, skip
self.iter.next_back()
} else {
Some(v)
}
}
None => None,
};
let range_start = if needle_idx.is_none() && self.i_back > 0 {
// Reached the very beginning of the input sequence
0
} else if needle_idx.is_none() && self.i_back == 0 {
return None;
} else {
// Do not include the found newline in the range
needle_idx.map(|v| v + 1)?
};
let out = &self.bytes[range_start..self.i_back];
self.i_back = needle_idx.unwrap_or(0);
Some(out)
}
}

fn count_lines(input: &[u8]) -> usize {
unix_byte_lines(input).count()
}
Expand Down Expand Up @@ -96,6 +126,7 @@ fn is_file(p: PathBuf) -> Result<PathBuf> {
#[cfg(test)]
mod tests {
use super::*;
use itertools::assert_equal;

#[derive(Default)]
pub struct TestLoader {
Expand All @@ -109,4 +140,20 @@ mod tests {
Ok(())
}
}

#[test]
fn test_double_ended_iterator_unix_byte_lines() {
let input = "1\n2\n3\n4\n";
let bytes = unix_byte_lines(input.as_bytes());

assert_equal(bytes, [b"1", b"2", b"3", b"4"])
}

#[test]
fn test_double_ended_iterator_unix_byte_lines_rev() {
let input = "1\n2\n3\n4\n";
let bytes = unix_byte_lines(input.as_bytes());

assert_equal(bytes.rev(), [b"4", b"3", b"2", b"1"])
}
}
3 changes: 2 additions & 1 deletion crates/atuin-client/src/import/nu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ impl Importer for Nu {
let now = OffsetDateTime::now_utc();

let mut counter = 0;
for b in unix_byte_lines(&self.bytes) {
// Reverse order so that recency is preserved
for b in unix_byte_lines(&self.bytes).rev() {
let s = match std::str::from_utf8(b) {
Ok(s) => s,
Err(_) => continue, // we can skip past things like invalid utf8
Expand Down
72 changes: 47 additions & 25 deletions crates/atuin-client/src/import/zsh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,44 +57,54 @@ impl Importer for Zsh {
}

async fn load(self, h: &mut impl Loader) -> Result<()> {
let now = OffsetDateTime::now_utc();
let mut line = String::new();

let mut counter = 0;
for b in unix_byte_lines(&self.bytes) {
// Reverse order so that recency of history is preserved
for b in unix_byte_lines(&self.bytes).rev() {
let s = match unmetafy(b) {
Some(s) => s,
_ => continue, // we can skip past things like invalid utf8
};

if let Some(s) = s.strip_suffix('\\') {
line.push_str(s);
line.push_str("\\\n");
// Prepend command from previous line in the history
line.insert_str(0, "\\\n");
line.insert_str(0, s);
} else {
line.push_str(&s);
let command = std::mem::take(&mut line);

if let Some(command) = command.strip_prefix(": ") {
counter += 1;
h.push(parse_extended(command, counter)).await?;
} else {
let offset = time::Duration::seconds(counter);
counter += 1;

let imported = History::import()
// preserve ordering
.timestamp(now - offset)
.command(command.trim_end().to_string());

h.push(imported.build().into()).await?;
if !line.is_empty() {
add_command(&mut line, &mut counter, h).await?;
}
line.push_str(&s);
}
}

add_command(&mut line, &mut counter, h).await?;
Ok(())
}
}

async fn add_command(line: &mut String, counter: &mut i64, h: &mut impl Loader) -> Result<()> {
if line.is_empty() {
return Ok(());
}
let now = OffsetDateTime::now_utc();
let command = std::mem::take(line);
if let Some(command) = command.strip_prefix(": ") {
*counter += 1;
h.push(parse_extended(command, *counter)).await?;
} else {
let offset = time::Duration::seconds(*counter);
*counter += 1;

let imported = History::import()
// preserve ordering
.timestamp(now - offset)
.command(command.trim_end().to_string());

h.push(imported.build().into()).await?;
}
Ok(())
}

fn parse_extended(line: &str, counter: i64) -> History {
let (time, duration) = line.split_once(':').unwrap();
let (duration, command) = duration.split_once(';').unwrap();
Expand Down Expand Up @@ -203,13 +213,25 @@ cargo update
assert_equal(
loader.buf.iter().map(|h| h.command.as_str()),
[
"cargo install atuin",
"cargo install atuin; \\\ncargo update",
"cargo :b̷i̶t̴r̵o̴t̴ ̵i̷s̴ ̷r̶e̵a̸l̷",
"cargo install atuin; \\\ncargo update",
"cargo install atuin",
],
);
}

#[tokio::test]
async fn test_parse_empty_file() {
let bytes = vec![];

let mut zsh = Zsh { bytes };
assert_eq!(zsh.entries().await.unwrap(), 0);

let mut loader = TestLoader::default();
zsh.load(&mut loader).await.unwrap();
assert!(loader.buf.is_empty());
}

#[tokio::test]
async fn test_parse_metafied() {
let bytes =
Expand All @@ -223,7 +245,7 @@ cargo update

assert_equal(
loader.buf.iter().map(|h| h.command.as_str()),
["echo 你好", "ls ~/音乐"],
["ls ~/音乐", "echo 你好"],
);
}
}

0 comments on commit 3cca45b

Please sign in to comment.