2021-07-02 22:39:32 +03:00
|
|
|
use {
|
2021-07-13 16:42:45 +03:00
|
|
|
crossterm::{
|
2022-10-30 23:41:13 +03:00
|
|
|
event::{KeyCode, KeyModifiers},
|
|
|
|
Result,
|
2021-07-13 16:42:45 +03:00
|
|
|
},
|
2021-07-16 21:56:16 +03:00
|
|
|
nu_ansi_term::{Color, Style},
|
2021-07-02 22:39:32 +03:00
|
|
|
reedline::{
|
2022-01-27 17:43:24 +03:00
|
|
|
default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings,
|
2022-10-30 23:41:13 +03:00
|
|
|
ColumnarMenu, DefaultCompleter, DefaultHinter, DefaultPrompt, DefaultValidator,
|
|
|
|
EditCommand, EditMode, Emacs, ExampleHighlighter, Keybindings, ListMenu, Reedline,
|
|
|
|
ReedlineEvent, ReedlineMenu, Signal, Vi,
|
2021-07-02 22:39:32 +03:00
|
|
|
},
|
2021-07-01 09:47:53 +03:00
|
|
|
};
|
2021-03-18 20:39:23 +03:00
|
|
|
|
2023-04-13 20:24:17 +03:00
|
|
|
use crossterm::cursor::SetCursorStyle;
|
2022-12-21 14:15:46 +03:00
|
|
|
use reedline::CursorConfig;
|
2022-10-30 23:41:13 +03:00
|
|
|
#[cfg(not(any(feature = "sqlite", feature = "sqlite-dynlib")))]
|
|
|
|
use reedline::FileBackedHistory;
|
|
|
|
|
2021-02-28 12:44:28 +03:00
|
|
|
fn main() -> Result<()> {
|
2022-10-16 23:13:55 +03:00
|
|
|
println!("Ctrl-D to quit");
|
2021-07-13 16:42:45 +03:00
|
|
|
// quick command like parameter handling
|
2021-08-28 21:10:16 +03:00
|
|
|
let vi_mode = matches!(std::env::args().nth(1), Some(x) if x == "--vi");
|
2021-06-24 01:41:27 +03:00
|
|
|
|
2022-10-30 18:09:53 +03:00
|
|
|
#[cfg(any(feature = "sqlite", feature = "sqlite-dynlib"))]
|
2022-06-06 19:11:02 +03:00
|
|
|
let history = Box::new(
|
|
|
|
reedline::SqliteBackedHistory::with_file("history.sqlite3".into())
|
|
|
|
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?,
|
|
|
|
);
|
2022-10-30 18:09:53 +03:00
|
|
|
#[cfg(not(any(feature = "sqlite", feature = "sqlite-dynlib")))]
|
2021-07-20 21:11:26 +03:00
|
|
|
let history = Box::new(FileBackedHistory::with_file(50, "history.txt".into())?);
|
2021-07-13 11:52:03 +03:00
|
|
|
let commands = vec![
|
|
|
|
"test".into(),
|
2021-07-20 21:11:26 +03:00
|
|
|
"clear".into(),
|
|
|
|
"exit".into(),
|
2022-01-13 18:48:26 +03:00
|
|
|
"history 1".into(),
|
|
|
|
"history 2".into(),
|
|
|
|
"history 3".into(),
|
|
|
|
"history 4".into(),
|
|
|
|
"history 5".into(),
|
2021-07-20 21:11:26 +03:00
|
|
|
"logout".into(),
|
2022-02-05 01:23:53 +03:00
|
|
|
"login".into(),
|
2021-07-13 11:52:03 +03:00
|
|
|
"hello world".into(),
|
|
|
|
"hello world reedline".into(),
|
2022-01-13 18:48:26 +03:00
|
|
|
"hello world something".into(),
|
|
|
|
"hello world another".into(),
|
|
|
|
"hello world 1".into(),
|
|
|
|
"hello world 2".into(),
|
|
|
|
"hello world 3".into(),
|
|
|
|
"hello world 4".into(),
|
2022-01-27 09:59:14 +03:00
|
|
|
"hello another very large option for hello word that will force one column".into(),
|
2021-07-20 21:11:26 +03:00
|
|
|
"this is the reedline crate".into(),
|
2022-03-03 11:43:11 +03:00
|
|
|
"abaaacas".into(),
|
|
|
|
"abaaac".into(),
|
|
|
|
"abaaaxyc".into(),
|
|
|
|
"abaaarabc".into(),
|
2022-03-27 13:46:57 +03:00
|
|
|
"こんにちは世界".into(),
|
|
|
|
"こんばんは世界".into(),
|
2021-07-13 11:52:03 +03:00
|
|
|
];
|
2021-07-09 07:27:27 +03:00
|
|
|
|
2021-07-16 21:56:16 +03:00
|
|
|
let completer = Box::new(DefaultCompleter::new_with_wordlen(commands.clone(), 2));
|
|
|
|
|
2022-12-21 14:15:46 +03:00
|
|
|
let cursor_config = CursorConfig {
|
2023-04-13 20:24:17 +03:00
|
|
|
vi_insert: Some(SetCursorStyle::BlinkingBar),
|
|
|
|
vi_normal: Some(SetCursorStyle::SteadyBlock),
|
2022-12-21 14:15:46 +03:00
|
|
|
emacs: None,
|
|
|
|
};
|
|
|
|
|
2023-04-18 15:13:49 +03:00
|
|
|
// Setting history_per_session to true will allow the history to be isolated to the current session
|
|
|
|
// Setting history_per_session to false will allow the history to be shared across all sessions
|
|
|
|
let history_per_session = false;
|
|
|
|
let mut history_session_id = if history_per_session {
|
|
|
|
Reedline::create_history_session_id()
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
2022-04-01 00:32:31 +03:00
|
|
|
let mut line_editor = Reedline::create()
|
2023-04-18 15:13:49 +03:00
|
|
|
.with_history_session_id(history_session_id)
|
2022-04-01 00:32:31 +03:00
|
|
|
.with_history(history)
|
2022-01-27 17:43:24 +03:00
|
|
|
.with_completer(completer)
|
2022-03-14 03:44:47 +03:00
|
|
|
.with_quick_completions(true)
|
2022-03-03 11:43:11 +03:00
|
|
|
.with_partial_completions(true)
|
2022-12-21 14:15:46 +03:00
|
|
|
.with_cursor_config(cursor_config)
|
2022-01-02 22:26:30 +03:00
|
|
|
.with_highlighter(Box::new(ExampleHighlighter::new(commands)))
|
2021-07-16 21:56:16 +03:00
|
|
|
.with_hinter(Box::new(
|
2022-01-20 21:25:40 +03:00
|
|
|
DefaultHinter::default().with_style(Style::new().fg(Color::DarkGray)),
|
2021-12-09 23:48:08 +03:00
|
|
|
))
|
2022-04-11 13:43:28 +03:00
|
|
|
.with_validator(Box::new(DefaultValidator))
|
2022-01-27 09:59:14 +03:00
|
|
|
.with_ansi_colors(true);
|
|
|
|
|
|
|
|
// Adding default menus for the compiled reedline
|
2022-01-27 17:43:24 +03:00
|
|
|
line_editor = line_editor
|
2022-04-04 13:04:57 +03:00
|
|
|
.with_menu(ReedlineMenu::EngineCompleter(Box::new(
|
|
|
|
ColumnarMenu::default().with_name("completion_menu"),
|
|
|
|
)))
|
|
|
|
.with_menu(ReedlineMenu::HistoryMenu(Box::new(
|
|
|
|
ListMenu::default().with_name("history_menu"),
|
|
|
|
)));
|
2022-01-27 09:59:14 +03:00
|
|
|
|
|
|
|
let edit_mode: Box<dyn EditMode> = if vi_mode {
|
|
|
|
let mut normal_keybindings = default_vi_normal_keybindings();
|
|
|
|
let mut insert_keybindings = default_vi_insert_keybindings();
|
|
|
|
|
|
|
|
add_menu_keybindings(&mut normal_keybindings);
|
|
|
|
add_menu_keybindings(&mut insert_keybindings);
|
|
|
|
|
2022-04-20 10:58:10 +03:00
|
|
|
add_newline_keybinding(&mut insert_keybindings);
|
|
|
|
|
2022-01-27 09:59:14 +03:00
|
|
|
Box::new(Vi::new(insert_keybindings, normal_keybindings))
|
|
|
|
} else {
|
|
|
|
let mut keybindings = default_emacs_keybindings();
|
|
|
|
add_menu_keybindings(&mut keybindings);
|
2022-04-20 10:58:10 +03:00
|
|
|
add_newline_keybinding(&mut keybindings);
|
2022-01-27 09:59:14 +03:00
|
|
|
|
|
|
|
Box::new(Emacs::new(keybindings))
|
|
|
|
};
|
|
|
|
|
|
|
|
line_editor = line_editor.with_edit_mode(edit_mode);
|
2021-02-28 12:44:28 +03:00
|
|
|
|
2022-04-30 17:09:43 +03:00
|
|
|
// Adding vi as text editor
|
|
|
|
line_editor = line_editor.with_buffer_editor("vi".into(), "nu".into());
|
|
|
|
|
2022-12-01 15:58:01 +03:00
|
|
|
let prompt = DefaultPrompt::default();
|
2021-04-30 03:26:52 +03:00
|
|
|
|
2021-03-16 03:10:33 +03:00
|
|
|
loop {
|
2021-08-07 01:10:00 +03:00
|
|
|
let sig = line_editor.read_line(&prompt);
|
2021-06-15 02:03:57 +03:00
|
|
|
|
2021-04-10 00:21:37 +03:00
|
|
|
match sig {
|
2021-08-07 01:10:00 +03:00
|
|
|
Ok(Signal::CtrlD) => {
|
2021-04-10 00:21:37 +03:00
|
|
|
break;
|
|
|
|
}
|
2021-08-07 01:10:00 +03:00
|
|
|
Ok(Signal::Success(buffer)) => {
|
2022-10-30 18:09:53 +03:00
|
|
|
#[cfg(any(feature = "sqlite", feature = "sqlite-dynlib"))]
|
2022-06-06 19:11:02 +03:00
|
|
|
let start = std::time::Instant::now();
|
|
|
|
// save timestamp, cwd, hostname to history
|
2022-10-30 18:09:53 +03:00
|
|
|
#[cfg(any(feature = "sqlite", feature = "sqlite-dynlib"))]
|
2022-06-06 19:11:02 +03:00
|
|
|
if !buffer.is_empty() {
|
|
|
|
line_editor
|
|
|
|
.update_last_command_context(&|mut c: reedline::HistoryItem| {
|
|
|
|
c.start_timestamp = Some(chrono::Utc::now());
|
|
|
|
c.hostname =
|
|
|
|
Some(gethostname::gethostname().to_string_lossy().to_string());
|
|
|
|
c.cwd = std::env::current_dir()
|
|
|
|
.ok()
|
|
|
|
.map(|e| e.to_string_lossy().to_string());
|
|
|
|
c
|
|
|
|
})
|
|
|
|
.expect("todo: error handling");
|
|
|
|
}
|
2021-04-10 00:21:37 +03:00
|
|
|
if (buffer.trim() == "exit") || (buffer.trim() == "logout") {
|
2021-03-17 14:01:50 +03:00
|
|
|
break;
|
|
|
|
}
|
2021-04-10 00:21:37 +03:00
|
|
|
if buffer.trim() == "clear" {
|
2022-04-20 11:10:58 +03:00
|
|
|
line_editor.clear_scrollback()?;
|
2021-04-10 00:21:37 +03:00
|
|
|
continue;
|
2021-03-17 14:01:50 +03:00
|
|
|
}
|
2023-04-18 15:13:49 +03:00
|
|
|
// Get the full history
|
2021-04-10 00:21:37 +03:00
|
|
|
if buffer.trim() == "history" {
|
|
|
|
line_editor.print_history()?;
|
|
|
|
continue;
|
2021-03-19 22:27:42 +03:00
|
|
|
}
|
2023-04-18 15:13:49 +03:00
|
|
|
// Get the history only pertinent to the current session
|
|
|
|
if buffer.trim() == "history session" {
|
|
|
|
line_editor.print_history_session()?;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// Get this history session identifier
|
|
|
|
if buffer.trim() == "history sessionid" {
|
|
|
|
line_editor.print_history_session_id()?;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// Toggle between the full history and the history pertinent to the current session
|
|
|
|
if buffer.trim() == "toggle history_session" {
|
|
|
|
let hist_session_id = if history_session_id.is_none() {
|
|
|
|
// If we never created a history session ID, create one now
|
|
|
|
let sesh = Reedline::create_history_session_id();
|
|
|
|
history_session_id = sesh;
|
|
|
|
sesh
|
|
|
|
} else {
|
|
|
|
history_session_id
|
|
|
|
};
|
|
|
|
line_editor.toggle_history_session_matching(hist_session_id)?;
|
|
|
|
continue;
|
|
|
|
}
|
2023-02-10 20:34:30 +03:00
|
|
|
if buffer.trim() == "clear-history" {
|
|
|
|
let hstry = Box::new(line_editor.history_mut());
|
|
|
|
hstry
|
|
|
|
.clear()
|
|
|
|
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
|
|
|
|
continue;
|
|
|
|
}
|
2023-01-27 17:58:25 +03:00
|
|
|
println!("Our buffer: {buffer}");
|
2022-10-30 18:09:53 +03:00
|
|
|
#[cfg(any(feature = "sqlite", feature = "sqlite-dynlib"))]
|
2022-06-06 19:11:02 +03:00
|
|
|
if !buffer.is_empty() {
|
|
|
|
line_editor
|
|
|
|
.update_last_command_context(&|mut c| {
|
|
|
|
c.duration = Some(start.elapsed());
|
|
|
|
c.exit_status = Some(0);
|
|
|
|
c
|
|
|
|
})
|
|
|
|
.expect("todo: error handling");
|
|
|
|
}
|
2021-04-10 00:21:37 +03:00
|
|
|
}
|
2021-08-07 01:10:00 +03:00
|
|
|
Ok(Signal::CtrlC) => {
|
2021-12-20 00:34:32 +03:00
|
|
|
// Prompt has been cleared and should start on the next line
|
2021-04-10 00:21:37 +03:00
|
|
|
}
|
2021-08-07 01:10:00 +03:00
|
|
|
Err(err) => {
|
2023-01-27 17:58:25 +03:00
|
|
|
println!("Error: {err:?}");
|
2021-08-07 01:10:00 +03:00
|
|
|
}
|
2021-02-28 12:44:28 +03:00
|
|
|
}
|
|
|
|
}
|
2021-03-16 03:10:33 +03:00
|
|
|
|
2021-02-28 12:44:28 +03:00
|
|
|
println!();
|
|
|
|
Ok(())
|
|
|
|
}
|
2021-07-13 16:42:45 +03:00
|
|
|
|
2022-01-27 09:59:14 +03:00
|
|
|
fn add_menu_keybindings(keybindings: &mut Keybindings) {
|
|
|
|
keybindings.add_binding(
|
|
|
|
KeyModifiers::CONTROL,
|
2022-01-28 01:34:35 +03:00
|
|
|
KeyCode::Char('x'),
|
2022-01-27 09:59:14 +03:00
|
|
|
ReedlineEvent::UntilFound(vec![
|
|
|
|
ReedlineEvent::Menu("history_menu".to_string()),
|
|
|
|
ReedlineEvent::MenuPageNext,
|
|
|
|
]),
|
|
|
|
);
|
|
|
|
|
|
|
|
keybindings.add_binding(
|
|
|
|
KeyModifiers::CONTROL | KeyModifiers::SHIFT,
|
2022-01-28 01:34:35 +03:00
|
|
|
KeyCode::Char('x'),
|
2022-01-27 09:59:14 +03:00
|
|
|
ReedlineEvent::MenuPagePrevious,
|
|
|
|
);
|
|
|
|
|
|
|
|
keybindings.add_binding(
|
|
|
|
KeyModifiers::NONE,
|
|
|
|
KeyCode::Tab,
|
|
|
|
ReedlineEvent::UntilFound(vec![
|
2022-01-27 17:43:24 +03:00
|
|
|
ReedlineEvent::Menu("completion_menu".to_string()),
|
2022-10-22 23:52:11 +03:00
|
|
|
ReedlineEvent::Edit(vec![EditCommand::Complete]),
|
2022-01-27 09:59:14 +03:00
|
|
|
]),
|
|
|
|
);
|
|
|
|
|
|
|
|
keybindings.add_binding(
|
|
|
|
KeyModifiers::SHIFT,
|
|
|
|
KeyCode::BackTab,
|
|
|
|
ReedlineEvent::MenuPrevious,
|
|
|
|
);
|
|
|
|
}
|
2022-02-02 16:25:48 +03:00
|
|
|
|
2022-04-20 10:58:10 +03:00
|
|
|
fn add_newline_keybinding(keybindings: &mut Keybindings) {
|
|
|
|
// This doesn't work for macOS
|
|
|
|
keybindings.add_binding(
|
|
|
|
KeyModifiers::ALT,
|
|
|
|
KeyCode::Enter,
|
|
|
|
ReedlineEvent::Edit(vec![EditCommand::InsertNewline]),
|
|
|
|
);
|
|
|
|
}
|