Fixed some weirdness in the logic for scrolling through command history
This commit is contained in:
@@ -207,9 +207,11 @@ fn dedupe_entries(entries: &[HistEntry]) -> Vec<HistEntry> {
|
||||
|
||||
pub struct History {
|
||||
path: PathBuf,
|
||||
pub pending: Option<String>,
|
||||
entries: Vec<HistEntry>,
|
||||
search_mask: Vec<HistEntry>,
|
||||
cursor: usize,
|
||||
no_matches: bool,
|
||||
pub cursor: usize,
|
||||
search_direction: Direction,
|
||||
ignore_dups: bool,
|
||||
max_size: Option<u32>,
|
||||
@@ -228,20 +230,14 @@ impl History {
|
||||
if entries.len() > max_hist {
|
||||
entries = entries.split_off(entries.len() - max_hist);
|
||||
}
|
||||
// Create pending entry for current input
|
||||
let id = entries.last().map(|ent| ent.id + 1).unwrap_or(0);
|
||||
entries.push(HistEntry {
|
||||
id,
|
||||
timestamp: SystemTime::now(),
|
||||
command: String::new(),
|
||||
new: true,
|
||||
});
|
||||
let search_mask = dedupe_entries(&entries);
|
||||
let cursor = search_mask.len().saturating_sub(1);
|
||||
let cursor = search_mask.len();
|
||||
Ok(Self {
|
||||
path,
|
||||
entries,
|
||||
pending: None,
|
||||
search_mask,
|
||||
no_matches: false,
|
||||
cursor,
|
||||
search_direction: Direction::Backward,
|
||||
ignore_dups,
|
||||
@@ -251,7 +247,7 @@ impl History {
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.search_mask = dedupe_entries(&self.entries);
|
||||
self.cursor = self.search_mask.len().saturating_sub(1);
|
||||
self.cursor = self.search_mask.len();
|
||||
}
|
||||
|
||||
pub fn entries(&self) -> &[HistEntry] {
|
||||
@@ -262,30 +258,26 @@ impl History {
|
||||
&self.search_mask
|
||||
}
|
||||
|
||||
pub fn push_empty_entry(&mut self) {
|
||||
let timestamp = SystemTime::now();
|
||||
let id = self.get_new_id();
|
||||
self.entries.push(HistEntry {
|
||||
id,
|
||||
timestamp,
|
||||
command: String::new(),
|
||||
new: true,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn cursor_entry(&self) -> Option<&HistEntry> {
|
||||
self.search_mask.get(self.cursor)
|
||||
}
|
||||
|
||||
pub fn at_pending(&self) -> bool {
|
||||
self.cursor >= self.search_mask.len()
|
||||
}
|
||||
|
||||
pub fn reset_to_pending(&mut self) {
|
||||
self.cursor = self.search_mask.len();
|
||||
}
|
||||
|
||||
pub fn update_pending_cmd(&mut self, command: &str) {
|
||||
let Some(ent) = self.last_mut() else { return };
|
||||
let cmd = command.to_string();
|
||||
let constraint = SearchConstraint {
|
||||
kind: SearchKind::Prefix,
|
||||
term: cmd.clone(),
|
||||
};
|
||||
|
||||
ent.command = cmd;
|
||||
self.pending = Some(cmd);
|
||||
self.constrain_entries(constraint);
|
||||
}
|
||||
|
||||
@@ -323,25 +315,26 @@ impl History {
|
||||
.collect();
|
||||
|
||||
self.search_mask = dedupe_entries(&filtered);
|
||||
self.no_matches = self.search_mask.is_empty();
|
||||
if self.no_matches {
|
||||
// If no matches, reset to full history so user can still scroll through it
|
||||
self.search_mask = dedupe_entries(&self.entries);
|
||||
}
|
||||
}
|
||||
self.cursor = self.search_mask.len().saturating_sub(1);
|
||||
self.cursor = self.search_mask.len();
|
||||
}
|
||||
SearchKind::Fuzzy => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hint_entry(&self) -> Option<&HistEntry> {
|
||||
let second_to_last = self.search_mask.len().checked_sub(2)?;
|
||||
self.search_mask.get(second_to_last)
|
||||
if self.no_matches { return None };
|
||||
self.search_mask.last()
|
||||
}
|
||||
|
||||
pub fn get_hint(&self) -> Option<String> {
|
||||
if self
|
||||
.cursor_entry()
|
||||
.is_some_and(|ent| ent.is_new() && !ent.command().is_empty())
|
||||
{
|
||||
if self.at_pending() && self.pending.as_ref().is_some_and(|p| !p.is_empty()) {
|
||||
let entry = self.hint_entry()?;
|
||||
let prefix = self.cursor_entry()?.command();
|
||||
Some(entry.command().to_string())
|
||||
} else {
|
||||
None
|
||||
@@ -349,15 +342,10 @@ impl History {
|
||||
}
|
||||
|
||||
pub fn scroll(&mut self, offset: isize) -> Option<&HistEntry> {
|
||||
let new_idx = self
|
||||
.cursor
|
||||
.saturating_add_signed(offset)
|
||||
.clamp(0, self.search_mask.len().saturating_sub(1));
|
||||
let ent = self.search_mask.get(new_idx)?;
|
||||
self.cursor = self.cursor.saturating_add_signed(offset).clamp(0, self.search_mask.len());
|
||||
|
||||
self.cursor = new_idx;
|
||||
|
||||
Some(ent)
|
||||
log::debug!("Scrolling history by offset {offset} from cursor at index {}", self.cursor);
|
||||
self.search_mask.get(self.cursor)
|
||||
}
|
||||
|
||||
pub fn push(&mut self, command: String) {
|
||||
@@ -411,8 +399,8 @@ impl History {
|
||||
}
|
||||
|
||||
file.write_all(data.as_bytes())?;
|
||||
self.push_empty_entry(); // Prepare for next command
|
||||
self.reset(); // Reset search mask to include new pending entry
|
||||
self.pending = None;
|
||||
self.reset();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -359,12 +359,13 @@ impl LineBuf {
|
||||
}
|
||||
pub fn set_hint(&mut self, hint: Option<String>) {
|
||||
if let Some(hint) = hint {
|
||||
let hint = hint.strip_prefix(&self.buffer).unwrap(); // If this ever panics, I will eat my hat
|
||||
if !hint.is_empty() {
|
||||
self.hint = Some(hint.to_string())
|
||||
} else {
|
||||
self.hint = None
|
||||
}
|
||||
if let Some(hint) = hint.strip_prefix(&self.buffer) {
|
||||
if !hint.is_empty() {
|
||||
self.hint = Some(hint.to_string())
|
||||
} else {
|
||||
self.hint = None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.hint = None
|
||||
}
|
||||
|
||||
@@ -170,6 +170,8 @@ impl FernVi {
|
||||
self.mode = Box::new(ViInsert::new());
|
||||
self.old_layout = None;
|
||||
self.needs_redraw = true;
|
||||
self.history.pending = None;
|
||||
self.history.reset();
|
||||
}
|
||||
|
||||
/// Process any available input and return readline event
|
||||
@@ -186,6 +188,9 @@ impl FernVi {
|
||||
|
||||
if self.should_accept_hint(&key) {
|
||||
self.editor.accept_hint();
|
||||
if !self.history.at_pending() {
|
||||
self.history.reset_to_pending();
|
||||
}
|
||||
self.history.update_pending_cmd(self.editor.as_str());
|
||||
self.needs_redraw = true;
|
||||
continue;
|
||||
@@ -207,6 +212,9 @@ impl FernVi {
|
||||
self.editor.set_buffer(line);
|
||||
self.editor.cursor.set(new_cursor);
|
||||
|
||||
if !self.history.at_pending() {
|
||||
self.history.reset_to_pending();
|
||||
}
|
||||
self.history.update_pending_cmd(self.editor.as_str());
|
||||
let hint = self.history.get_hint();
|
||||
self.editor.set_hint(hint);
|
||||
@@ -244,12 +252,14 @@ impl FernVi {
|
||||
self.writer.flush_write("\n")?;
|
||||
let buf = self.editor.take_buf();
|
||||
// Save command to history if auto_hist is enabled
|
||||
if crate::state::read_shopts(|s| s.core.auto_hist) {
|
||||
self.history.push(buf.clone());
|
||||
if let Err(e) = self.history.save() {
|
||||
eprintln!("Failed to save history: {e}");
|
||||
}
|
||||
}
|
||||
if crate::state::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();
|
||||
return Ok(ReadlineEvent::Line(buf));
|
||||
}
|
||||
|
||||
@@ -300,39 +310,30 @@ impl FernVi {
|
||||
*/
|
||||
let count = &cmd.motion().unwrap().0;
|
||||
let motion = &cmd.motion().unwrap().1;
|
||||
let entry = match motion {
|
||||
let count = match motion {
|
||||
Motion::LineUpCharwise => {
|
||||
let Some(hist_entry) = self.history.scroll(-(*count as isize)) else {
|
||||
return;
|
||||
};
|
||||
hist_entry
|
||||
-(*count as isize)
|
||||
}
|
||||
Motion::LineDownCharwise => {
|
||||
let Some(hist_entry) = self.history.scroll(*count as isize) else {
|
||||
return;
|
||||
};
|
||||
hist_entry
|
||||
*count as isize
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let col = self.editor.saved_col.unwrap_or(self.editor.cursor_col());
|
||||
let mut buf = LineBuf::new().with_initial(entry.command(), 0);
|
||||
let line_end = buf.end_of_line();
|
||||
if let Some(dest) = self.mode.hist_scroll_start_pos() {
|
||||
match dest {
|
||||
To::Start => { /* Already at 0 */ }
|
||||
To::End => {
|
||||
// History entries cannot be empty
|
||||
// So this subtraction is safe (maybe)
|
||||
buf.cursor.add(line_end);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let target = (col).min(line_end);
|
||||
buf.cursor.add(target);
|
||||
}
|
||||
|
||||
self.editor = buf
|
||||
let entry = self.history.scroll(count);
|
||||
log::info!("Scrolled history, got entry: {:?}", entry.as_ref());
|
||||
if let Some(entry) = entry {
|
||||
log::info!("Setting buffer to history entry: {}", entry.command());
|
||||
let pending = self.editor.take_buf();
|
||||
self.editor.set_buffer(entry.command().to_string());
|
||||
if self.history.pending.is_none() {
|
||||
self.history.pending = Some(pending);
|
||||
}
|
||||
self.editor.set_hint(None);
|
||||
} else if let Some(pending) = self.history.pending.take() {
|
||||
log::info!("Setting buffer to pending command: {}", &pending);
|
||||
self.editor.set_buffer(pending);
|
||||
self.editor.set_hint(None);
|
||||
}
|
||||
}
|
||||
pub fn should_accept_hint(&self, event: &KeyEvent) -> bool {
|
||||
if self.editor.cursor_at_max() && self.editor.has_hint() {
|
||||
@@ -361,8 +362,7 @@ impl FernVi {
|
||||
|| (cmd
|
||||
.motion()
|
||||
.is_some_and(|m| matches!(m, MotionCmd(_, Motion::LineDownCharwise)))
|
||||
&& self.editor.end_of_line() == self.editor.cursor_max()
|
||||
&& !self.history.cursor_entry().is_some_and(|ent| ent.is_new()))
|
||||
&& self.editor.end_of_line() == self.editor.cursor_max())
|
||||
}
|
||||
|
||||
pub fn line_text(&mut self) -> String {
|
||||
|
||||
Reference in New Issue
Block a user