Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Extract history management into struct
* Encapsulates the up/down arrow browsing
* Still fully in-memory but easy to add file based constructors and
flushing
* No further integration with the statefulness of the Ctrl-R search
(yet)
  • Loading branch information
sholderbach committed Apr 10, 2021
commit a355325f2431e16b4966d1c6bf6e5cdfd8e9be95
59 changes: 10 additions & 49 deletions src/engine.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::line_buffer::LineBuffer;
use crate::Prompt;
use crate::{history::History, line_buffer::LineBuffer};
use crate::{
history_search::{BasicSearch, BasicSearchCommand},
line_buffer::InsertionPoint,
Expand All @@ -11,13 +11,11 @@ use crossterm::{
terminal::{self, Clear, ClearType},
QueueableCommand, Result,
};
use std::collections::VecDeque;
use std::{
io::{stdout, Stdout, Write},
time::Duration,
};

const HISTORY_SIZE: usize = 100;
static PROMPT_INDICATOR: &str = "〉";
const PROMPT_COLOR: Color = Color::Blue;

Expand Down Expand Up @@ -55,9 +53,7 @@ pub struct Reedline {
cut_buffer: String,

// History
history: VecDeque<String>,
history_cursor: i64,
has_history: bool,
history: History,
history_search: Option<BasicSearch>, // This could be have more features in the future (fzf, configurable?)

// Stdout
Expand All @@ -79,18 +75,14 @@ impl Default for Reedline {

impl Reedline {
pub fn new() -> Reedline {
let history = VecDeque::with_capacity(HISTORY_SIZE);
let history_cursor = -1i64;
let has_history = false;
let history = History::default();
let cut_buffer = String::new();
let stdout = stdout();

Reedline {
line_buffer: LineBuffer::new(),
cut_buffer,
history,
history_cursor,
has_history,
history_search: None,
stdout,
}
Expand Down Expand Up @@ -235,52 +227,21 @@ impl Reedline {
self.set_insertion_point(0);
}
EditCommand::AppendToHistory => {
if self.history.len() + 1 == HISTORY_SIZE {
// History is "full", so we delete the oldest entry first,
// before adding a new one.
self.history.pop_back();
}
// Don't append if the preceding value is identical
if self
.history
.front()
.map_or(true, |entry| entry.as_str() != self.insertion_line())
{
self.history.push_front(self.insertion_line().to_string());
}
self.has_history = true;
self.history.append(self.insertion_line().to_string());
// reset the history cursor - we want to start at the bottom of the
// history again.
self.history_cursor = -1;
self.history.reset_cursor();
}
EditCommand::PreviousHistory => {
if self.has_history && self.history_cursor < (self.history.len() as i64 - 1) {
self.history_cursor += 1;
let history_entry = self
.history
.get(self.history_cursor as usize)
.unwrap()
.clone();
self.set_buffer(history_entry.clone());
if let Some(history_entry) = self.history.go_back() {
let new_buffer = history_entry.to_string();
self.set_buffer(new_buffer);
self.move_to_end();
}
}
EditCommand::NextHistory => {
if self.history_cursor >= 0 {
self.history_cursor -= 1;
}
let new_buffer = if self.history_cursor < 0 {
String::new()
} else {
// We can be sure that we always have an entry on hand, that's why
// unwrap is fine.
self.history
.get(self.history_cursor as usize)
.unwrap()
.clone()
};

self.set_buffer(new_buffer.clone());
let new_buffer = self.history.go_forward().unwrap_or_default().to_string();
self.set_buffer(new_buffer);
self.move_to_end();
}
EditCommand::SearchHistory => {
Expand Down
95 changes: 95 additions & 0 deletions src/history.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use std::{collections::VecDeque, ops::Index};

/// Default size of the `History`
const HISTORY_SIZE: usize = 100;

/// Stateful history that allows up/down-arrow browsing with an internal cursor
pub struct History {
capacity: usize,
entries: VecDeque<String>,
cursor: isize, // -1 not browsing through history, >= 0 index into history
}

impl Default for History {
fn default() -> Self {
Self::new(HISTORY_SIZE)
}
}

impl History {
/// Creates an in-memory history that remembers `<= capacity` elements
pub fn new(capacity: usize) -> Self {
if capacity > isize::MAX as usize {
panic!("History capacity too large to be addressed safely");
}
History {
capacity,
entries: VecDeque::with_capacity(capacity),
cursor: -1,
}
}

/// Access the underlying entries (exported for possible fancy access to underlying VecDeque)
#[allow(dead_code)]
pub fn entries(&self) -> &VecDeque<String> {
&self.entries
}

/// Append an entry if non-empty and not repetition of the previous entry
pub fn append(&mut self, entry: String) {
if self.entries.len() + 1 == self.capacity {
// History is "full", so we delete the oldest entry first,
// before adding a new one.
self.entries.pop_back();
}
// Don't append if the preceding value is identical or the string empty
if self
.entries
.front()
.map_or(true, |previous| previous != &entry)
&& !entry.is_empty()
{
self.entries.push_front(entry);
}
}

/// Reset the internal browsing cursor
pub fn reset_cursor(&mut self) {
self.cursor = -1
}

/// Try to move back in history. Returns `None` if history is exhausted.
pub fn go_back(&mut self) -> Option<&str> {
if self.cursor < (self.entries.len() as isize - 1) {
self.cursor += 1;
Some(self.entries.get(self.cursor as usize).unwrap())
} else {
None
}
}

/// Try to move forward in history. Returns `None` if history is exhausted (moving beyond most recent element).
pub fn go_forward(&mut self) -> Option<&str> {
if self.cursor >= 0 {
self.cursor -= 1;
}
if self.cursor >= 0 {
Some(self.entries.get(self.cursor as usize).unwrap())
} else {
None
}
}

/// Yields iterator to immutable references from the underlying data structure
pub fn iter(&self) -> std::collections::vec_deque::Iter<'_, String> {
self.entries.iter()
}
}

impl Index<usize> for History {
type Output = String;

fn index(&self, index: usize) -> &Self::Output {
&self.entries[index]
}
}
4 changes: 2 additions & 2 deletions src/history_search.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::collections::VecDeque;
use crate::history::History;

#[derive(Clone)]
pub struct BasicSearch {
Expand All @@ -20,7 +20,7 @@ impl BasicSearch {
}
}

pub fn step(&mut self, command: BasicSearchCommand, history: &VecDeque<String>) {
pub fn step(&mut self, command: BasicSearchCommand, history: &History) {
let mut start = self
.result
.map(|(history_index, _)| history_index)
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
mod engine;
pub use engine::{Reedline, Signal};

mod history;

mod history_search;

mod prompt;
Expand Down