Skip to content

Commit 636b8a4

Browse files
committed
feat: add history + undo/redo
1 parent 8e83b2a commit 636b8a4

4 files changed

Lines changed: 447 additions & 20 deletions

File tree

src/cli.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ pub enum Command {
4242
rm: bool,
4343
},
4444
Dedup,
45+
Undo {
46+
list: bool,
47+
},
48+
Redo,
4549
Help,
4650
}
4751

@@ -204,6 +208,22 @@ fn parse_parser(mut parser: lexopt::Parser) -> Result<Command> {
204208
}
205209
Ok(Command::Dedup)
206210
},
211+
"undo" => {
212+
let mut list = false;
213+
while let Some(arg) = parser.next()? {
214+
match arg {
215+
Long("list") => list = true,
216+
Short(_) | Long(_) | Value(_) => return Err(arg.unexpected().into()),
217+
}
218+
}
219+
Ok(Command::Undo { list })
220+
},
221+
"redo" => {
222+
if let Some(arg) = parser.next()? {
223+
return Err(arg.unexpected().into());
224+
}
225+
Ok(Command::Redo)
226+
},
207227
_ => Ok(Command::Help),
208228
}
209229
}

src/commands.rs

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ use serde_json::Value;
3333

3434
use crate::{
3535
fetch,
36+
history,
3637
lock,
3738
pins,
3839
pins::{
@@ -50,7 +51,7 @@ const STARTER_TOML: &str = include_str!("../assets/pins.toml");
5051
const RESOLVER_NIX: &str = include_str!("../.tack/default.nix");
5152
const MARKER: &str = "# tack-managed resolver.";
5253

53-
fn dir() -> PathBuf {
54+
pub fn dir() -> PathBuf {
5455
if let Some(dir) = env::var_os("TACK_DIR") {
5556
return PathBuf::from(dir);
5657
}
@@ -61,10 +62,10 @@ fn dir() -> PathBuf {
6162
cwd.join(".tack")
6263
}
6364

64-
fn pins_path(dir: &Path) -> PathBuf {
65+
pub fn pins_path(dir: &Path) -> PathBuf {
6566
dir.join("pins.toml")
6667
}
67-
fn lock_path(dir: &Path) -> PathBuf {
68+
pub fn lock_path(dir: &Path) -> PathBuf {
6869
dir.join("pins.lock.json")
6970
}
7071

@@ -119,7 +120,7 @@ fn short(rev: &str) -> String {
119120
rev.chars().take(7).collect()
120121
}
121122

122-
fn write_atomic(path: &Path, contents: &str) -> Result<()> {
123+
pub fn write_atomic(path: &Path, contents: &str) -> Result<()> {
123124
let mut tmp_str = path.as_os_str().to_owned();
124125
tmp_str.push(".tmp");
125126
let tmp = PathBuf::from(tmp_str);
@@ -1153,6 +1154,52 @@ fn pick_name(id: &str, aliases: &BTreeSet<String>) -> String {
11531154
.unwrap_or_default()
11541155
}
11551156

1157+
pub fn undo(list: bool) -> Result<()> {
1158+
let dir = dir();
1159+
if list {
1160+
match history::list(&dir) {
1161+
Some(view) => render(&view, 0, view.rows.len().saturating_sub(1)),
1162+
None => println!("no history"),
1163+
}
1164+
return Ok(());
1165+
}
1166+
match history::undo(&dir)? {
1167+
Some(view) => render_window(&view),
1168+
None => println!("nothing to undo"),
1169+
}
1170+
Ok(())
1171+
}
1172+
1173+
pub fn redo() -> Result<()> {
1174+
match history::redo(&dir())? {
1175+
Some(view) => render_window(&view),
1176+
None => println!("nothing to redo"),
1177+
}
1178+
Ok(())
1179+
}
1180+
1181+
/// a radius-1 window around the new cursor: the redo target, the live state,
1182+
/// the undo target. fewer lines at the ends.
1183+
fn render_window(view: &history::View) {
1184+
let lo = view.cursor.saturating_sub(1);
1185+
let hi = (view.cursor + 1).min(view.rows.len().saturating_sub(1));
1186+
render(view, lo, hi);
1187+
}
1188+
1189+
/// rows `lo..=hi` newest-first, relative times aligned, `>` marking the cursor
1190+
fn render(view: &history::View, lo: usize, hi: usize) {
1191+
let now = history::now();
1192+
let times = (lo..=hi)
1193+
.map(|idx| history::rel_time(now, view.rows[idx].ts))
1194+
.collect::<Vec<String>>();
1195+
let width = times.iter().map(String::len).max().unwrap_or(0);
1196+
for idx in (lo..=hi).rev() {
1197+
let marker = if idx == view.cursor { '>' } else { ' ' };
1198+
let when = &times[idx - lo];
1199+
println!("{marker} {when:width$} {}", view.rows[idx].label);
1200+
}
1201+
}
1202+
11561203
pub fn help() {
11571204
println!(
11581205
"tack: flake-like toml nix pins, lazily fetched and transformed
@@ -1167,6 +1214,8 @@ usage:
11671214
tack rm <name>
11681215
tack alias <name> <template> | tack alias --rm <name>
11691216
tack dedup
1217+
tack undo [--list]
1218+
tack redo
11701219
11711220
pin types: flake (default), fetch (source tree only), fixed (FOD)
11721221

0 commit comments

Comments
 (0)