@@ -33,6 +33,7 @@ use serde_json::Value;
3333
3434use crate :: {
3535 fetch,
36+ history,
3637 lock,
3738 pins,
3839 pins:: {
@@ -50,7 +51,7 @@ const STARTER_TOML: &str = include_str!("../assets/pins.toml");
5051const RESOLVER_NIX : & str = include_str ! ( "../.tack/default.nix" ) ;
5152const 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+
11561203pub 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
11711220pin types: flake (default), fetch (source tree only), fixed (FOD)
11721221
0 commit comments