summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorConrad Ludgate <conradludgate@gmail.com>2022-09-12 20:19:22 +0100
committerGitHub <noreply@github.com>2022-09-12 20:19:22 +0100
commite8c841527880ac6ca47fd514ac70aad81764dae8 (patch)
tree577d51ad1e6e21b3a19359578f0de27f0522eebd
parent702a644f68c687142c9a03b48cf451665ed41b62 (diff)
update default layout (#523)
* update layouts * add other duration changes * fmt :(
-rw-r--r--Cargo.lock9
-rw-r--r--Cargo.toml3
-rw-r--r--src/command/client/history.rs14
-rw-r--r--src/command/client/search.rs2
-rw-r--r--src/command/client/search/duration.rs50
-rw-r--r--src/command/client/search/interactive.rs224
6 files changed, 144 insertions, 158 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 3747553c..6a25cb6d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -86,7 +86,6 @@ dependencies = [
"directories",
"eyre",
"fs-err",
- "humantime 2.1.0",
"indicatif",
"itertools",
"log",
@@ -568,7 +567,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
dependencies = [
"atty",
- "humantime 1.3.0",
+ "humantime",
"log",
"regex",
"termcolor",
@@ -838,12 +837,6 @@ dependencies = [
]
[[package]]
-name = "humantime"
-version = "2.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
-
-[[package]]
name = "hyper"
version = "0.14.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 05c7964a..d925c363 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,7 +2,7 @@
name = "atuin"
version = "0.10.0"
authors = ["Ellie Huxtable <ellie@elliehuxtable.com>"]
-edition = "2018"
+edition = "2021"
rust-version = "1.59"
license = "MIT"
description = "atuin - magical shell history"
@@ -65,7 +65,6 @@ async-trait = "0.1.49"
chrono-english = "0.1.4"
cli-table = { version = "0.4", default-features = false }
base64 = "0.13.0"
-humantime = "2.1.0"
crossbeam-channel = "0.5.1"
clap = { version = "3.1.18", features = ["derive"] }
clap_complete = "3.1.4"
diff --git a/src/command/client/history.rs b/src/command/client/history.rs
index 805fe4ca..fe5bfcbe 100644
--- a/src/command/client/history.rs
+++ b/src/command/client/history.rs
@@ -16,6 +16,8 @@ use atuin_client::{
#[cfg(feature = "sync")]
use atuin_client::sync;
+use super::search::format_duration;
+
#[derive(Subcommand)]
#[clap(infer_subcommands = true)]
pub enum Cmd {
@@ -92,11 +94,7 @@ pub fn print_list(h: &[History], list_mode: ListMode) {
#[allow(clippy::cast_sign_loss)]
pub fn print_human_list(w: &mut StdoutLock, h: &[History]) {
for h in h.iter().rev() {
- let duration =
- humantime::format_duration(Duration::from_nanos(std::cmp::max(h.duration, 0) as u64))
- .to_string();
- let duration: Vec<&str> = duration.split(' ').collect();
- let duration = duration[0];
+ let duration = format_duration(Duration::from_nanos(std::cmp::max(h.duration, 0) as u64));
let time = h.timestamp.format("%Y-%m-%d %H:%M:%S");
let cmd = h.command.trim();
@@ -108,11 +106,7 @@ pub fn print_human_list(w: &mut StdoutLock, h: &[History]) {
#[allow(clippy::cast_sign_loss)]
pub fn print_regular(w: &mut StdoutLock, h: &[History]) {
for h in h.iter().rev() {
- let duration =
- humantime::format_duration(Duration::from_nanos(std::cmp::max(h.duration, 0) as u64))
- .to_string();
- let duration: Vec<&str> = duration.split(' ').collect();
- let duration = duration[0];
+ let duration = format_duration(Duration::from_nanos(std::cmp::max(h.duration, 0) as u64));
let time = h.timestamp.format("%Y-%m-%d %H:%M:%S");
let cmd = h.command.trim();
diff --git a/src/command/client/search.rs b/src/command/client/search.rs
index 7b84d410..915589a5 100644
--- a/src/command/client/search.rs
+++ b/src/command/client/search.rs
@@ -9,8 +9,10 @@ use atuin_client::{
use super::history::ListMode;
mod cursor;
+mod duration;
mod event;
mod interactive;
+pub use duration::format_duration;
#[derive(Parser)]
pub struct Cmd {
diff --git a/src/command/client/search/duration.rs b/src/command/client/search/duration.rs
new file mode 100644
index 00000000..3cdd4e83
--- /dev/null
+++ b/src/command/client/search/duration.rs
@@ -0,0 +1,50 @@
+use std::{ops::ControlFlow, time::Duration};
+
+#[allow(clippy::module_name_repetitions)]
+pub fn format_duration(f: Duration) -> String {
+ fn item(name: &str, value: u64) -> ControlFlow<String> {
+ if value > 0 {
+ ControlFlow::Break(format!("{}{}", value, name))
+ } else {
+ ControlFlow::Continue(())
+ }
+ }
+
+ // impl taken and modified from
+ // https://github.com/tailhook/humantime/blob/master/src/duration.rs#L295-L331
+ // Copyright (c) 2016 The humantime Developers
+ fn fmt(f: Duration) -> ControlFlow<String, ()> {
+ let secs = f.as_secs();
+ let nanos = f.subsec_nanos();
+
+ let years = secs / 31_557_600; // 365.25d
+ let year_days = secs % 31_557_600;
+ let months = year_days / 2_630_016; // 30.44d
+ let month_days = year_days % 2_630_016;
+ let days = month_days / 86400;
+ let day_secs = month_days % 86400;
+ let hours = day_secs / 3600;
+ let minutes = day_secs % 3600 / 60;
+ let seconds = day_secs % 60;
+
+ let millis = nanos / 1_000_000;
+
+ // a difference from our impl than the original is that
+ // we only care about the most-significant segment of the duration.
+ // If the item call returns `Break`, then the `?` will early-return.
+ // This allows for a very consise impl
+ item("y", years)?;
+ item("mo", months)?;
+ item("d", days)?;
+ item("h", hours)?;
+ item("m", minutes)?;
+ item("s", seconds)?;
+ item("ms", u64::from(millis))?;
+ ControlFlow::Continue(())
+ }
+
+ match fmt(f) {
+ ControlFlow::Break(b) => b,
+ ControlFlow::Continue(()) => String::from("0s"),
+ }
+}
diff --git a/src/command/client/search/interactive.rs b/src/command/client/search/interactive.rs
index e355b3f2..10a440a3 100644
--- a/src/command/client/search/interactive.rs
+++ b/src/command/client/search/interactive.rs
@@ -10,7 +10,7 @@ use tui::{
layout::{Alignment, Constraint, Corner, Direction, Layout},
style::{Color, Modifier, Style},
text::{Span, Spans, Text},
- widgets::{Block, Borders, List, ListItem, ListState, Paragraph},
+ widgets::{Block, BorderType, Borders, List, ListItem, ListState, Paragraph},
Frame, Terminal,
};
use unicode_width::UnicodeWidthStr;
@@ -26,6 +26,7 @@ use atuin_client::{
use super::{
cursor::Cursor,
event::{Event, Events},
+ format_duration,
};
use crate::VERSION;
@@ -41,120 +42,62 @@ struct State {
context: Context,
}
-impl State {
- #[allow(clippy::cast_sign_loss)]
- fn durations(&self) -> Vec<(String, String)> {
- self.results
- .iter()
- .map(|h| {
- let duration =
- Duration::from_millis(std::cmp::max(h.duration, 0) as u64 / 1_000_000);
- let duration = humantime::format_duration(duration).to_string();
- let duration: Vec<&str> = duration.split(' ').collect();
-
- let ago = chrono::Utc::now().sub(h.timestamp);
-
- // Account for the chance that h.timestamp is "in the future"
- // This would mean that "ago" is negative, and the unwrap here
- // would fail.
- // If the timestamp would otherwise be in the future, display
- // the time ago as 0.
- let ago = humantime::format_duration(
- ago.to_std().unwrap_or_else(|_| Duration::new(0, 0)),
- )
- .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()
- }
+fn duration(h: &History) -> String {
+ let duration = Duration::from_nanos(u64::try_from(h.duration).unwrap_or(0));
+ format_duration(duration)
+}
+
+fn ago(h: &History) -> String {
+ let ago = chrono::Utc::now().sub(h.timestamp);
+ // Account for the chance that h.timestamp is "in the future"
+ // This would mean that "ago" is negative, and the unwrap here
+ // would fail.
+ // If the timestamp would otherwise be in the future, display
+ // the time ago as 0.
+ let ago = ago.to_std().unwrap_or_default();
+ format_duration(ago) + " ago"
+}
+
+impl State {
fn render_results<T: tui::backend::Backend>(
&mut self,
f: &mut tui::Frame<T>,
r: tui::layout::Rect,
b: tui::widgets::Block,
) {
- let durations = self.durations();
- let max_length = durations.iter().fold(0, |largest, i| {
- std::cmp::max(largest, i.0.len() + i.1.len())
- });
+ let max_length = 12; // '123ms' + '59s ago'
let results: Vec<ListItem> = self
.results
.iter()
.enumerate()
.map(|(i, m)| {
- let command = m.command.to_string().replace('\n', " ").replace('\t', " ");
-
- let mut command = Span::raw(command);
-
- let (duration, mut ago) = durations[i].clone();
-
- while (duration.len() + ago.len()) < max_length {
- ago = format!(" {}", ago);
- }
-
- let selected_index = match self.results_state.selected() {
- None => Span::raw(" "),
- Some(selected) => match i.checked_sub(selected) {
- None => Span::raw(" "),
- Some(diff) => {
- if 0 < diff && diff < 10 {
- Span::raw(format!(" {} ", diff))
- } else {
- Span::raw(" ")
- }
- }
- },
+ // these encode the slices of `" > "`, `" {n} "`, or `" "` in a compact form.
+ // Yes, this is a hack, but it makes me feel happy
+ let slices = " > 1 2 3 4 5 6 7 8 9 ";
+ let index = self.results_state.selected().and_then(|s| i.checked_sub(s));
+ let slice_index = index.unwrap_or(10).min(10) * 2;
+
+ let status_colour = if m.success() {
+ Color::Green
+ } else {
+ Color::Red
};
+ let ago = ago(m);
+ let duration = format!("{:width$}", duration(m), width = max_length - ago.len());
- let duration = Span::styled(
- duration,
- Style::default().fg(if m.success() {
- Color::Green
- } else {
- Color::Red
- }),
- );
-
- let ago = Span::styled(ago, Style::default().fg(Color::Blue));
-
- if let Some(selected) = self.results_state.selected() {
- if selected == i {
- command.style =
- Style::default().fg(Color::Red).add_modifier(Modifier::BOLD);
- }
+ let command = m.command.replace(['\n', '\t'], " ");
+ let mut command = Span::raw(command);
+ if slice_index == 0 {
+ command.style = Style::default().fg(Color::Red).add_modifier(Modifier::BOLD);
}
let spans = Spans::from(vec![
- selected_index,
- duration,
+ Span::raw(&slices[slice_index..slice_index + 3]),
+ Span::styled(duration, Style::default().fg(status_colour)),
Span::raw(" "),
- ago,
+ Span::styled(ago, Style::default().fg(Color::Blue)),
Span::raw(" "),
command,
]);
@@ -163,10 +106,7 @@ impl State {
})
.collect();
- let results = List::new(results)
- .block(b)
- .start_corner(Corner::BottomLeft)
- .highlight_symbol(">> ");
+ let results = List::new(results).block(b).start_corner(Corner::BottomLeft);
f.render_stateful_widget(results, r, &mut self.results_state);
}
@@ -280,73 +220,78 @@ impl State {
fn draw<T: Backend>(&mut self, f: &mut Frame<'_, T>, history_count: i64) {
let chunks = Layout::default()
.direction(Direction::Vertical)
- .margin(1)
- .constraints(
- [
- Constraint::Length(2),
- Constraint::Min(1),
- Constraint::Length(3),
- ]
- .as_ref(),
- )
+ .margin(0)
+ .constraints([
+ Constraint::Length(3),
+ Constraint::Min(1),
+ Constraint::Length(3),
+ ])
.split(f.size());
let top_chunks = Layout::default()
.direction(Direction::Horizontal)
- .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
+ .constraints([Constraint::Percentage(50); 2])
.split(chunks[0]);
let top_left_chunks = Layout::default()
.direction(Direction::Vertical)
- .constraints([Constraint::Length(1), Constraint::Length(1)].as_ref())
+ .constraints([Constraint::Length(1); 3])
.split(top_chunks[0]);
let top_right_chunks = Layout::default()
.direction(Direction::Vertical)
- .constraints([Constraint::Length(1), Constraint::Length(1)].as_ref())
+ .constraints([Constraint::Length(1); 3])
.split(top_chunks[1]);
let title = Paragraph::new(Text::from(Span::styled(
- format!("Atuin v{}", VERSION),
+ format!(" Atuin v{VERSION}"),
Style::default().add_modifier(Modifier::BOLD),
)));
let help = vec![
- Span::raw("Press "),
+ Span::raw(" Press "),
Span::styled("Esc", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to exit."),
];
- let help = Text::from(Spans::from(help));
- let help = Paragraph::new(help);
-
- let input = Paragraph::new(self.input.as_str().to_owned()).block(
- Block::default()
- .borders(Borders::ALL)
- .title(self.filter_mode.as_str()),
- );
-
+ let help = Paragraph::new(Text::from(Spans::from(help)));
let stats = Paragraph::new(Text::from(Span::raw(format!(
- "history count: {}",
- history_count,
- ))))
- .alignment(Alignment::Right);
+ "history count: {history_count} ",
+ ))));
- f.render_widget(title, top_left_chunks[0]);
- f.render_widget(help, top_left_chunks[1]);
- f.render_widget(stats, top_right_chunks[0]);
+ f.render_widget(title, top_left_chunks[1]);
+ f.render_widget(help, top_left_chunks[2]);
+ f.render_widget(stats.alignment(Alignment::Right), top_right_chunks[1]);
self.render_results(
f,
chunks[1],
- Block::default().borders(Borders::ALL).title("History"),
+ Block::default()
+ .borders(Borders::TOP | Borders::LEFT | Borders::RIGHT)
+ .border_type(BorderType::Rounded), // .title("History"),
+ );
+
+ let input = format!(
+ "[{:^14}] {}",
+ self.filter_mode.as_str(),
+ self.input.as_str(),
+ );
+ let input = Paragraph::new(input).block(
+ Block::default()
+ .borders(Borders::BOTTOM | Borders::LEFT | Borders::RIGHT)
+ .border_type(BorderType::Rounded)
+ .title(format!(
+ "{:─>width$}",
+ "",
+ width = chunks[2].width as usize - 2
+ )),
);
f.render_widget(input, chunks[2]);
let width = UnicodeWidthStr::width(self.input.substring());
f.set_cursor(
// Put cursor past the end of the input text
- chunks[2].x + width as u16 + 1,
+ chunks[2].x + width as u16 + 18,
// Move one line down, from the border to the input line
chunks[2].y + 1,
);
@@ -399,22 +344,25 @@ impl State {
.style(Style::default().fg(Color::DarkGray))
.alignment(Alignment::Right);
- let filter_mode = self.filter_mode.as_str();
- let input = Paragraph::new(format!("{}] {}", filter_mode, self.input.as_str()))
- .block(Block::default());
-
f.render_widget(title, header_chunks[0]);
f.render_widget(help, header_chunks[1]);
f.render_widget(stats, header_chunks[2]);
self.render_results(f, chunks[1], Block::default());
+
+ let input = format!(
+ "[{:^14}] {}",
+ self.filter_mode.as_str(),
+ self.input.as_str(),
+ );
+ let input = Paragraph::new(input).block(Block::default());
f.render_widget(input, chunks[2]);
- let extra_width = UnicodeWidthStr::width(self.input.substring()) + filter_mode.len();
+ let extra_width = UnicodeWidthStr::width(self.input.substring());
f.set_cursor(
// Put cursor past the end of the input text
- chunks[2].x + extra_width as u16 + 2,
+ chunks[2].x + extra_width as u16 + 17,
// Move one line down, from the border to the input line
chunks[2].y + 1,
);