the second field of history entries in the hist file now contain command runtime in seconds, instead of an id
updated rustfmt.toml and formatted codebase
This commit is contained in:
@@ -1,7 +1,5 @@
|
|||||||
max_width = 100
|
max_width = 100
|
||||||
tab_spaces = 2
|
tab_spaces = 2
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
newline_style = "Unix"
|
newline_style = "Unix"
|
||||||
|
|
||||||
wrap_comments = true
|
|
||||||
|
|||||||
@@ -1404,7 +1404,6 @@ impl FromStr for ParamExp {
|
|||||||
))
|
))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Handle indirect var expansion: ${!var}
|
// Handle indirect var expansion: ${!var}
|
||||||
if let Some(var) = s.strip_prefix('!') {
|
if let Some(var) = s.strip_prefix('!') {
|
||||||
if var.ends_with('*') || var.ends_with('@') {
|
if var.ends_with('*') || var.ends_with('@') {
|
||||||
@@ -2374,7 +2373,7 @@ pub fn parse_key_alias(alias: &str) -> Option<KeyEvent> {
|
|||||||
"RIGHT" => KeyCode::Right,
|
"RIGHT" => KeyCode::Right,
|
||||||
"HOME" => KeyCode::Home,
|
"HOME" => KeyCode::Home,
|
||||||
"END" => KeyCode::End,
|
"END" => KeyCode::End,
|
||||||
"CMD" => KeyCode::ExMode,
|
"CMD" => KeyCode::ExMode,
|
||||||
"PGUP" | "PAGEUP" => KeyCode::PageUp,
|
"PGUP" | "PAGEUP" => KeyCode::PageUp,
|
||||||
"PGDN" | "PAGEDOWN" => KeyCode::PageDown,
|
"PGDN" | "PAGEDOWN" => KeyCode::PageDown,
|
||||||
k if k.len() == 1 => KeyCode::Char(k.chars().next().unwrap()),
|
k if k.len() == 1 => KeyCode::Char(k.chars().next().unwrap()),
|
||||||
|
|||||||
186
src/main.rs
186
src/main.rs
@@ -37,7 +37,7 @@ use crate::prelude::*;
|
|||||||
use crate::readline::term::{LineWriter, RawModeGuard, raw_mode};
|
use crate::readline::term::{LineWriter, RawModeGuard, raw_mode};
|
||||||
use crate::readline::{Prompt, ReadlineEvent, ShedVi};
|
use crate::readline::{Prompt, ReadlineEvent, ShedVi};
|
||||||
use crate::signal::{GOT_SIGWINCH, JOB_DONE, QUIT_CODE, check_signals, sig_setup, signals_pending};
|
use crate::signal::{GOT_SIGWINCH, JOB_DONE, QUIT_CODE, check_signals, sig_setup, signals_pending};
|
||||||
use crate::state::{AutoCmdKind, read_logic, source_rc, write_jobs, write_meta};
|
use crate::state::{AutoCmdKind, read_logic, read_shopts, source_rc, write_jobs, write_meta};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use state::{read_vars, write_vars};
|
use state::{read_vars, write_vars};
|
||||||
|
|
||||||
@@ -292,36 +292,9 @@ fn shed_interactive(args: ShedArgs) -> ShResult<()> {
|
|||||||
let action = km.action_expanded();
|
let action = km.action_expanded();
|
||||||
readline.pending_keymap.clear();
|
readline.pending_keymap.clear();
|
||||||
for key in action {
|
for key in action {
|
||||||
if let Some(event) = readline.handle_key(key)? {
|
let event = readline.handle_key(key).transpose();
|
||||||
match event {
|
if let Some(event) = event {
|
||||||
ReadlineEvent::Line(input) => {
|
handle_readline_event(&mut readline, event)?;
|
||||||
let start = Instant::now();
|
|
||||||
write_meta(|m| m.start_timer());
|
|
||||||
if let Err(e) = RawModeGuard::with_cooked_mode(|| {
|
|
||||||
exec_input(input, None, true, Some("<stdin>".into()))
|
|
||||||
}) {
|
|
||||||
match e.kind() {
|
|
||||||
ShErrKind::CleanExit(code) => {
|
|
||||||
QUIT_CODE.store(*code, Ordering::SeqCst);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
_ => e.print_error(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let command_run_time = start.elapsed();
|
|
||||||
log::info!("Command executed in {:.2?}", command_run_time);
|
|
||||||
write_meta(|m| m.stop_timer());
|
|
||||||
readline.fix_column()?;
|
|
||||||
readline.writer.flush_write("\n\r")?;
|
|
||||||
readline.reset(true)?;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ReadlineEvent::Eof => {
|
|
||||||
QUIT_CODE.store(0, Ordering::SeqCst);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
ReadlineEvent::Pending => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -331,36 +304,9 @@ fn shed_interactive(args: ShedArgs) -> ShResult<()> {
|
|||||||
);
|
);
|
||||||
let buffered = std::mem::take(&mut readline.pending_keymap);
|
let buffered = std::mem::take(&mut readline.pending_keymap);
|
||||||
for key in buffered {
|
for key in buffered {
|
||||||
if let Some(event) = readline.handle_key(key)? {
|
let event = readline.handle_key(key).transpose();
|
||||||
match event {
|
if let Some(event) = event {
|
||||||
ReadlineEvent::Line(input) => {
|
handle_readline_event(&mut readline, event)?;
|
||||||
let start = Instant::now();
|
|
||||||
write_meta(|m| m.start_timer());
|
|
||||||
if let Err(e) = RawModeGuard::with_cooked_mode(|| {
|
|
||||||
exec_input(input, None, true, Some("<stdin>".into()))
|
|
||||||
}) {
|
|
||||||
match e.kind() {
|
|
||||||
ShErrKind::CleanExit(code) => {
|
|
||||||
QUIT_CODE.store(*code, Ordering::SeqCst);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
_ => e.print_error(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let command_run_time = start.elapsed();
|
|
||||||
log::info!("Command executed in {:.2?}", command_run_time);
|
|
||||||
write_meta(|m| m.stop_timer());
|
|
||||||
readline.fix_column()?;
|
|
||||||
readline.writer.flush_write("\n\r")?;
|
|
||||||
readline.reset(true)?;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ReadlineEvent::Eof => {
|
|
||||||
QUIT_CODE.store(0, Ordering::SeqCst);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
ReadlineEvent::Pending => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -394,58 +340,76 @@ fn shed_interactive(args: ShedArgs) -> ShResult<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Process any available input
|
// Process any available input
|
||||||
match readline.process_input() {
|
let event = readline.process_input();
|
||||||
Ok(ReadlineEvent::Line(input)) => {
|
match handle_readline_event(&mut readline, event)? {
|
||||||
let pre_exec = read_logic(|l| l.get_autocmds(AutoCmdKind::PreCmd));
|
true => return Ok(()),
|
||||||
let post_exec = read_logic(|l| l.get_autocmds(AutoCmdKind::PostCmd));
|
false => { /* continue looping */ }
|
||||||
|
|
||||||
pre_exec.exec_with(&input);
|
|
||||||
|
|
||||||
let start = Instant::now();
|
|
||||||
write_meta(|m| m.start_timer());
|
|
||||||
if let Err(e) = RawModeGuard::with_cooked_mode(|| {
|
|
||||||
exec_input(input.clone(), None, true, Some("<stdin>".into()))
|
|
||||||
}) {
|
|
||||||
match e.kind() {
|
|
||||||
ShErrKind::CleanExit(code) => {
|
|
||||||
QUIT_CODE.store(*code, Ordering::SeqCst);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
_ => e.print_error(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let command_run_time = start.elapsed();
|
|
||||||
log::info!("Command executed in {:.2?}", command_run_time);
|
|
||||||
write_meta(|m| m.stop_timer());
|
|
||||||
|
|
||||||
post_exec.exec_with(&input);
|
|
||||||
|
|
||||||
readline.fix_column()?;
|
|
||||||
readline.writer.flush_write("\n\r")?;
|
|
||||||
|
|
||||||
// Reset for next command with fresh prompt
|
|
||||||
readline.reset(true)?;
|
|
||||||
|
|
||||||
let real_end = start.elapsed();
|
|
||||||
log::info!("Total round trip time: {:.2?}", real_end);
|
|
||||||
}
|
|
||||||
Ok(ReadlineEvent::Eof) => {
|
|
||||||
// Ctrl+D on empty line
|
|
||||||
QUIT_CODE.store(0, Ordering::SeqCst);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
Ok(ReadlineEvent::Pending) => {
|
|
||||||
// No complete input yet, keep polling
|
|
||||||
}
|
|
||||||
Err(e) => match e.kind() {
|
|
||||||
ShErrKind::CleanExit(code) => {
|
|
||||||
QUIT_CODE.store(*code, Ordering::SeqCst);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
_ => e.print_error(),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_readline_event(readline: &mut ShedVi, event: ShResult<ReadlineEvent>) -> ShResult<bool> {
|
||||||
|
match event {
|
||||||
|
Ok(ReadlineEvent::Line(input)) => {
|
||||||
|
let pre_exec = read_logic(|l| l.get_autocmds(AutoCmdKind::PreCmd));
|
||||||
|
let post_exec = read_logic(|l| l.get_autocmds(AutoCmdKind::PostCmd));
|
||||||
|
|
||||||
|
pre_exec.exec_with(&input);
|
||||||
|
|
||||||
|
let start = Instant::now();
|
||||||
|
write_meta(|m| m.start_timer());
|
||||||
|
if let Err(e) = RawModeGuard::with_cooked_mode(|| {
|
||||||
|
exec_input(input.clone(), None, true, Some("<stdin>".into()))
|
||||||
|
}) {
|
||||||
|
match e.kind() {
|
||||||
|
ShErrKind::CleanExit(code) => {
|
||||||
|
QUIT_CODE.store(*code, Ordering::SeqCst);
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
_ => e.print_error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let command_run_time = start.elapsed();
|
||||||
|
log::info!("Command executed in {:.2?}", command_run_time);
|
||||||
|
write_meta(|m| m.stop_timer());
|
||||||
|
|
||||||
|
post_exec.exec_with(&input);
|
||||||
|
|
||||||
|
if read_shopts(|s| s.core.auto_hist) && !input.is_empty() {
|
||||||
|
readline.history.push(input.clone());
|
||||||
|
readline.history.save()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
readline.fix_column()?;
|
||||||
|
readline.writer.flush_write("\n\r")?;
|
||||||
|
|
||||||
|
// Reset for next command with fresh prompt
|
||||||
|
readline.reset(true)?;
|
||||||
|
|
||||||
|
let real_end = start.elapsed();
|
||||||
|
log::info!("Total round trip time: {:.2?}", real_end);
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
Ok(ReadlineEvent::Eof) => {
|
||||||
|
// Ctrl+D on empty line
|
||||||
|
QUIT_CODE.store(0, Ordering::SeqCst);
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
Ok(ReadlineEvent::Pending) => {
|
||||||
|
// No complete input yet, keep polling
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
Err(e) => match e.kind() {
|
||||||
|
ShErrKind::CleanExit(code) => {
|
||||||
|
QUIT_CODE.store(*code, Ordering::SeqCst);
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
e.print_error();
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -877,7 +877,7 @@ impl Iterator for LexStream {
|
|||||||
if self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
if self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
||||||
self.get_token(ch_idx..self.cursor, TkRule::Comment)
|
self.get_token(ch_idx..self.cursor, TkRule::Comment)
|
||||||
} else {
|
} else {
|
||||||
// After consuming the comment, we call next() recursively. This effectively filters out comment tokens.
|
// After consuming the comment, we call next() recursively. This effectively filters out comment tokens.
|
||||||
return self.next();
|
return self.next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ impl ParsedSrc {
|
|||||||
Err(error) => return Err(vec![error]),
|
Err(error) => return Err(vec![error]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log::debug!("Tokens: {:#?}", tokens);
|
log::debug!("Tokens: {:#?}", tokens);
|
||||||
|
|
||||||
let mut errors = vec![];
|
let mut errors = vec![];
|
||||||
let mut nodes = vec![];
|
let mut nodes = vec![];
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ impl IoMode {
|
|||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
pub fn get_pipes() -> (Self, Self) {
|
pub fn get_pipes() -> (Self, Self) {
|
||||||
let (rpipe, wpipe) = pipe().unwrap();
|
let (rpipe, wpipe) = nix::unistd::pipe2(OFlag::O_CLOEXEC).unwrap();
|
||||||
(
|
(
|
||||||
Self::Pipe {
|
Self::Pipe {
|
||||||
tgt_fd: STDIN_FILENO,
|
tgt_fd: STDIN_FILENO,
|
||||||
@@ -220,6 +220,12 @@ impl<'e> IoFrame {
|
|||||||
let tgt_fd = io_mode.tgt_fd();
|
let tgt_fd = io_mode.tgt_fd();
|
||||||
let src_fd = io_mode.src_fd();
|
let src_fd = io_mode.src_fd();
|
||||||
dup2(src_fd, tgt_fd)?;
|
dup2(src_fd, tgt_fd)?;
|
||||||
|
// Close the original pipe fd after dup2 — it's been duplicated to
|
||||||
|
// tgt_fd and keeping it open prevents SIGPIPE delivery in pipelines.
|
||||||
|
// We replace the IoMode to drop the Arc<OwnedFd>, which closes the fd.
|
||||||
|
if matches!(io_mode, IoMode::Pipe { .. }) {
|
||||||
|
*io_mode = IoMode::Close { tgt_fd };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(RedirGuard::new(self))
|
Ok(RedirGuard::new(self))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -713,7 +713,7 @@ pub struct FuzzySelector {
|
|||||||
filtered: Vec<ScoredCandidate>,
|
filtered: Vec<ScoredCandidate>,
|
||||||
candidates: Vec<String>,
|
candidates: Vec<String>,
|
||||||
cursor: ClampedUsize,
|
cursor: ClampedUsize,
|
||||||
number_candidates: bool,
|
number_candidates: bool,
|
||||||
old_layout: Option<FuzzyLayout>,
|
old_layout: Option<FuzzyLayout>,
|
||||||
max_height: usize,
|
max_height: usize,
|
||||||
scroll_offset: usize,
|
scroll_offset: usize,
|
||||||
@@ -749,7 +749,7 @@ impl FuzzySelector {
|
|||||||
filtered: vec![],
|
filtered: vec![],
|
||||||
candidates: vec![],
|
candidates: vec![],
|
||||||
cursor: ClampedUsize::new(0, 0, true),
|
cursor: ClampedUsize::new(0, 0, true),
|
||||||
number_candidates: false,
|
number_candidates: false,
|
||||||
old_layout: None,
|
old_layout: None,
|
||||||
scroll_offset: 0,
|
scroll_offset: 0,
|
||||||
active: false,
|
active: false,
|
||||||
@@ -759,12 +759,12 @@ impl FuzzySelector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn number_candidates(self, enable: bool) -> Self {
|
pub fn number_candidates(self, enable: bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
number_candidates: enable,
|
number_candidates: enable,
|
||||||
..self
|
..self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn activate(&mut self, candidates: Vec<String>) {
|
pub fn activate(&mut self, candidates: Vec<String>) {
|
||||||
self.active = true;
|
self.active = true;
|
||||||
@@ -772,11 +772,11 @@ impl FuzzySelector {
|
|||||||
self.score_candidates();
|
self.score_candidates();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_query(&mut self, query: String) {
|
pub fn set_query(&mut self, query: String) {
|
||||||
self.query.linebuf = LineBuf::new().with_initial(&query, query.len());
|
self.query.linebuf = LineBuf::new().with_initial(&query, query.len());
|
||||||
self.query.update_scroll_offset();
|
self.query.update_scroll_offset();
|
||||||
self.score_candidates();
|
self.score_candidates();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
self.query.clear();
|
self.query.clear();
|
||||||
@@ -812,7 +812,9 @@ impl FuzzySelector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn candidate_height(&self, idx: usize) -> usize {
|
fn candidate_height(&self, idx: usize) -> usize {
|
||||||
self.filtered.get(idx)
|
self
|
||||||
|
.filtered
|
||||||
|
.get(idx)
|
||||||
.map(|c| c.content.trim_end().lines().count().max(1))
|
.map(|c| c.content.trim_end().lines().count().max(1))
|
||||||
.unwrap_or(1)
|
.unwrap_or(1)
|
||||||
}
|
}
|
||||||
@@ -929,9 +931,15 @@ impl FuzzySelector {
|
|||||||
let num_candidates = format!("\x1b[33m{}\x1b[0m", self.candidates.len());
|
let num_candidates = format!("\x1b[33m{}\x1b[0m", self.candidates.len());
|
||||||
let title = self.title.clone();
|
let title = self.title.clone();
|
||||||
let title_width = title.len() as u16;
|
let title_width = title.len() as u16;
|
||||||
let number_candidates = self.number_candidates;
|
let number_candidates = self.number_candidates;
|
||||||
let min_pad = self.candidates.len().to_string().len().saturating_add(1).max(6);
|
let min_pad = self
|
||||||
let max_height = self.max_height;
|
.candidates
|
||||||
|
.len()
|
||||||
|
.to_string()
|
||||||
|
.len()
|
||||||
|
.saturating_add(1)
|
||||||
|
.max(6);
|
||||||
|
let max_height = self.max_height;
|
||||||
let visible = self.get_window();
|
let visible = self.get_window();
|
||||||
let mut rows: u16 = 0;
|
let mut rows: u16 = 0;
|
||||||
let top_bar = format!(
|
let top_bar = format!(
|
||||||
@@ -966,51 +974,63 @@ impl FuzzySelector {
|
|||||||
buf.push_str(&sep_line_final);
|
buf.push_str(&sep_line_final);
|
||||||
rows += 1;
|
rows += 1;
|
||||||
|
|
||||||
let mut lines_drawn = 0;
|
let mut lines_drawn = 0;
|
||||||
for (i, candidate) in visible.iter().enumerate() {
|
for (i, candidate) in visible.iter().enumerate() {
|
||||||
if lines_drawn >= max_height {
|
if lines_drawn >= max_height {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let selector = if i + offset == cursor_pos {
|
let selector = if i + offset == cursor_pos {
|
||||||
Self::SELECTOR_HL
|
Self::SELECTOR_HL
|
||||||
} else {
|
} else {
|
||||||
Self::SELECTOR_GRAY
|
Self::SELECTOR_GRAY
|
||||||
};
|
};
|
||||||
let mut drew_number = false;
|
let mut drew_number = false;
|
||||||
for line in candidate.content.trim_end().lines() {
|
for line in candidate.content.trim_end().lines() {
|
||||||
if lines_drawn >= max_height {
|
if lines_drawn >= max_height {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let mut line = line.trim_end().replace('\t', " ");
|
let mut line = line.trim_end().replace('\t', " ");
|
||||||
let col_lim = if number_candidates{
|
let col_lim = if number_candidates {
|
||||||
cols.saturating_sub(3 + min_pad as u16)
|
cols.saturating_sub(3 + min_pad as u16)
|
||||||
} else {
|
} else {
|
||||||
cols.saturating_sub(3)
|
cols.saturating_sub(3)
|
||||||
};
|
};
|
||||||
if calc_str_width(&line) > col_lim {
|
if calc_str_width(&line) > col_lim {
|
||||||
line.truncate(col_lim.saturating_sub(6) as usize);
|
line.truncate(col_lim.saturating_sub(6) as usize);
|
||||||
line.push_str("...");
|
line.push_str("...");
|
||||||
}
|
}
|
||||||
let left = if number_candidates {
|
let left = if number_candidates {
|
||||||
if !drew_number {
|
if !drew_number {
|
||||||
let this_num = i + offset + 1;
|
let this_num = i + offset + 1;
|
||||||
let right_pad = " ".repeat(min_pad.saturating_sub(this_num.to_string().len()));
|
let right_pad = " ".repeat(min_pad.saturating_sub(this_num.to_string().len()));
|
||||||
format!("{} {}\x1b[33m{}\x1b[39m{right_pad}{}\x1b[0m", Self::VERT_LINE, &selector,i + offset + 1, &line)
|
format!(
|
||||||
} else {
|
"{} {}\x1b[33m{}\x1b[39m{right_pad}{}\x1b[0m",
|
||||||
let right_pad = " ".repeat(min_pad);
|
Self::VERT_LINE,
|
||||||
format!("{} {}{}{}\x1b[0m", Self::VERT_LINE, &selector,right_pad, &line)
|
&selector,
|
||||||
}
|
i + offset + 1,
|
||||||
} else {
|
&line
|
||||||
format!("{} {}{}\x1b[0m", Self::VERT_LINE, &selector, &line)
|
)
|
||||||
};
|
} else {
|
||||||
let cols_used = calc_str_width(&left);
|
let right_pad = " ".repeat(min_pad);
|
||||||
let right_pad = " ".repeat(cols.saturating_sub(cols_used + 1) as usize);
|
format!(
|
||||||
let hl_cand_line = format!("{}{}{}", left, right_pad, Self::VERT_LINE);
|
"{} {}{}{}\x1b[0m",
|
||||||
buf.push_str(&hl_cand_line);
|
Self::VERT_LINE,
|
||||||
rows += 1;
|
&selector,
|
||||||
drew_number = true;
|
right_pad,
|
||||||
lines_drawn += 1;
|
&line
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
format!("{} {}{}\x1b[0m", Self::VERT_LINE, &selector, &line)
|
||||||
|
};
|
||||||
|
let cols_used = calc_str_width(&left);
|
||||||
|
let right_pad = " ".repeat(cols.saturating_sub(cols_used + 1) as usize);
|
||||||
|
let hl_cand_line = format!("{}{}{}", left, right_pad, Self::VERT_LINE);
|
||||||
|
buf.push_str(&hl_cand_line);
|
||||||
|
rows += 1;
|
||||||
|
drew_number = true;
|
||||||
|
lines_drawn += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let bot_bar = format!(
|
let bot_bar = format!(
|
||||||
@@ -1100,7 +1120,9 @@ impl Default for FuzzyCompleter {
|
|||||||
|
|
||||||
impl Completer for FuzzyCompleter {
|
impl Completer for FuzzyCompleter {
|
||||||
fn set_prompt_line_context(&mut self, line_width: u16, cursor_col: u16) {
|
fn set_prompt_line_context(&mut self, line_width: u16, cursor_col: u16) {
|
||||||
self.selector.set_prompt_line_context(line_width, cursor_col);
|
self
|
||||||
|
.selector
|
||||||
|
.set_prompt_line_context(line_width, cursor_col);
|
||||||
}
|
}
|
||||||
fn reset_stay_active(&mut self) {
|
fn reset_stay_active(&mut self) {
|
||||||
self.selector.reset_stay_active();
|
self.selector.reset_stay_active();
|
||||||
|
|||||||
@@ -105,9 +105,9 @@ impl Highlighter {
|
|||||||
self.in_selection = false;
|
self.in_selection = false;
|
||||||
}
|
}
|
||||||
_ if self.only_hl_visual => {
|
_ if self.only_hl_visual => {
|
||||||
if !is_marker(ch) {
|
if !is_marker(ch) {
|
||||||
self.output.push(ch);
|
self.output.push(ch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
markers::STRING_DQ_END
|
markers::STRING_DQ_END
|
||||||
| markers::STRING_SQ_END
|
| markers::STRING_SQ_END
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
use std::{
|
use std::{
|
||||||
cmp::Ordering, collections::HashSet, env, fmt::{Display, Write}, fs::{self, OpenOptions}, io::Write as IoWrite, path::{Path, PathBuf}, str::FromStr, time::{Duration, SystemTime, UNIX_EPOCH}
|
cmp::Ordering,
|
||||||
|
collections::HashSet,
|
||||||
|
env,
|
||||||
|
fmt::{Display, Write},
|
||||||
|
fs::{self, OpenOptions},
|
||||||
|
io::Write as IoWrite,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
str::FromStr,
|
||||||
|
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
libsh::error::{ShErr, ShErrKind, ShResult},
|
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||||
readline::{complete::FuzzySelector, linebuf::LineBuf},
|
readline::{complete::FuzzySelector, linebuf::LineBuf},
|
||||||
|
state::read_meta,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Default, Clone, Copy, Debug)]
|
#[derive(Default, Clone, Copy, Debug)]
|
||||||
@@ -28,16 +37,13 @@ impl SearchConstraint {
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct HistEntry {
|
pub struct HistEntry {
|
||||||
id: u32,
|
runtime: Duration,
|
||||||
timestamp: SystemTime,
|
timestamp: SystemTime,
|
||||||
command: String,
|
command: String,
|
||||||
new: bool,
|
new: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HistEntry {
|
impl HistEntry {
|
||||||
pub fn id(&self) -> u32 {
|
|
||||||
self.id
|
|
||||||
}
|
|
||||||
pub fn timestamp(&self) -> &SystemTime {
|
pub fn timestamp(&self) -> &SystemTime {
|
||||||
&self.timestamp
|
&self.timestamp
|
||||||
}
|
}
|
||||||
@@ -73,24 +79,25 @@ impl FromStr for HistEntry {
|
|||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
//248972349;148;echo foo; echo bar
|
//248972349;148;echo foo; echo bar
|
||||||
let Some((timestamp, id_and_command)) = cleaned.split_once(';') else {
|
let Some((timestamp, runtime_and_cmd)) = cleaned.split_once(';') else {
|
||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
//("248972349","148;echo foo; echo bar")
|
//("248972349","148;echo foo; echo bar")
|
||||||
let Some((id, command)) = id_and_command.split_once(';') else {
|
let Some((runtime, command)) = runtime_and_cmd.split_once(';') else {
|
||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
//("148","echo foo; echo bar")
|
//("148","echo foo; echo bar")
|
||||||
let Ok(ts_seconds) = timestamp.parse::<u64>() else {
|
let Ok(ts_seconds) = timestamp.parse::<u64>() else {
|
||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
let Ok(id) = id.parse::<u32>() else {
|
let Ok(runtime) = runtime.parse::<u64>() else {
|
||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
|
let runtime = Duration::from_secs(runtime);
|
||||||
let timestamp = UNIX_EPOCH + Duration::from_secs(ts_seconds);
|
let timestamp = UNIX_EPOCH + Duration::from_secs(ts_seconds);
|
||||||
let command = command.to_string();
|
let command = command.to_string();
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
id,
|
runtime,
|
||||||
timestamp,
|
timestamp,
|
||||||
command,
|
command,
|
||||||
new: false,
|
new: false,
|
||||||
@@ -103,13 +110,14 @@ impl Display for HistEntry {
|
|||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
let command = self.with_escaped_newlines();
|
let command = self.with_escaped_newlines();
|
||||||
let HistEntry {
|
let HistEntry {
|
||||||
id,
|
runtime,
|
||||||
timestamp,
|
timestamp,
|
||||||
command: _,
|
command: _,
|
||||||
new: _,
|
new: _,
|
||||||
} = self;
|
} = self;
|
||||||
let timestamp = timestamp.duration_since(UNIX_EPOCH).unwrap().as_secs();
|
let timestamp = timestamp.duration_since(UNIX_EPOCH).unwrap().as_secs();
|
||||||
writeln!(f, ": {timestamp};{id};{command}")
|
let runtime = runtime.as_secs();
|
||||||
|
writeln!(f, ": {timestamp};{runtime};{command}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +208,7 @@ pub struct History {
|
|||||||
pub pending: Option<LineBuf>, // command, cursor_pos
|
pub pending: Option<LineBuf>, // command, cursor_pos
|
||||||
entries: Vec<HistEntry>,
|
entries: Vec<HistEntry>,
|
||||||
search_mask: Vec<HistEntry>,
|
search_mask: Vec<HistEntry>,
|
||||||
pub fuzzy_finder: FuzzySelector,
|
pub fuzzy_finder: FuzzySelector,
|
||||||
no_matches: bool,
|
no_matches: bool,
|
||||||
pub cursor: usize,
|
pub cursor: usize,
|
||||||
//search_direction: Direction,
|
//search_direction: Direction,
|
||||||
@@ -223,11 +231,15 @@ impl History {
|
|||||||
}
|
}
|
||||||
let search_mask = dedupe_entries(&entries);
|
let search_mask = dedupe_entries(&entries);
|
||||||
let cursor = search_mask.len();
|
let cursor = search_mask.len();
|
||||||
let max_size = if max_hist < 0 { None } else { Some(max_hist as u32) };
|
let max_size = if max_hist < 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(max_hist as u32)
|
||||||
|
};
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
path,
|
path,
|
||||||
entries,
|
entries,
|
||||||
fuzzy_finder: FuzzySelector::new("History").number_candidates(true),
|
fuzzy_finder: FuzzySelector::new("History").number_candidates(true),
|
||||||
pending: None,
|
pending: None,
|
||||||
search_mask,
|
search_mask,
|
||||||
no_matches: false,
|
no_matches: false,
|
||||||
@@ -238,19 +250,22 @@ impl History {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_search(&mut self, initial: &str) -> Option<String> {
|
pub fn start_search(&mut self, initial: &str) -> Option<String> {
|
||||||
if self.search_mask.is_empty() {
|
if self.search_mask.is_empty() {
|
||||||
None
|
None
|
||||||
} else if self.search_mask.len() == 1 {
|
} else if self.search_mask.len() == 1 {
|
||||||
Some(self.search_mask[0].command().to_string())
|
Some(self.search_mask[0].command().to_string())
|
||||||
} else {
|
} else {
|
||||||
self.fuzzy_finder.set_query(initial.to_string());
|
self.fuzzy_finder.set_query(initial.to_string());
|
||||||
let raw_entries = self.search_mask.clone().into_iter()
|
let raw_entries = self
|
||||||
.map(|ent| ent.command().to_string());
|
.search_mask
|
||||||
self.fuzzy_finder.activate(raw_entries.collect());
|
.clone()
|
||||||
None
|
.into_iter()
|
||||||
}
|
.map(|ent| ent.command().to_string());
|
||||||
}
|
self.fuzzy_finder.activate(raw_entries.collect());
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
self.search_mask = dedupe_entries(&self.entries);
|
self.search_mask = dedupe_entries(&self.entries);
|
||||||
@@ -301,42 +316,36 @@ impl History {
|
|||||||
pub fn last_mut(&mut self) -> Option<&mut HistEntry> {
|
pub fn last_mut(&mut self) -> Option<&mut HistEntry> {
|
||||||
self.entries.last_mut()
|
self.entries.last_mut()
|
||||||
}
|
}
|
||||||
pub fn last(&self) -> Option<&HistEntry> {
|
pub fn last(&self) -> Option<&HistEntry> {
|
||||||
self.entries.last()
|
self.entries.last()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resolve_hist_token(&self, token: &str) -> Option<String> {
|
pub fn resolve_hist_token(&self, token: &str) -> Option<String> {
|
||||||
let token = token.strip_prefix('!').unwrap_or(token).to_string();
|
let token = token.strip_prefix('!').unwrap_or(token).to_string();
|
||||||
if let Ok(num) = token.parse::<i32>() && num != 0 {
|
if let Ok(num) = token.parse::<i32>()
|
||||||
match num.cmp(&0) {
|
&& num != 0
|
||||||
Ordering::Less => {
|
{
|
||||||
if num.unsigned_abs() > self.entries.len() as u32 {
|
match num.cmp(&0) {
|
||||||
return None;
|
Ordering::Less => {
|
||||||
}
|
if num.unsigned_abs() > self.entries.len() as u32 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
let rev_idx = self.entries.len() - num.unsigned_abs() as usize;
|
let rev_idx = self.entries.len() - num.unsigned_abs() as usize;
|
||||||
self.entries.get(rev_idx)
|
self.entries.get(rev_idx).map(|e| e.command().to_string())
|
||||||
.map(|e| e.command().to_string())
|
}
|
||||||
}
|
Ordering::Greater => self
|
||||||
Ordering::Greater => {
|
.entries
|
||||||
self.entries.get(num as usize)
|
.get(num as usize)
|
||||||
.map(|e| e.command().to_string())
|
.map(|e| e.command().to_string()),
|
||||||
}
|
_ => unreachable!(),
|
||||||
_ => unreachable!()
|
}
|
||||||
}
|
} else {
|
||||||
} else {
|
let mut rev_search = self.entries.iter();
|
||||||
let mut rev_search = self.entries.iter();
|
rev_search
|
||||||
rev_search
|
.rfind(|e| e.command().starts_with(&token))
|
||||||
.rfind(|e| e.command().starts_with(&token))
|
.map(|e| e.command().to_string())
|
||||||
.map(|e| e.command().to_string())
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_new_id(&self) -> u32 {
|
|
||||||
let Some(ent) = self.entries.last() else {
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
ent.id + 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ignore_dups(&mut self, yn: bool) {
|
pub fn ignore_dups(&mut self, yn: bool) {
|
||||||
@@ -401,12 +410,12 @@ impl History {
|
|||||||
|
|
||||||
pub fn push(&mut self, command: String) {
|
pub fn push(&mut self, command: String) {
|
||||||
let timestamp = SystemTime::now();
|
let timestamp = SystemTime::now();
|
||||||
let id = self.get_new_id();
|
let runtime = read_meta(|m| m.get_time()).unwrap_or_default();
|
||||||
if self.ignore_dups && self.is_dup(&command) {
|
if self.ignore_dups && self.is_dup(&command) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.entries.push(HistEntry {
|
self.entries.push(HistEntry {
|
||||||
id,
|
runtime,
|
||||||
timestamp,
|
timestamp,
|
||||||
command,
|
command,
|
||||||
new: true,
|
new: true,
|
||||||
|
|||||||
@@ -114,10 +114,10 @@ impl KeyEvent {
|
|||||||
"Cannot convert unknown escape sequence to Vim key sequence".to_string(),
|
"Cannot convert unknown escape sequence to Vim key sequence".to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
KeyCode::ExMode => {
|
KeyCode::ExMode => {
|
||||||
seq.push_str("CMD");
|
seq.push_str("CMD");
|
||||||
needs_angle_bracket = true;
|
needs_angle_bracket = true;
|
||||||
}
|
}
|
||||||
KeyCode::Backspace => {
|
KeyCode::Backspace => {
|
||||||
seq.push_str("BS");
|
seq.push_str("BS");
|
||||||
needs_angle_bracket = true;
|
needs_angle_bracket = true;
|
||||||
@@ -227,8 +227,8 @@ pub enum KeyCode {
|
|||||||
Tab,
|
Tab,
|
||||||
Up,
|
Up,
|
||||||
|
|
||||||
// weird stuff
|
// weird stuff
|
||||||
ExMode, // keycode emitted by the <cmd> byte alias in vim keymaps
|
ExMode, // keycode emitted by the <cmd> byte alias in vim keymaps
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags::bitflags! {
|
bitflags::bitflags! {
|
||||||
|
|||||||
@@ -19,7 +19,10 @@ use crate::{
|
|||||||
},
|
},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
readline::{
|
readline::{
|
||||||
history::History, markers, register::{RegisterContent, write_register}, term::RawModeGuard
|
history::History,
|
||||||
|
markers,
|
||||||
|
register::{RegisterContent, write_register},
|
||||||
|
term::RawModeGuard,
|
||||||
},
|
},
|
||||||
state::{VarFlags, VarKind, read_shopts, write_meta, write_vars},
|
state::{VarFlags, VarKind, read_shopts, write_meta, write_vars},
|
||||||
};
|
};
|
||||||
@@ -297,13 +300,13 @@ impl ClampedUsize {
|
|||||||
let max = self.upper_bound();
|
let max = self.upper_bound();
|
||||||
self.value = (self.value + value).clamp(0, max)
|
self.value = (self.value + value).clamp(0, max)
|
||||||
}
|
}
|
||||||
pub fn add_signed(&mut self, value: isize) {
|
pub fn add_signed(&mut self, value: isize) {
|
||||||
if value.is_negative() {
|
if value.is_negative() {
|
||||||
self.sub(value.wrapping_abs() as usize);
|
self.sub(value.wrapping_abs() as usize);
|
||||||
} else {
|
} else {
|
||||||
self.add(value as usize);
|
self.add(value as usize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn sub(&mut self, value: usize) {
|
pub fn sub(&mut self, value: usize) {
|
||||||
self.value = self.value.saturating_sub(value)
|
self.value = self.value.saturating_sub(value)
|
||||||
}
|
}
|
||||||
@@ -650,10 +653,10 @@ impl LineBuf {
|
|||||||
self.buffer.push_str(slice);
|
self.buffer.push_str(slice);
|
||||||
self.update_graphemes();
|
self.update_graphemes();
|
||||||
}
|
}
|
||||||
pub fn insert_str_at_cursor(&mut self, slice: &str) {
|
pub fn insert_str_at_cursor(&mut self, slice: &str) {
|
||||||
let pos = self.index_byte_pos(self.cursor.get());
|
let pos = self.index_byte_pos(self.cursor.get());
|
||||||
self.insert_str_at(pos, slice);
|
self.insert_str_at(pos, slice);
|
||||||
}
|
}
|
||||||
pub fn insert_at_cursor(&mut self, ch: char) {
|
pub fn insert_at_cursor(&mut self, ch: char) {
|
||||||
self.insert_at(self.cursor.get(), ch);
|
self.insert_at(self.cursor.get(), ch);
|
||||||
}
|
}
|
||||||
@@ -3327,71 +3330,74 @@ impl LineBuf {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn attempt_history_expansion(&mut self, hist: &History) -> bool {
|
pub fn attempt_history_expansion(&mut self, hist: &History) -> bool {
|
||||||
self.update_graphemes();
|
self.update_graphemes();
|
||||||
let mut changes: Vec<(Range<usize>,String)> = vec![];
|
let mut changes: Vec<(Range<usize>, String)> = vec![];
|
||||||
let mut graphemes = self.buffer.grapheme_indices(true);
|
let mut graphemes = self.buffer.grapheme_indices(true);
|
||||||
let mut qt_state = QuoteState::default();
|
let mut qt_state = QuoteState::default();
|
||||||
|
|
||||||
while let Some((i,gr)) = graphemes.next() {
|
while let Some((i, gr)) = graphemes.next() {
|
||||||
match gr {
|
match gr {
|
||||||
"\\" => {
|
"\\" | "$" => {
|
||||||
graphemes.next();
|
// skip on dollars because '$!' is a shell parameter
|
||||||
}
|
graphemes.next();
|
||||||
"'" => qt_state.toggle_single(),
|
}
|
||||||
"\"" => qt_state.toggle_double(),
|
"'" => qt_state.toggle_single(),
|
||||||
"!" if !qt_state.in_single() => {
|
"\"" => qt_state.toggle_double(),
|
||||||
let start = i;
|
"!" if !qt_state.in_single() => {
|
||||||
match graphemes.next() {
|
let start = i;
|
||||||
Some((_,"!")) => {
|
match graphemes.next() {
|
||||||
// we have "!!", which expands to the previous command
|
Some((_, "!")) => {
|
||||||
if let Some(prev) = hist.last() {
|
// we have "!!", which expands to the previous command
|
||||||
let raw = prev.command();
|
if let Some(prev) = hist.last() {
|
||||||
changes.push((start..start+2, raw.to_string()));
|
let raw = prev.command();
|
||||||
}
|
changes.push((start..start + 2, raw.to_string()));
|
||||||
}
|
}
|
||||||
Some((_,"$")) => {
|
}
|
||||||
// we have "!$", which expands to the last word of the previous command
|
Some((_, "$")) => {
|
||||||
if let Some(prev) = hist.last() {
|
// we have "!$", which expands to the last word of the previous command
|
||||||
let raw = prev.command();
|
if let Some(prev) = hist.last() {
|
||||||
if let Some(last_word) = raw.split_whitespace().last() {
|
let raw = prev.command();
|
||||||
changes.push((start..start+2, last_word.to_string()));
|
if let Some(last_word) = raw.split_whitespace().last() {
|
||||||
}
|
changes.push((start..start + 2, last_word.to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some((j,gr)) if !is_whitespace(gr) => {
|
}
|
||||||
let mut end = j + gr.len();
|
Some((j, gr)) if !is_whitespace(gr) => {
|
||||||
while let Some((k, gr2)) = graphemes.next() {
|
let mut end = j + gr.len();
|
||||||
if is_whitespace(gr2) { break; }
|
while let Some((k, gr2)) = graphemes.next() {
|
||||||
end = k + gr2.len();
|
if is_whitespace(gr2) {
|
||||||
}
|
break;
|
||||||
let token = &self.buffer[j..end];
|
}
|
||||||
let cmd = hist.resolve_hist_token(token).unwrap_or(token.into());
|
end = k + gr2.len();
|
||||||
changes.push((start..end, cmd));
|
}
|
||||||
}
|
let token = &self.buffer[j..end];
|
||||||
_ => { /* not a hist expansion */ }
|
let cmd = hist.resolve_hist_token(token).unwrap_or(token.into());
|
||||||
}
|
changes.push((start..end, cmd));
|
||||||
}
|
}
|
||||||
_ => { /* carry on */ }
|
_ => { /* not a hist expansion */ }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_ => { /* carry on */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let ret = !changes.is_empty();
|
let ret = !changes.is_empty();
|
||||||
|
|
||||||
let buf_len = self.grapheme_indices().len();
|
let buf_len = self.grapheme_indices().len();
|
||||||
|
|
||||||
for (range,change) in changes.into_iter().rev() {
|
for (range, change) in changes.into_iter().rev() {
|
||||||
self.buffer.replace_range(range, &change);
|
self.buffer.replace_range(range, &change);
|
||||||
}
|
}
|
||||||
self.update_graphemes();
|
self.update_graphemes();
|
||||||
|
|
||||||
let new_len = self.grapheme_indices().len();
|
let new_len = self.grapheme_indices().len();
|
||||||
let delta = new_len as isize - buf_len as isize;
|
let delta = new_len as isize - buf_len as isize;
|
||||||
|
|
||||||
self.cursor.set_max(new_len);
|
self.cursor.set_max(new_len);
|
||||||
self.cursor.add_signed(delta);
|
self.cursor.add_signed(delta);
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
&self.buffer // FIXME: this will have to be fixed up later
|
&self.buffer // FIXME: this will have to be fixed up later
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ use crate::readline::complete::{FuzzyCompleter, SelectorResponse};
|
|||||||
use crate::readline::term::{Pos, TermReader, calc_str_width};
|
use crate::readline::term::{Pos, TermReader, calc_str_width};
|
||||||
use crate::readline::vimode::{ViEx, ViVerbatim};
|
use crate::readline::vimode::{ViEx, ViVerbatim};
|
||||||
use crate::state::{
|
use crate::state::{
|
||||||
AutoCmdKind, ShellParam, VarFlags, VarKind, read_logic, read_shopts, with_vars, write_meta, write_vars
|
AutoCmdKind, ShellParam, VarFlags, VarKind, read_logic, read_shopts, with_vars, write_meta,
|
||||||
|
write_vars,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
libsh::error::ShResult,
|
libsh::error::ShResult,
|
||||||
@@ -212,7 +213,7 @@ impl Prompt {
|
|||||||
|
|
||||||
fn refresh_now(&mut self) {
|
fn refresh_now(&mut self) {
|
||||||
let saved_status = state::get_status();
|
let saved_status = state::get_status();
|
||||||
*self = Self::new();
|
*self = Self::new();
|
||||||
state::set_status(saved_status);
|
state::set_status(saved_status);
|
||||||
self.dirty = false;
|
self.dirty = false;
|
||||||
}
|
}
|
||||||
@@ -405,49 +406,65 @@ impl ShedVi {
|
|||||||
// Process all available keys
|
// Process all available keys
|
||||||
while let Some(key) = self.reader.read_key()? {
|
while let Some(key) = self.reader.read_key()? {
|
||||||
// If completer or history search are active, delegate input to it
|
// If completer or history search are active, delegate input to it
|
||||||
if self.history.fuzzy_finder.is_active() {
|
if self.history.fuzzy_finder.is_active() {
|
||||||
self.print_line(false)?;
|
self.print_line(false)?;
|
||||||
match self.history.fuzzy_finder.handle_key(key)? {
|
match self.history.fuzzy_finder.handle_key(key)? {
|
||||||
SelectorResponse::Accept(cmd) => {
|
SelectorResponse::Accept(cmd) => {
|
||||||
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistorySelect));
|
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistorySelect));
|
||||||
|
|
||||||
self.editor.set_buffer(cmd.to_string());
|
self.editor.set_buffer(cmd.to_string());
|
||||||
self.editor.move_cursor_to_end();
|
self.editor.move_cursor_to_end();
|
||||||
self.history.update_pending_cmd((self.editor.as_str(), self.editor.cursor.get()));
|
self
|
||||||
self.editor.set_hint(None);
|
.history
|
||||||
self.history.fuzzy_finder.clear(&mut self.writer)?;
|
.update_pending_cmd((self.editor.as_str(), self.editor.cursor.get()));
|
||||||
self.history.fuzzy_finder.reset();
|
self.editor.set_hint(None);
|
||||||
|
self.history.fuzzy_finder.clear(&mut self.writer)?;
|
||||||
|
self.history.fuzzy_finder.reset();
|
||||||
|
|
||||||
with_vars([("_HIST_ENTRY".into(), cmd.clone())], || {
|
with_vars([("_HIST_ENTRY".into(), cmd.clone())], || {
|
||||||
post_cmds.exec_with(&cmd);
|
post_cmds.exec_with(&cmd);
|
||||||
});
|
});
|
||||||
|
|
||||||
write_vars(|v| v.set_var("SHED_VI_MODE", VarKind::Str(self.mode.report_mode().to_string()), VarFlags::NONE)).ok();
|
write_vars(|v| {
|
||||||
self.prompt.refresh();
|
v.set_var(
|
||||||
self.needs_redraw = true;
|
"SHED_VI_MODE",
|
||||||
continue;
|
VarKind::Str(self.mode.report_mode().to_string()),
|
||||||
}
|
VarFlags::NONE,
|
||||||
SelectorResponse::Dismiss => {
|
)
|
||||||
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistoryClose));
|
})
|
||||||
post_cmds.exec();
|
.ok();
|
||||||
|
self.prompt.refresh();
|
||||||
|
self.needs_redraw = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
SelectorResponse::Dismiss => {
|
||||||
|
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistoryClose));
|
||||||
|
post_cmds.exec();
|
||||||
|
|
||||||
self.editor.set_hint(None);
|
self.editor.set_hint(None);
|
||||||
self.history.fuzzy_finder.clear(&mut self.writer)?;
|
self.history.fuzzy_finder.clear(&mut self.writer)?;
|
||||||
write_vars(|v| v.set_var("SHED_VI_MODE", VarKind::Str(self.mode.report_mode().to_string()), VarFlags::NONE)).ok();
|
write_vars(|v| {
|
||||||
self.prompt.refresh();
|
v.set_var(
|
||||||
self.needs_redraw = true;
|
"SHED_VI_MODE",
|
||||||
continue;
|
VarKind::Str(self.mode.report_mode().to_string()),
|
||||||
}
|
VarFlags::NONE,
|
||||||
SelectorResponse::Consumed => {
|
)
|
||||||
self.needs_redraw = true;
|
})
|
||||||
continue;
|
.ok();
|
||||||
}
|
self.prompt.refresh();
|
||||||
}
|
self.needs_redraw = true;
|
||||||
} else if self.completer.is_active() {
|
continue;
|
||||||
|
}
|
||||||
|
SelectorResponse::Consumed => {
|
||||||
|
self.needs_redraw = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if self.completer.is_active() {
|
||||||
self.print_line(false)?;
|
self.print_line(false)?;
|
||||||
match self.completer.handle_key(key.clone())? {
|
match self.completer.handle_key(key.clone())? {
|
||||||
CompResponse::Accept(candidate) => {
|
CompResponse::Accept(candidate) => {
|
||||||
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnCompletionSelect));
|
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnCompletionSelect));
|
||||||
|
|
||||||
let span_start = self.completer.token_span().0;
|
let span_start = self.completer.token_span().0;
|
||||||
let new_cursor = span_start + candidate.len();
|
let new_cursor = span_start + candidate.len();
|
||||||
@@ -468,21 +485,28 @@ impl ShedVi {
|
|||||||
self.needs_redraw = true;
|
self.needs_redraw = true;
|
||||||
self.completer.reset();
|
self.completer.reset();
|
||||||
|
|
||||||
with_vars([("_COMP_CANDIDATE".into(), candidate.clone())], || {
|
with_vars([("_COMP_CANDIDATE".into(), candidate.clone())], || {
|
||||||
post_cmds.exec_with(&candidate);
|
post_cmds.exec_with(&candidate);
|
||||||
});
|
});
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
CompResponse::Dismiss => {
|
CompResponse::Dismiss => {
|
||||||
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnCompletionCancel));
|
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnCompletionCancel));
|
||||||
post_cmds.exec();
|
post_cmds.exec();
|
||||||
|
|
||||||
let hint = self.history.get_hint();
|
let hint = self.history.get_hint();
|
||||||
self.editor.set_hint(hint);
|
self.editor.set_hint(hint);
|
||||||
self.completer.clear(&mut self.writer)?;
|
self.completer.clear(&mut self.writer)?;
|
||||||
write_vars(|v| v.set_var("SHED_VI_MODE", VarKind::Str(self.mode.report_mode().to_string()), VarFlags::NONE)).ok();
|
write_vars(|v| {
|
||||||
self.prompt.refresh();
|
v.set_var(
|
||||||
|
"SHED_VI_MODE",
|
||||||
|
VarKind::Str(self.mode.report_mode().to_string()),
|
||||||
|
VarFlags::NONE,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
self.prompt.refresh();
|
||||||
self.completer.reset();
|
self.completer.reset();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -531,9 +555,16 @@ impl ShedVi {
|
|||||||
return Ok(event);
|
return Ok(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !self.completer.is_active() && !self.history.fuzzy_finder.is_active() {
|
if !self.completer.is_active() && !self.history.fuzzy_finder.is_active() {
|
||||||
write_vars(|v| v.set_var("SHED_VI_MODE", VarKind::Str(self.mode.report_mode().to_string()), VarFlags::NONE)).ok();
|
write_vars(|v| {
|
||||||
}
|
v.set_var(
|
||||||
|
"SHED_VI_MODE",
|
||||||
|
VarKind::Str(self.mode.report_mode().to_string()),
|
||||||
|
VarFlags::NONE,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
|
||||||
// Redraw if we processed any input
|
// Redraw if we processed any input
|
||||||
if self.needs_redraw {
|
if self.needs_redraw {
|
||||||
@@ -546,7 +577,10 @@ impl ShedVi {
|
|||||||
|
|
||||||
pub fn handle_key(&mut self, key: KeyEvent) -> ShResult<Option<ReadlineEvent>> {
|
pub fn handle_key(&mut self, key: KeyEvent) -> ShResult<Option<ReadlineEvent>> {
|
||||||
if self.should_accept_hint(&key) {
|
if self.should_accept_hint(&key) {
|
||||||
log::debug!("Accepting hint on key {key:?} in mode {:?}", self.mode.report_mode());
|
log::debug!(
|
||||||
|
"Accepting hint on key {key:?} in mode {:?}",
|
||||||
|
self.mode.report_mode()
|
||||||
|
);
|
||||||
self.editor.accept_hint();
|
self.editor.accept_hint();
|
||||||
if !self.history.at_pending() {
|
if !self.history.at_pending() {
|
||||||
self.history.reset_to_pending();
|
self.history.reset_to_pending();
|
||||||
@@ -559,11 +593,11 @@ impl ShedVi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let KeyEvent(KeyCode::Tab, mod_keys) = key {
|
if let KeyEvent(KeyCode::Tab, mod_keys) = key {
|
||||||
if self.editor.attempt_history_expansion(&self.history) {
|
if self.editor.attempt_history_expansion(&self.history) {
|
||||||
// If history expansion occurred, don't attempt completion yet
|
// If history expansion occurred, don't attempt completion yet
|
||||||
// allow the user to see the expanded command and accept or edit it before completing
|
// allow the user to see the expanded command and accept or edit it before completing
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let direction = match mod_keys {
|
let direction = match mod_keys {
|
||||||
ModKeys::SHIFT => -1,
|
ModKeys::SHIFT => -1,
|
||||||
@@ -579,11 +613,11 @@ impl ShedVi {
|
|||||||
self.old_layout = None;
|
self.old_layout = None;
|
||||||
}
|
}
|
||||||
Ok(Some(line)) => {
|
Ok(Some(line)) => {
|
||||||
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnCompletionSelect));
|
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnCompletionSelect));
|
||||||
let cand = self.completer.selected_candidate().unwrap_or_default();
|
let cand = self.completer.selected_candidate().unwrap_or_default();
|
||||||
with_vars([("_COMP_CANDIDATE".into(), cand.clone())], || {
|
with_vars([("_COMP_CANDIDATE".into(), cand.clone())], || {
|
||||||
post_cmds.exec_with(&cand);
|
post_cmds.exec_with(&cand);
|
||||||
});
|
});
|
||||||
|
|
||||||
let span_start = self.completer.token_span().0;
|
let span_start = self.completer.token_span().0;
|
||||||
|
|
||||||
@@ -605,19 +639,24 @@ impl ShedVi {
|
|||||||
.update_pending_cmd((self.editor.as_str(), self.editor.cursor.get()));
|
.update_pending_cmd((self.editor.as_str(), self.editor.cursor.get()));
|
||||||
let hint = self.history.get_hint();
|
let hint = self.history.get_hint();
|
||||||
self.editor.set_hint(hint);
|
self.editor.set_hint(hint);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnCompletionStart));
|
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnCompletionStart));
|
||||||
|
|
||||||
post_cmds.exec();
|
post_cmds.exec();
|
||||||
|
|
||||||
self.writer.send_bell().ok();
|
self.writer.send_bell().ok();
|
||||||
if self.completer.is_active() {
|
if self.completer.is_active() {
|
||||||
write_vars(|v| v.set_var("SHED_VI_MODE", VarKind::Str("COMPLETE".to_string()), VarFlags::NONE)).ok();
|
write_vars(|v| {
|
||||||
self.prompt.refresh();
|
v.set_var(
|
||||||
self.needs_redraw = true;
|
"SHED_VI_MODE",
|
||||||
|
VarKind::Str("COMPLETE".to_string()),
|
||||||
|
VarFlags::NONE,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
self.prompt.refresh();
|
||||||
|
self.needs_redraw = true;
|
||||||
self.editor.set_hint(None);
|
self.editor.set_hint(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -626,33 +665,42 @@ impl ShedVi {
|
|||||||
self.needs_redraw = true;
|
self.needs_redraw = true;
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
} else if let KeyEvent(KeyCode::Char('R'), ModKeys::CTRL) = key {
|
} else if let KeyEvent(KeyCode::Char('R'), ModKeys::CTRL) = key {
|
||||||
let initial = self.editor.as_str();
|
let initial = self.editor.as_str();
|
||||||
match self.history.start_search(initial) {
|
match self.history.start_search(initial) {
|
||||||
Some(entry) => {
|
Some(entry) => {
|
||||||
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistorySelect));
|
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistorySelect));
|
||||||
with_vars([("_HIST_ENTRY".into(), entry.clone())], || {
|
with_vars([("_HIST_ENTRY".into(), entry.clone())], || {
|
||||||
post_cmds.exec_with(&entry);
|
post_cmds.exec_with(&entry);
|
||||||
});
|
});
|
||||||
|
|
||||||
self.editor.set_buffer(entry);
|
self.editor.set_buffer(entry);
|
||||||
self.editor.move_cursor_to_end();
|
self.editor.move_cursor_to_end();
|
||||||
self.history.update_pending_cmd((self.editor.as_str(), self.editor.cursor.get()));
|
self
|
||||||
self.editor.set_hint(None);
|
.history
|
||||||
}
|
.update_pending_cmd((self.editor.as_str(), self.editor.cursor.get()));
|
||||||
None => {
|
self.editor.set_hint(None);
|
||||||
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistoryOpen));
|
}
|
||||||
post_cmds.exec();
|
None => {
|
||||||
|
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistoryOpen));
|
||||||
|
post_cmds.exec();
|
||||||
|
|
||||||
self.writer.send_bell().ok();
|
self.writer.send_bell().ok();
|
||||||
if self.history.fuzzy_finder.is_active() {
|
if self.history.fuzzy_finder.is_active() {
|
||||||
write_vars(|v| v.set_var("SHED_VI_MODE", VarKind::Str("SEARCH".to_string()), VarFlags::NONE)).ok();
|
write_vars(|v| {
|
||||||
self.prompt.refresh();
|
v.set_var(
|
||||||
self.needs_redraw = true;
|
"SHED_VI_MODE",
|
||||||
self.editor.set_hint(None);
|
VarKind::Str("SEARCH".to_string()),
|
||||||
}
|
VarFlags::NONE,
|
||||||
}
|
)
|
||||||
}
|
})
|
||||||
}
|
.ok();
|
||||||
|
self.prompt.refresh();
|
||||||
|
self.needs_redraw = true;
|
||||||
|
self.editor.set_hint(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let KeyEvent(KeyCode::Char('\\'), ModKeys::NONE) = key
|
if let KeyEvent(KeyCode::Char('\\'), ModKeys::NONE) = key
|
||||||
&& !self.next_is_escaped
|
&& !self.next_is_escaped
|
||||||
@@ -684,23 +732,17 @@ impl ShedVi {
|
|||||||
&& !self.editor.buffer.ends_with('\\')
|
&& !self.editor.buffer.ends_with('\\')
|
||||||
&& (self.should_submit()? || !read_shopts(|o| o.prompt.linebreak_on_incomplete))
|
&& (self.should_submit()? || !read_shopts(|o| o.prompt.linebreak_on_incomplete))
|
||||||
{
|
{
|
||||||
if self.editor.attempt_history_expansion(&self.history) {
|
if self.editor.attempt_history_expansion(&self.history) {
|
||||||
// If history expansion occurred, don't submit yet
|
// If history expansion occurred, don't submit yet
|
||||||
// allow the user to see the expanded command and accept or edit it before submitting
|
// allow the user to see the expanded command and accept or edit it before submitting
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.editor.set_hint(None);
|
self.editor.set_hint(None);
|
||||||
self.editor.cursor.set(self.editor.cursor_max());
|
self.editor.cursor.set(self.editor.cursor_max());
|
||||||
self.print_line(true)?;
|
self.print_line(true)?;
|
||||||
self.writer.flush_write("\n")?;
|
self.writer.flush_write("\n")?;
|
||||||
let buf = self.editor.take_buf();
|
let buf = self.editor.take_buf();
|
||||||
if read_shopts(|s| s.core.auto_hist) && !buf.is_empty() {
|
|
||||||
self.history.push(buf.clone());
|
|
||||||
if let Err(e) = self.history.save() {
|
|
||||||
eprintln!("Failed to save history: {e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.history.reset();
|
self.history.reset();
|
||||||
return Ok(Some(ReadlineEvent::Line(buf)));
|
return Ok(Some(ReadlineEvent::Line(buf)));
|
||||||
}
|
}
|
||||||
@@ -942,8 +984,11 @@ impl ShedVi {
|
|||||||
.set_prompt_line_context(preceding_width, new_layout.cursor.col);
|
.set_prompt_line_context(preceding_width, new_layout.cursor.col);
|
||||||
self.completer.draw(&mut self.writer)?;
|
self.completer.draw(&mut self.writer)?;
|
||||||
|
|
||||||
self.history.fuzzy_finder.set_prompt_line_context(preceding_width, new_layout.cursor.col);
|
self
|
||||||
self.history.fuzzy_finder.draw(&mut self.writer)?;
|
.history
|
||||||
|
.fuzzy_finder
|
||||||
|
.set_prompt_line_context(preceding_width, new_layout.cursor.col);
|
||||||
|
self.history.fuzzy_finder.draw(&mut self.writer)?;
|
||||||
|
|
||||||
self.old_layout = Some(new_layout);
|
self.old_layout = Some(new_layout);
|
||||||
self.needs_redraw = false;
|
self.needs_redraw = false;
|
||||||
|
|||||||
@@ -54,15 +54,13 @@ impl ViMode for ViInsert {
|
|||||||
.set_motion(MotionCmd(1, Motion::ForwardChar));
|
.set_motion(MotionCmd(1, Motion::ForwardChar));
|
||||||
self.register_and_return()
|
self.register_and_return()
|
||||||
}
|
}
|
||||||
E(K::ExMode, _) => {
|
E(K::ExMode, _) => Some(ViCmd {
|
||||||
Some(ViCmd {
|
register: Default::default(),
|
||||||
register: Default::default(),
|
verb: Some(VerbCmd(1, Verb::ExMode)),
|
||||||
verb: Some(VerbCmd(1, Verb::ExMode)),
|
motion: None,
|
||||||
motion: None,
|
raw_seq: String::new(),
|
||||||
raw_seq: String::new(),
|
flags: Default::default(),
|
||||||
flags: Default::default(),
|
}),
|
||||||
})
|
|
||||||
}
|
|
||||||
E(K::Char('W'), M::CTRL) => {
|
E(K::Char('W'), M::CTRL) => {
|
||||||
self.pending_cmd.set_verb(VerbCmd(1, Verb::Delete));
|
self.pending_cmd.set_verb(VerbCmd(1, Verb::Delete));
|
||||||
self.pending_cmd.set_motion(MotionCmd(
|
self.pending_cmd.set_motion(MotionCmd(
|
||||||
|
|||||||
@@ -756,15 +756,15 @@ impl ViMode for ViNormal {
|
|||||||
raw_seq: "".into(),
|
raw_seq: "".into(),
|
||||||
flags: self.flags(),
|
flags: self.flags(),
|
||||||
}),
|
}),
|
||||||
E(K::ExMode, _) => {
|
E(K::ExMode, _) => {
|
||||||
return Some(ViCmd {
|
return Some(ViCmd {
|
||||||
register: Default::default(),
|
register: Default::default(),
|
||||||
verb: Some(VerbCmd(1, Verb::ExMode)),
|
verb: Some(VerbCmd(1, Verb::ExMode)),
|
||||||
motion: None,
|
motion: None,
|
||||||
raw_seq: self.take_cmd(),
|
raw_seq: self.take_cmd(),
|
||||||
flags: self.flags(),
|
flags: self.flags(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
E(K::Char('A'), M::CTRL) => {
|
E(K::Char('A'), M::CTRL) => {
|
||||||
let count = self
|
let count = self
|
||||||
.parse_count(&mut self.pending_seq.chars().peekable())
|
.parse_count(&mut self.pending_seq.chars().peekable())
|
||||||
|
|||||||
@@ -41,15 +41,13 @@ impl ViMode for ViReplace {
|
|||||||
.set_motion(MotionCmd(1, Motion::ForwardChar));
|
.set_motion(MotionCmd(1, Motion::ForwardChar));
|
||||||
self.register_and_return()
|
self.register_and_return()
|
||||||
}
|
}
|
||||||
E(K::ExMode, _) => {
|
E(K::ExMode, _) => Some(ViCmd {
|
||||||
Some(ViCmd {
|
register: Default::default(),
|
||||||
register: Default::default(),
|
verb: Some(VerbCmd(1, Verb::ExMode)),
|
||||||
verb: Some(VerbCmd(1, Verb::ExMode)),
|
motion: None,
|
||||||
motion: None,
|
raw_seq: String::new(),
|
||||||
raw_seq: String::new(),
|
flags: Default::default(),
|
||||||
flags: Default::default(),
|
}),
|
||||||
})
|
|
||||||
}
|
|
||||||
E(K::Char('W'), M::CTRL) => {
|
E(K::Char('W'), M::CTRL) => {
|
||||||
self.pending_cmd.set_verb(VerbCmd(1, Verb::Delete));
|
self.pending_cmd.set_verb(VerbCmd(1, Verb::Delete));
|
||||||
self.pending_cmd.set_motion(MotionCmd(
|
self.pending_cmd.set_motion(MotionCmd(
|
||||||
|
|||||||
@@ -614,15 +614,15 @@ impl ViMode for ViVisual {
|
|||||||
raw_seq: "".into(),
|
raw_seq: "".into(),
|
||||||
flags: CmdFlags::empty(),
|
flags: CmdFlags::empty(),
|
||||||
}),
|
}),
|
||||||
E(K::ExMode, _) => {
|
E(K::ExMode, _) => {
|
||||||
return Some(ViCmd {
|
return Some(ViCmd {
|
||||||
register: Default::default(),
|
register: Default::default(),
|
||||||
verb: Some(VerbCmd(1, Verb::ExMode)),
|
verb: Some(VerbCmd(1, Verb::ExMode)),
|
||||||
motion: None,
|
motion: None,
|
||||||
raw_seq: String::new(),
|
raw_seq: String::new(),
|
||||||
flags: Default::default(),
|
flags: Default::default(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
E(K::Char('A'), M::CTRL) => {
|
E(K::Char('A'), M::CTRL) => {
|
||||||
let count = self
|
let count = self
|
||||||
.parse_count(&mut self.pending_seq.chars().peekable())
|
.parse_count(&mut self.pending_seq.chars().peekable())
|
||||||
|
|||||||
36
src/state.rs
36
src/state.rs
@@ -550,12 +550,12 @@ pub enum AutoCmdKind {
|
|||||||
PostPrompt,
|
PostPrompt,
|
||||||
PreModeChange,
|
PreModeChange,
|
||||||
PostModeChange,
|
PostModeChange,
|
||||||
OnHistoryOpen,
|
OnHistoryOpen,
|
||||||
OnHistoryClose,
|
OnHistoryClose,
|
||||||
OnHistorySelect,
|
OnHistorySelect,
|
||||||
OnCompletionStart,
|
OnCompletionStart,
|
||||||
OnCompletionCancel,
|
OnCompletionCancel,
|
||||||
OnCompletionSelect,
|
OnCompletionSelect,
|
||||||
OnExit,
|
OnExit,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -571,12 +571,12 @@ impl Display for AutoCmdKind {
|
|||||||
Self::PostPrompt => write!(f, "post-prompt"),
|
Self::PostPrompt => write!(f, "post-prompt"),
|
||||||
Self::PreModeChange => write!(f, "pre-mode-change"),
|
Self::PreModeChange => write!(f, "pre-mode-change"),
|
||||||
Self::PostModeChange => write!(f, "post-mode-change"),
|
Self::PostModeChange => write!(f, "post-mode-change"),
|
||||||
Self::OnHistoryOpen => write!(f, "on-history-open"),
|
Self::OnHistoryOpen => write!(f, "on-history-open"),
|
||||||
Self::OnHistoryClose => write!(f, "on-history-close"),
|
Self::OnHistoryClose => write!(f, "on-history-close"),
|
||||||
Self::OnHistorySelect => write!(f, "on-history-select"),
|
Self::OnHistorySelect => write!(f, "on-history-select"),
|
||||||
Self::OnCompletionStart => write!(f, "on-completion-start"),
|
Self::OnCompletionStart => write!(f, "on-completion-start"),
|
||||||
Self::OnCompletionCancel => write!(f, "on-completion-cancel"),
|
Self::OnCompletionCancel => write!(f, "on-completion-cancel"),
|
||||||
Self::OnCompletionSelect => write!(f, "on-completion-select"),
|
Self::OnCompletionSelect => write!(f, "on-completion-select"),
|
||||||
Self::OnExit => write!(f, "on-exit"),
|
Self::OnExit => write!(f, "on-exit"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -595,12 +595,12 @@ impl FromStr for AutoCmdKind {
|
|||||||
"post-prompt" => Ok(Self::PostPrompt),
|
"post-prompt" => Ok(Self::PostPrompt),
|
||||||
"pre-mode-change" => Ok(Self::PreModeChange),
|
"pre-mode-change" => Ok(Self::PreModeChange),
|
||||||
"post-mode-change" => Ok(Self::PostModeChange),
|
"post-mode-change" => Ok(Self::PostModeChange),
|
||||||
"on-history-open" => Ok(Self::OnHistoryOpen),
|
"on-history-open" => Ok(Self::OnHistoryOpen),
|
||||||
"on-history-close" => Ok(Self::OnHistoryClose),
|
"on-history-close" => Ok(Self::OnHistoryClose),
|
||||||
"on-history-select" => Ok(Self::OnHistorySelect),
|
"on-history-select" => Ok(Self::OnHistorySelect),
|
||||||
"on-completion-start" => Ok(Self::OnCompletionStart),
|
"on-completion-start" => Ok(Self::OnCompletionStart),
|
||||||
"on-completion-cancel" => Ok(Self::OnCompletionCancel),
|
"on-completion-cancel" => Ok(Self::OnCompletionCancel),
|
||||||
"on-completion-select" => Ok(Self::OnCompletionSelect),
|
"on-completion-select" => Ok(Self::OnCompletionSelect),
|
||||||
"on-exit" => Ok(Self::OnExit),
|
"on-exit" => Ok(Self::OnExit),
|
||||||
_ => Err(ShErr::simple(
|
_ => Err(ShErr::simple(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
|
|||||||
Reference in New Issue
Block a user