Various bugfixes

This commit is contained in:
2026-01-29 19:47:12 -05:00
parent 8933153c2b
commit d04dd4bc1e
7 changed files with 54 additions and 13 deletions

View File

@@ -1,5 +1,6 @@
pub mod highlight; pub mod highlight;
pub mod readline; pub mod readline;
pub mod statusline;
use readline::{FernVi, Readline}; use readline::{FernVi, Readline};

View File

@@ -1,4 +1,5 @@
use std::{ use std::{
collections::HashSet,
env, env,
fmt::{Display, Write}, fmt::{Display, Write},
fs::{self, OpenOptions}, fs::{self, OpenOptions},
@@ -194,6 +195,22 @@ fn read_hist_file(path: &Path) -> ShResult<Vec<HistEntry>> {
Ok(raw.parse::<HistEntries>()?.0) Ok(raw.parse::<HistEntries>()?.0)
} }
/// Deduplicate entries, keeping only the most recent occurrence of each command.
/// Preserves chronological order (oldest to newest).
fn dedupe_entries(entries: &[HistEntry]) -> Vec<HistEntry> {
let mut seen = HashSet::new();
// Iterate backwards (newest first), keeping first occurrence of each command
entries
.iter()
.rev()
.filter(|ent| seen.insert(ent.command.clone()))
.cloned()
.collect::<Vec<_>>()
.into_iter()
.rev() // Restore chronological order
.collect()
}
pub struct History { pub struct History {
path: PathBuf, path: PathBuf,
entries: Vec<HistEntry>, entries: Vec<HistEntry>,
@@ -222,7 +239,7 @@ impl History {
new: true, new: true,
}) })
} }
let search_mask = entries.clone(); let search_mask = dedupe_entries(&entries);
let cursor = entries.len() - 1; let cursor = entries.len() - 1;
let mut new = Self { let mut new = Self {
path, path,
@@ -288,15 +305,16 @@ impl History {
match kind { match kind {
SearchKind::Prefix => { SearchKind::Prefix => {
if term.is_empty() { if term.is_empty() {
self.search_mask = self.entries.clone(); self.search_mask = dedupe_entries(&self.entries);
} else { } else {
let filtered = self let filtered: Vec<_> = self
.entries .entries
.clone() .iter()
.into_iter() .filter(|ent| ent.command().starts_with(&term))
.filter(|ent| ent.command().starts_with(&term)); .cloned()
.collect();
self.search_mask = filtered.collect(); self.search_mask = dedupe_entries(&filtered);
} }
self.cursor = self.search_mask.len().saturating_sub(1); self.cursor = self.search_mask.len().saturating_sub(1);
} }

View File

@@ -362,6 +362,8 @@ impl LineBuf {
let hint = hint.strip_prefix(&self.buffer).unwrap(); // If this ever panics, I will eat my hat let hint = hint.strip_prefix(&self.buffer).unwrap(); // If this ever panics, I will eat my hat
if !hint.is_empty() { if !hint.is_empty() {
self.hint = Some(hint.to_string()) self.hint = Some(hint.to_string())
} else {
self.hint = None
} }
} else { } else {
self.hint = None self.hint = None

View File

@@ -57,7 +57,6 @@ impl Readline for FernVi {
Err(_) | Ok(None) => { Err(_) | Ok(None) => {
flog!(DEBUG, "EOF detected"); flog!(DEBUG, "EOF detected");
raw_mode_guard.disable_for(|| self.writer.flush_write("\n"))?; raw_mode_guard.disable_for(|| self.writer.flush_write("\n"))?;
std::mem::drop(raw_mode_guard);
return Err(ShErr::simple(ShErrKind::ReadlineErr, "EOF")); return Err(ShErr::simple(ShErrKind::ReadlineErr, "EOF"));
} }
@@ -68,7 +67,6 @@ impl Readline for FernVi {
if self.should_accept_hint(&key) { if self.should_accept_hint(&key) {
self.editor.accept_hint(); self.editor.accept_hint();
self.history.update_pending_cmd(self.editor.as_str()); self.history.update_pending_cmd(self.editor.as_str());
self.print_line()?;
continue; continue;
} }
@@ -81,19 +79,22 @@ impl Readline for FernVi {
if self.should_grab_history(&cmd) { if self.should_grab_history(&cmd) {
self.scroll_history(cmd); self.scroll_history(cmd);
self.print_line()?;
continue; continue;
} }
if cmd.should_submit() { if cmd.should_submit() {
raw_mode_guard.disable_for(|| self.writer.flush_write("\n"))?; raw_mode_guard.disable_for(|| self.writer.flush_write("\n"))?;
std::mem::drop(raw_mode_guard); let buf = self.editor.take_buf();
return Ok(self.editor.take_buf()); // Save command to history
self.history.push(buf.clone());
if let Err(e) = self.history.save() {
eprintln!("Failed to save history: {e}");
}
return Ok(buf);
} }
if cmd.verb().is_some_and(|v| v.1 == Verb::EndOfFile) { if cmd.verb().is_some_and(|v| v.1 == Verb::EndOfFile) {
if self.editor.buffer.is_empty() { if self.editor.buffer.is_empty() {
std::mem::drop(raw_mode_guard);
return Err(ShErr::simple(ShErrKind::CleanExit(0), "exit")); return Err(ShErr::simple(ShErrKind::CleanExit(0), "exit"));
} else { } else {
self.editor.buffer.clear(); self.editor.buffer.clear();

View File

@@ -36,6 +36,9 @@ pub fn raw_mode() -> RawModeGuard {
&raw, &raw,
) )
.expect("Failed to set terminal to raw mode"); .expect("Failed to set terminal to raw mode");
let (cols, rows) = get_win_size(STDIN_FILENO);
RawModeGuard { RawModeGuard {
orig, orig,
fd: STDIN_FILENO, fd: STDIN_FILENO,

1
src/prompt/statusline.rs Normal file
View File

@@ -0,0 +1 @@

View File

@@ -103,6 +103,11 @@ pub fn sig_setup() {
flags, flags,
SigSet::empty(), SigSet::empty(),
), ),
SigAction::new(
SigHandler::Handler(handle_sigwinch),
flags,
SigSet::empty(),
),
]; ];
@@ -114,9 +119,19 @@ pub fn sig_setup() {
sigaction(Signal::SIGINT, &actions[4]).unwrap(); sigaction(Signal::SIGINT, &actions[4]).unwrap();
sigaction(Signal::SIGTTIN, &actions[5]).unwrap(); sigaction(Signal::SIGTTIN, &actions[5]).unwrap();
sigaction(Signal::SIGTTOU, &actions[6]).unwrap(); sigaction(Signal::SIGTTOU, &actions[6]).unwrap();
sigaction(Signal::SIGWINCH, &actions[7]).unwrap();
} }
} }
extern "C" fn handle_sigwinch(_: libc::c_int) {
/* do nothing
* this exists for the sole purpose of interrupting readline
* readline will be refreshed after the interruption,
* which will cause window size calculations to be re-run
* and we get window resize handling for free as a result
*/
}
extern "C" fn handle_sighup(_: libc::c_int) { extern "C" fn handle_sighup(_: libc::c_int) {
GOT_SIGHUP.store(true, Ordering::SeqCst); GOT_SIGHUP.store(true, Ordering::SeqCst);
SHOULD_QUIT.store(true, Ordering::SeqCst); SHOULD_QUIT.store(true, Ordering::SeqCst);