summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorEllie Huxtable <e@elm.sh>2021-04-21 18:13:51 +0100
committerEllie Huxtable <e@elm.sh>2021-04-21 21:26:44 +0100
commit4a50ce366639ca9dac7324d6a47d6a0e6c7fccdf (patch)
tree7ffd8848f675e1377f750cc0757768d074a5ac05 /src
parenta9b117aad7e6bd09c7ea188258924dc02855db05 (diff)
Bugfixes, show time ago, perf improvements
Also allow unique listing and more ergonomic cwd usage
Diffstat (limited to 'src')
-rw-r--r--src/command/history.rs38
-rw-r--r--src/command/login.rs26
-rw-r--r--src/command/mod.rs20
-rw-r--r--src/command/register.rs31
-rw-r--r--src/command/search.rs91
-rw-r--r--src/command/stats.rs2
-rw-r--r--src/main.rs19
-rw-r--r--src/shell/atuin.zsh6
8 files changed, 135 insertions, 98 deletions
diff --git a/src/command/history.rs b/src/command/history.rs
index 2b691bac..a88aeae2 100644
--- a/src/command/history.rs
+++ b/src/command/history.rs
@@ -1,7 +1,6 @@
use std::env;
use eyre::Result;
-use fork::{fork, Fork};
use structopt::StructOpt;
use atuin_client::database::Database;
@@ -44,6 +43,12 @@ pub enum Cmd {
aliases=&["se", "sea", "sear", "searc"],
)]
Search { query: Vec<String> },
+
+ #[structopt(
+ about="get the last command ran",
+ aliases=&["la", "las"],
+ )]
+ Last {},
}
fn print_list(h: &[History]) {
@@ -74,22 +79,24 @@ impl Cmd {
}
let mut h = db.load(id)?;
+
+ if h.duration > 0 {
+ debug!("cannot end history - already has duration");
+
+ // returning OK as this can occur if someone Ctrl-c a prompt
+ return Ok(());
+ }
+
h.exit = *exit;
h.duration = chrono::Utc::now().timestamp_nanos() - h.timestamp.timestamp_nanos();
db.update(&h)?;
if settings.should_sync()? {
- match fork() {
- Ok(Fork::Parent(child)) => {
- debug!("launched sync background process with PID {}", child);
- }
- Ok(Fork::Child) => {
- debug!("running periodic background sync");
- sync::sync(settings, false, db).await?;
- }
- Err(_) => println!("Fork failed"),
- }
+ debug!("running periodic background sync");
+ sync::sync(settings, false, db).await?;
+ } else {
+ debug!("sync disabled! not syncing");
}
Ok(())
@@ -107,7 +114,7 @@ impl Cmd {
let session = env::var("ATUIN_SESSION")?;
let history = match params {
- (false, false) => db.list()?,
+ (false, false) => db.list(None, false)?,
(true, false) => db.query(QUERY_SESSION, &[session.as_str()])?,
(false, true) => db.query(QUERY_DIR, &[cwd.as_str()])?,
(true, true) => {
@@ -126,6 +133,13 @@ impl Cmd {
Ok(())
}
+
+ Self::Last {} => {
+ let last = db.last()?;
+ print_list(&[last]);
+
+ Ok(())
+ }
}
}
}
diff --git a/src/command/login.rs b/src/command/login.rs
index eaeb297f..eacb2105 100644
--- a/src/command/login.rs
+++ b/src/command/login.rs
@@ -1,10 +1,10 @@
-use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
-use eyre::{eyre, Result};
+use eyre::Result;
use structopt::StructOpt;
+use atuin_client::api_client;
use atuin_client::settings::Settings;
#[derive(StructOpt)]
@@ -22,25 +22,15 @@ pub struct Cmd {
impl Cmd {
pub fn run(&self, settings: &Settings) -> Result<()> {
- let mut map = HashMap::new();
- map.insert("username", self.username.clone());
- map.insert("password", self.password.clone());
-
- let url = format!("{}/login", settings.sync_address);
- let client = reqwest::blocking::Client::new();
-
- let resp = client.post(url).json(&map).send()?;
-
- if resp.status() != reqwest::StatusCode::OK {
- return Err(eyre!("invalid login details"));
- }
-
- let session = resp.json::<HashMap<String, String>>()?;
- let session = session["session"].clone();
+ let session = api_client::login(
+ settings.sync_address.as_str(),
+ self.username.as_str(),
+ self.password.as_str(),
+ )?;
let session_path = settings.session_path.as_str();
let mut file = File::create(session_path)?;
- file.write_all(session.as_bytes())?;
+ file.write_all(session.session.as_bytes())?;
let key_path = settings.key_path.as_str();
let mut file = File::create(key_path)?;
diff --git a/src/command/mod.rs b/src/command/mod.rs
index 6fd52613..805ad9f0 100644
--- a/src/command/mod.rs
+++ b/src/command/mod.rs
@@ -43,7 +43,18 @@ pub enum AtuinCmd {
Uuid,
#[structopt(about = "interactive history search")]
- Search { query: Vec<String> },
+ Search {
+ #[structopt(long, short, about = "filter search result by directory")]
+ cwd: Option<String>,
+
+ #[structopt(long, short, about = "filter search result by exit code")]
+ exit: Option<i64>,
+
+ #[structopt(long, short, about = "open interactive search UI")]
+ interactive: bool,
+
+ query: Vec<String>,
+ },
#[structopt(about = "sync with the configured server")]
Sync {
@@ -76,7 +87,12 @@ impl AtuinCmd {
Self::Server(server) => server.run(&server_settings).await,
Self::Stats(stats) => stats.run(&mut db, &client_settings),
Self::Init => init::init(),
- Self::Search { query } => search::run(&query, &mut db),
+ Self::Search {
+ cwd,
+ exit,
+ interactive,
+ query,
+ } => search::run(cwd, exit, interactive, &query, &mut db),
Self::Sync { force } => sync::run(&client_settings, force, &mut db).await,
Self::Login(l) => l.run(&client_settings),
diff --git a/src/command/register.rs b/src/command/register.rs
index 1126645a..acf9b1a3 100644
--- a/src/command/register.rs
+++ b/src/command/register.rs
@@ -1,10 +1,10 @@
-use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
-use eyre::{eyre, Result};
+use eyre::Result;
use structopt::StructOpt;
+use atuin_client::api_client;
use atuin_client::settings::Settings;
#[derive(StructOpt)]
@@ -21,34 +21,11 @@ pub struct Cmd {
}
pub fn run(settings: &Settings, username: &str, email: &str, password: &str) -> Result<()> {
- let mut map = HashMap::new();
- map.insert("username", username);
- map.insert("email", email);
- map.insert("password", password);
-
- let url = format!("{}/user/{}", settings.sync_address, username);
- let resp = reqwest::blocking::get(url)?;
-
- if resp.status().is_success() {
- println!("Username is already in use! Please try another.");
- return Ok(());
- }
-
- let url = format!("{}/register", settings.sync_address);
- let client = reqwest::blocking::Client::new();
- let resp = client.post(url).json(&map).send()?;
-
- if !resp.status().is_success() {
- println!("Failed to register user - please check your details and try again");
- return Err(eyre!("failed to register user"));
- }
-
- let session = resp.json::<HashMap<String, String>>()?;
- let session = session["session"].clone();
+ let session = api_client::register(settings.sync_address.as_str(), username, email, password)?;
let path = settings.session_path.as_str();
let mut file = File::create(path)?;
- file.write_all(session.as_bytes())?;
+ file.write_all(session.session.as_bytes())?;
Ok(())
}
diff --git a/src/command/search.rs b/src/command/search.rs
index 773c112f..b074371e 100644
--- a/src/command/search.rs
+++ b/src/command/search.rs
@@ -1,7 +1,6 @@
use eyre::Result;
-use itertools::Itertools;
-use std::io::stdout;
use std::time::Duration;
+use std::{io::stdout, ops::Sub};
use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::AlternateScreen};
use tui::{
@@ -31,7 +30,7 @@ struct State {
#[allow(clippy::clippy::cast_sign_loss)]
impl State {
- fn durations(&self) -> Vec<String> {
+ fn durations(&self) -> Vec<(String, String)> {
self.results
.iter()
.map(|h| {
@@ -40,7 +39,33 @@ impl State {
let duration = humantime::format_duration(duration).to_string();
let duration: Vec<&str> = duration.split(' ').collect();
- duration[0].to_string()
+ let ago = chrono::Utc::now().sub(h.timestamp);
+ let ago = humantime::format_duration(ago.to_std().unwrap()).to_string();
+ let ago: Vec<&str> = ago.split(' ').collect();
+
+ (
+ duration[0]
+ .to_string()
+ .replace("days", "d")
+ .replace("day", "d")
+ .replace("weeks", "w")
+ .replace("week", "w")
+ .replace("months", "mo")
+ .replace("month", "mo")
+ .replace("years", "y")
+ .replace("year", "y"),
+ ago[0]
+ .to_string()
+ .replace("days", "d")
+ .replace("day", "d")
+ .replace("weeks", "w")
+ .replace("week", "w")
+ .replace("months", "mo")
+ .replace("month", "mo")
+ .replace("years", "y")
+ .replace("year", "y")
+ + " ago",
+ )
})
.collect()
}
@@ -51,9 +76,9 @@ impl State {
r: tui::layout::Rect,
) {
let durations = self.durations();
- let max_length = durations
- .iter()
- .fold(0, |largest, i| std::cmp::max(largest, i.len()));
+ let max_length = durations.iter().fold(0, |largest, i| {
+ std::cmp::max(largest, i.0.len() + i.1.len())
+ });
let results: Vec<ListItem> = self
.results
@@ -64,10 +89,10 @@ impl State {
let mut command = Span::raw(command);
- let mut duration = durations[i].clone();
+ let (duration, mut ago) = durations[i].clone();
- while duration.len() < max_length {
- duration.push(' ');
+ while (duration.len() + ago.len()) < max_length {
+ ago = " ".to_owned() + ago.as_str();
}
let duration = Span::styled(
@@ -79,6 +104,8 @@ impl State {
}),
);
+ let ago = Span::styled(ago, Style::default().fg(Color::Blue));
+
if let Some(selected) = self.results_state.selected() {
if selected == i {
command.style =
@@ -86,7 +113,8 @@ impl State {
}
}
- let spans = Spans::from(vec![duration, Span::raw(" "), command]);
+ let spans =
+ Spans::from(vec![duration, Span::raw(" "), ago, Span::raw(" "), command]);
ListItem::new(spans)
})
@@ -103,12 +131,12 @@ impl State {
fn query_results(app: &mut State, db: &mut impl Database) {
let results = match app.input.as_str() {
- "" => db.list(),
+ "" => db.list(Some(200), true),
i => db.prefix_search(i),
};
if let Ok(results) = results {
- app.results = results.into_iter().rev().unique().collect();
+ app.results = results;
}
if app.results.is_empty() {
@@ -120,7 +148,8 @@ fn query_results(app: &mut State, db: &mut impl Database) {
fn key_handler(input: Key, db: &mut impl Database, app: &mut State) -> Option<String> {
match input {
- Key::Esc | Key::Char('\n') => {
+ Key::Esc => return Some(String::from("")),
+ Key::Char('\n') => {
let i = app.results_state.selected().unwrap_or(0);
return Some(
@@ -268,9 +297,37 @@ fn select_history(query: &[String], db: &mut impl Database) -> Result<String> {
}
}
-pub fn run(query: &[String], db: &mut impl Database) -> Result<()> {
- let item = select_history(query, db)?;
- eprintln!("{}", item);
+pub fn run(
+ cwd: Option<String>,
+ exit: Option<i64>,
+ interactive: bool,
+ query: &[String],
+ db: &mut impl Database,
+) -> Result<()> {
+ let dir = if let Some(cwd) = cwd {
+ if cwd == "." {
+ let current = std::env::current_dir()?;
+ let current = current.as_os_str();
+ let current = current.to_str().unwrap();
+
+ Some(current.to_owned())
+ } else {
+ Some(cwd)
+ }
+ } else {
+ None
+ };
+
+ if interactive {
+ let item = select_history(query, db)?;
+ eprintln!("{}", item);
+ } else {
+ let results = db.search(dir, exit, query.join(" ").as_str())?;
+
+ for i in &results {
+ println!("{}", i.command);
+ }
+ }
Ok(())
}
diff --git a/src/command/stats.rs b/src/command/stats.rs
index 0da303d7..5c9a9dbb 100644
--- a/src/command/stats.rs
+++ b/src/command/stats.rs
@@ -94,7 +94,7 @@ impl Cmd {
}
Self::All => {
- let history = db.list()?;
+ let history = db.list(None, false)?;
compute_stats(&history)?;
diff --git a/src/main.rs b/src/main.rs
index c116d1f3..184c0323 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,7 +2,6 @@
#![allow(clippy::use_self)] // not 100% reliable
use eyre::Result;
-use fern::colors::{Color, ColoredLevelConfig};
use structopt::{clap::AppSettings, StructOpt};
#[macro_use]
@@ -32,23 +31,7 @@ impl Atuin {
#[tokio::main]
async fn main() -> Result<()> {
- let colors = ColoredLevelConfig::new()
- .warn(Color::Yellow)
- .error(Color::Red);
-
- fern::Dispatch::new()
- .format(move |out, message, record| {
- out.finish(format_args!(
- "{} [{}] {}",
- chrono::Local::now().to_rfc3339(),
- colors.color(record.level()),
- message
- ))
- })
- .level(log::LevelFilter::Info)
- .level_for("sqlx", log::LevelFilter::Warn)
- .chain(std::io::stdout())
- .apply()?;
+ pretty_env_logger::init();
Atuin::from_args().run().await
}
diff --git a/src/shell/atuin.zsh b/src/shell/atuin.zsh
index d6d58f53..cdef5e54 100644
--- a/src/shell/atuin.zsh
+++ b/src/shell/atuin.zsh
@@ -15,8 +15,8 @@ _atuin_precmd(){
[[ -z "${ATUIN_HISTORY_ID}" ]] && return
- atuin history end $ATUIN_HISTORY_ID --exit $EXIT
- export ATUIN_HISTORY_ID=""
+
+ (RUST_LOG=error atuin history end $ATUIN_HISTORY_ID --exit $EXIT &) > /dev/null 2>&1
}
_atuin_search(){
@@ -27,7 +27,7 @@ _atuin_search(){
echoti rmkx
# swap stderr and stdout, so that the tui stuff works
# TODO: not this
- output=$(atuin search $BUFFER 3>&1 1>&2 2>&3)
+ output=$(RUST_LOG=error atuin search -i $BUFFER 3>&1 1>&2 2>&3)
echoti smkx
if [[ -n $output ]] ; then