mirror of
https://github.com/atuinsh/atuin.git
synced 2024-12-26 00:50:22 +01:00
parent
61607e023f
commit
716c7722cd
67
Cargo.lock
generated
67
Cargo.lock
generated
@ -106,7 +106,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "atuin"
|
||||
version = "0.3.3"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"chrono-english",
|
||||
@ -116,6 +116,7 @@ dependencies = [
|
||||
"eyre",
|
||||
"hostname",
|
||||
"indicatif",
|
||||
"itertools",
|
||||
"log 0.4.14",
|
||||
"pretty_env_logger",
|
||||
"rocket",
|
||||
@ -124,6 +125,9 @@ dependencies = [
|
||||
"serde_derive",
|
||||
"shellexpand",
|
||||
"structopt",
|
||||
"termion",
|
||||
"tui",
|
||||
"unicode-width",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
@ -214,6 +218,12 @@ version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b"
|
||||
|
||||
[[package]]
|
||||
name = "cassowary"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.66"
|
||||
@ -449,6 +459,12 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||
|
||||
[[package]]
|
||||
name = "encode_unicode"
|
||||
version = "0.3.6"
|
||||
@ -682,6 +698,15 @@ dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.7"
|
||||
@ -846,6 +871,12 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17b02fc0ff9a9e4b35b3342880f48e896ebf69f2967921fe8646bf5b7125956a"
|
||||
|
||||
[[package]]
|
||||
name = "numtoa"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.5.2"
|
||||
@ -1046,6 +1077,15 @@ dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_termios"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f"
|
||||
dependencies = [
|
||||
"redox_syscall 0.2.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.3.5"
|
||||
@ -1367,6 +1407,18 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termion"
|
||||
version = "1.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"numtoa",
|
||||
"redox_syscall 0.2.4",
|
||||
"redox_termios",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
@ -1434,6 +1486,19 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079"
|
||||
|
||||
[[package]]
|
||||
name = "tui"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ced152a8e9295a5b168adc254074525c17ac4a83c90b2716274cc38118bddc9"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cassowary",
|
||||
"termion",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typeable"
|
||||
version = "0.1.2"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "atuin"
|
||||
version = "0.3.3"
|
||||
version = "0.4.0"
|
||||
authors = ["Ellie Huxtable <e@elm.sh>"]
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
@ -23,6 +23,10 @@ cli-table = "0.4"
|
||||
config = "0.10"
|
||||
serde_derive = "1.0.124"
|
||||
serde = "1.0.124"
|
||||
tui = "0.14"
|
||||
termion = "1.5"
|
||||
unicode-width = "0.1"
|
||||
itertools = "0.10.0"
|
||||
|
||||
[dependencies.rusqlite]
|
||||
version = "0.24"
|
||||
|
32
README.md
32
README.md
@ -29,10 +29,6 @@ As well as the expected command, A'tuin stores
|
||||
|
||||
- zsh
|
||||
|
||||
## Requirements
|
||||
|
||||
- [fzf](https://github.com/junegunn/fzf)
|
||||
|
||||
## Install
|
||||
|
||||
### AUR
|
||||
@ -77,9 +73,9 @@ to your `.zshrc`/`.bashrc`/whatever your shell uses.
|
||||
|
||||
### History search
|
||||
|
||||
By default A'tuin will rebind ctrl-r to use fzf to fuzzy search your history.
|
||||
It will also rebind the up arrow to use fzf, just without sorting. You can
|
||||
prevent this by putting
|
||||
By default A'tuin will rebind ctrl-r and the up arrow to search your history.
|
||||
|
||||
You can prevent this by putting
|
||||
|
||||
```
|
||||
export ATUIN_BINDKEYS="false"
|
||||
@ -87,28 +83,6 @@ export ATUIN_BINDKEYS="false"
|
||||
|
||||
into your shell config.
|
||||
|
||||
You may also change the default history selection. The default behaviour will search your entire history, however
|
||||
|
||||
```
|
||||
export ATUIN_HISTORY="atuin history list --cwd"
|
||||
```
|
||||
|
||||
will switch to only searching history for the current directory.
|
||||
|
||||
Similarly,
|
||||
|
||||
```
|
||||
export ATUIN_HISTORY="atuin history list --session"
|
||||
```
|
||||
|
||||
will search for the current session only, and
|
||||
|
||||
```
|
||||
export ATUIN_HISTORY="atuin history list --session --cwd"
|
||||
```
|
||||
|
||||
will do both!
|
||||
|
||||
### Import history
|
||||
|
||||
```
|
||||
|
68
src/command/event.rs
Normal file
68
src/command/event.rs
Normal file
@ -0,0 +1,68 @@
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use termion::event::Key;
|
||||
use termion::input::TermRead;
|
||||
|
||||
pub enum Event<I> {
|
||||
Input(I),
|
||||
Tick,
|
||||
}
|
||||
|
||||
/// A small event handler that wrap termion input and tick events. Each event
|
||||
/// type is handled in its own thread and returned to a common `Receiver`
|
||||
pub struct Events {
|
||||
rx: mpsc::Receiver<Event<Key>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Config {
|
||||
pub exit_key: Key,
|
||||
pub tick_rate: Duration,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Config {
|
||||
Config {
|
||||
exit_key: Key::Char('q'),
|
||||
tick_rate: Duration::from_millis(250),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Events {
|
||||
pub fn new() -> Events {
|
||||
Events::with_config(Config::default())
|
||||
}
|
||||
|
||||
pub fn with_config(config: Config) -> Events {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
{
|
||||
let tx = tx.clone();
|
||||
thread::spawn(move || {
|
||||
let tty = termion::get_tty().expect("Could not find tty");
|
||||
for key in tty.keys().flatten() {
|
||||
if let Err(err) = tx.send(Event::Input(key)) {
|
||||
eprintln!("{}", err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
thread::spawn(move || loop {
|
||||
if tx.send(Event::Tick).is_err() {
|
||||
break;
|
||||
}
|
||||
thread::sleep(config.tick_rate);
|
||||
});
|
||||
|
||||
Events { rx }
|
||||
}
|
||||
|
||||
pub fn next(&self) -> Result<Event<Key>, mpsc::RecvError> {
|
||||
self.rx.recv()
|
||||
}
|
||||
}
|
@ -35,6 +35,12 @@ pub enum Cmd {
|
||||
#[structopt(long, short)]
|
||||
session: bool,
|
||||
},
|
||||
|
||||
#[structopt(
|
||||
about="search for a command",
|
||||
aliases=&["se", "sea", "sear", "searc"],
|
||||
)]
|
||||
Search { query: Vec<String> },
|
||||
}
|
||||
|
||||
fn print_list(h: &[History]) {
|
||||
@ -102,6 +108,13 @@ impl Cmd {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Self::Search { query } => {
|
||||
let history = db.prefix_search(&query.join(""))?;
|
||||
print_list(&history);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,9 +5,11 @@ use uuid::Uuid;
|
||||
use crate::local::database::Database;
|
||||
use crate::settings::Settings;
|
||||
|
||||
mod event;
|
||||
mod history;
|
||||
mod import;
|
||||
mod init;
|
||||
mod search;
|
||||
mod server;
|
||||
mod stats;
|
||||
|
||||
@ -33,6 +35,9 @@ pub enum AtuinCmd {
|
||||
|
||||
#[structopt(about = "generates a UUID")]
|
||||
Uuid,
|
||||
|
||||
#[structopt(about = "interactive history search")]
|
||||
Search { query: Vec<String> },
|
||||
}
|
||||
|
||||
pub fn uuid_v4() -> String {
|
||||
@ -47,6 +52,7 @@ impl AtuinCmd {
|
||||
Self::Server(server) => server.run(),
|
||||
Self::Stats(stats) => stats.run(db, settings),
|
||||
Self::Init => init::init(),
|
||||
Self::Search { query } => search::run(&query, db),
|
||||
|
||||
Self::Uuid => {
|
||||
println!("{}", uuid_v4());
|
||||
|
220
src/command/search.rs
Normal file
220
src/command/search.rs
Normal file
@ -0,0 +1,220 @@
|
||||
use eyre::Result;
|
||||
use itertools::Itertools;
|
||||
use std::io::stdout;
|
||||
use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::AlternateScreen};
|
||||
use tui::{
|
||||
backend::TermionBackend,
|
||||
layout::{Alignment, Constraint, Corner, Direction, Layout},
|
||||
style::{Color, Modifier, Style},
|
||||
text::{Span, Spans, Text},
|
||||
widgets::{Block, Borders, List, ListItem, ListState, Paragraph},
|
||||
Terminal,
|
||||
};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::command::event::{Event, Events};
|
||||
use crate::local::database::Database;
|
||||
use crate::local::history::History;
|
||||
|
||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
struct State {
|
||||
input: String,
|
||||
|
||||
results: Vec<History>,
|
||||
|
||||
results_state: ListState,
|
||||
}
|
||||
|
||||
fn query_results(app: &mut State, db: &mut impl Database) {
|
||||
let results = match app.input.as_str() {
|
||||
"" => db.list(),
|
||||
i => db.prefix_search(i),
|
||||
};
|
||||
|
||||
if let Ok(results) = results {
|
||||
app.results = results.into_iter().rev().unique().collect();
|
||||
}
|
||||
|
||||
if app.results.is_empty() {
|
||||
app.results_state.select(None);
|
||||
} else {
|
||||
app.results_state.select(Some(0));
|
||||
}
|
||||
}
|
||||
|
||||
fn key_handler(input: Key, db: &mut impl Database, app: &mut State) -> Option<String> {
|
||||
match input {
|
||||
Key::Esc | Key::Char('\n') => {
|
||||
let i = app.results_state.selected().unwrap_or(0);
|
||||
|
||||
return Some(app.results.get(i).unwrap().command.clone());
|
||||
}
|
||||
Key::Char(c) => {
|
||||
app.input.push(c);
|
||||
query_results(app, db);
|
||||
}
|
||||
Key::Backspace => {
|
||||
app.input.pop();
|
||||
query_results(app, db);
|
||||
}
|
||||
Key::Down => {
|
||||
let i = match app.results_state.selected() {
|
||||
Some(i) => {
|
||||
if i == 0 {
|
||||
0
|
||||
} else {
|
||||
i - 1
|
||||
}
|
||||
}
|
||||
None => 0,
|
||||
};
|
||||
app.results_state.select(Some(i));
|
||||
}
|
||||
Key::Up => {
|
||||
let i = match app.results_state.selected() {
|
||||
Some(i) => {
|
||||
if i >= app.results.len() - 1 {
|
||||
app.results.len() - 1
|
||||
} else {
|
||||
i + 1
|
||||
}
|
||||
}
|
||||
None => 0,
|
||||
};
|
||||
app.results_state.select(Some(i));
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
// this is a big blob of horrible! clean it up!
|
||||
// for now, it works. But it'd be great if it were more easily readable, and
|
||||
// modular. I'd like to add some more stats and stuff at some point
|
||||
#[allow(clippy::clippy::cast_possible_truncation)]
|
||||
fn select_history(query: &[String], db: &mut impl Database) -> Result<String> {
|
||||
let stdout = stdout().into_raw_mode()?;
|
||||
let stdout = MouseTerminal::from(stdout);
|
||||
let stdout = AlternateScreen::from(stdout);
|
||||
let backend = TermionBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
|
||||
// Setup event handlers
|
||||
let events = Events::new();
|
||||
|
||||
let mut app = State {
|
||||
input: query.join(" "),
|
||||
results: Vec::new(),
|
||||
results_state: ListState::default(),
|
||||
};
|
||||
|
||||
query_results(&mut app, db);
|
||||
|
||||
loop {
|
||||
// Handle input
|
||||
if let Event::Input(input) = events.next()? {
|
||||
if let Some(output) = key_handler(input, db, &mut app) {
|
||||
return Ok(output);
|
||||
}
|
||||
}
|
||||
|
||||
terminal.draw(|f| {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.margin(1)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Length(2),
|
||||
Constraint::Min(1),
|
||||
Constraint::Length(3),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(f.size());
|
||||
|
||||
let top_chunks = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
||||
.split(chunks[0]);
|
||||
|
||||
let top_left_chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Length(1), Constraint::Length(1)].as_ref())
|
||||
.split(top_chunks[0]);
|
||||
|
||||
let top_right_chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Length(1), Constraint::Length(1)].as_ref())
|
||||
.split(top_chunks[1]);
|
||||
|
||||
let title = Paragraph::new(Text::from(Span::styled(
|
||||
format!("A'tuin v{}", VERSION),
|
||||
Style::default().add_modifier(Modifier::BOLD),
|
||||
)));
|
||||
|
||||
let help = vec![
|
||||
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(app.input.as_ref())
|
||||
.block(Block::default().borders(Borders::ALL).title("Search"));
|
||||
|
||||
let results: Vec<ListItem> = app
|
||||
.results
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, m)| {
|
||||
let mut content = Span::raw(m.command.to_string());
|
||||
|
||||
if let Some(selected) = app.results_state.selected() {
|
||||
if selected == i {
|
||||
content.style =
|
||||
Style::default().fg(Color::Red).add_modifier(Modifier::BOLD);
|
||||
}
|
||||
}
|
||||
|
||||
ListItem::new(content)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let results = List::new(results)
|
||||
.block(Block::default().borders(Borders::ALL).title("History"))
|
||||
.start_corner(Corner::BottomLeft)
|
||||
.highlight_symbol(">> ");
|
||||
|
||||
let stats = Paragraph::new(Text::from(Span::raw(format!(
|
||||
"history count: {}",
|
||||
db.history_count().unwrap()
|
||||
))))
|
||||
.alignment(Alignment::Right);
|
||||
|
||||
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_stateful_widget(results, chunks[1], &mut app.results_state);
|
||||
f.render_widget(input, chunks[2]);
|
||||
|
||||
f.set_cursor(
|
||||
// Put cursor past the end of the input text
|
||||
chunks[2].x + app.input.width() as u16 + 1,
|
||||
// Move one line down, from the border to the input line
|
||||
chunks[2].y + 1,
|
||||
);
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(query: &[String], db: &mut impl Database) -> Result<()> {
|
||||
let item = select_history(query, db)?;
|
||||
eprintln!("{}", item);
|
||||
|
||||
Ok(())
|
||||
}
|
@ -15,12 +15,17 @@ pub enum QueryParam {
|
||||
pub trait Database {
|
||||
fn save(&mut self, h: &History) -> Result<()>;
|
||||
fn save_bulk(&mut self, h: &[History]) -> Result<()>;
|
||||
|
||||
fn load(&self, id: &str) -> Result<History>;
|
||||
fn list(&self) -> Result<Vec<History>>;
|
||||
fn range(&self, from: chrono::DateTime<Utc>, to: chrono::DateTime<Utc>)
|
||||
-> Result<Vec<History>>;
|
||||
fn update(&self, h: &History) -> Result<()>;
|
||||
|
||||
fn query(&self, query: &str, params: &[QueryParam]) -> Result<Vec<History>>;
|
||||
fn update(&self, h: &History) -> Result<()>;
|
||||
fn history_count(&self) -> Result<i64>;
|
||||
|
||||
fn prefix_search(&self, query: &str) -> Result<Vec<History>>;
|
||||
}
|
||||
|
||||
// Intended for use on a developer machine and not a sync server.
|
||||
@ -199,6 +204,21 @@ impl Database for Sqlite {
|
||||
|
||||
Ok(history_iter.filter_map(Result::ok).collect())
|
||||
}
|
||||
|
||||
fn prefix_search(&self, query: &str) -> Result<Vec<History>> {
|
||||
self.query(
|
||||
"select * from history where command like ?1 || '%' order by timestamp asc",
|
||||
&[QueryParam::Text(query.to_string())],
|
||||
)
|
||||
}
|
||||
|
||||
fn history_count(&self) -> Result<i64> {
|
||||
let res: i64 =
|
||||
self.conn
|
||||
.query_row_and_then("select count(1) from history;", params![], |row| row.get(0))?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
fn history_from_sqlite_row(
|
||||
|
@ -1,8 +1,9 @@
|
||||
use std::env;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
use crate::command::uuid_v4;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct History {
|
||||
pub id: String,
|
||||
pub timestamp: i64,
|
||||
@ -42,3 +43,21 @@ impl History {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for History {
|
||||
// for the sakes of listing unique history only, we do not care about
|
||||
// anything else
|
||||
// obviously this does not refer to the *same* item of history, but when
|
||||
// we only render the command, it looks the same
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.command == other.command
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for History {}
|
||||
|
||||
impl Hash for History {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.command.hash(state);
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ mod settings;
|
||||
#[derive(StructOpt)]
|
||||
#[structopt(
|
||||
author = "Ellie Huxtable <e@elm.sh>",
|
||||
version = "0.3.2",
|
||||
version = "0.4.0",
|
||||
about = "Magical shell history"
|
||||
)]
|
||||
struct Atuin {
|
||||
|
@ -20,7 +20,9 @@ _atuin_search(){
|
||||
emulate -L zsh
|
||||
zle -I
|
||||
|
||||
output=$(eval $ATUIN_HISTORY | fzf)
|
||||
# swap stderr and stdout, so that the tui stuff works
|
||||
# TODO: not this
|
||||
output=$(atuin search $BUFFER 3>&1 1>&2 2>&3)
|
||||
|
||||
if [[ -n $output ]] ; then
|
||||
LBUFFER=$output
|
||||
@ -33,7 +35,9 @@ _atuin_up_search(){
|
||||
emulate -L zsh
|
||||
zle -I
|
||||
|
||||
output=$(eval $ATUIN_HISTORY | fzf --no-sort --tac)
|
||||
# swap stderr and stdout, so that the tui stuff works
|
||||
# TODO: not this
|
||||
output=$(atuin search $BUFFER 3>&1 1>&2 2>&3)
|
||||
|
||||
if [[ -n $output ]] ; then
|
||||
LBUFFER=$output
|
||||
|
Loading…
Reference in New Issue
Block a user