Various bugfixes
This commit is contained in:
@@ -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};
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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
1
src/prompt/statusline.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user