a tiny, local-first shell history manager with fuzzy search.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118use std::path::{Path, PathBuf};
use rusqlite::{Connection, Result};
/// Newest rows pulled into the search UI.const SEARCH_LIMIT: usize = 10_000;/// Rows shown by `diethist list`.const LIST_LIMIT: usize = 50;
pub fn db_dir() -> PathBuf { let dir = match std::env::var_os("XDG_DATA_HOME") { Some(d) if !d.is_empty() => PathBuf::from(d), _ => PathBuf::from(std::env::var_os("HOME").unwrap_or_default()).join(".local/share"), };
dir.join("diethist")}
pub fn db_path() -> PathBuf { db_dir().join("history.db")}
pub fn open(path: &Path) -> Result<Connection> { if let Some(parent) = path.parent() { std::fs::create_dir_all(parent).map_err(|e| { rusqlite::Error::InvalidPath(PathBuf::from(format!("{}: {e}", parent.display()))) })?;
#[cfg(unix)] { use std::os::unix::fs::PermissionsExt; let _ = std::fs::set_permissions(parent, std::fs::Permissions::from_mode(0o700)); } }
let db = Connection::open(path)?; db.busy_timeout(std::time::Duration::from_millis(5000))?; db.pragma_update(None, "journal_mode", "WAL")?;
db.execute_batch( r" create table if not exists history ( id integer primary key autoincrement, cmd text not null unique, cwd text, exit integer, ts integer, session text );
-- newest-first, so queries can stop at the limit. create index if not exists history_ts_cmd on history(ts desc, cmd);", )?;
Ok(db)}
pub struct Candidate { pub cmd: String, /// Unix seconds. pub ts: i64,}
pub fn load_candidates(db: &Connection) -> Result<Vec<Candidate>> { let mut stmt = db.prepare(&format!( "select cmd, ts from history order by ts desc limit {SEARCH_LIMIT}" ))?;
let rows = stmt.query_map([], |r| { Ok(Candidate { cmd: r.get(0)?, ts: r.get(1).unwrap_or(0), }) })?;
Ok(rows.flatten().collect())}
/// Records a command, bumping the timestamp when it has been run before.pub fn add(db: &Connection, cmd: &str, exit: i64, ts: i64) -> Result<()> { let cwd = std::env::current_dir().unwrap_or_default(); let session = std::env::var("diethist_SESSION").unwrap_or_default();
db.execute( "insert into history(cmd, cwd, exit, ts, session) values(?1, ?2, ?3, ?4, ?5) on conflict(cmd) do update set cwd = excluded.cwd, exit = excluded.exit, ts = excluded.ts, session = excluded.session", rusqlite::params![cmd, cwd.to_string_lossy(), exit, ts, session], )?;
Ok(())}
pub struct Entry { pub ts: i64, pub exit: i64, pub cmd: String,}
pub fn recent(db: &Connection) -> Result<Vec<Entry>> { let mut stmt = db.prepare(&format!( "select ts, exit, cmd from history order by id desc limit {LIST_LIMIT}" ))?;
let rows = stmt.query_map([], |r| { Ok(Entry { ts: r.get(0).unwrap_or(0), exit: r.get(1).unwrap_or(0), cmd: r.get(2)?, }) })?;
Ok(rows.flatten().collect())}