Skip to content

Commit

Permalink
Implement open mode query pinning
Browse files Browse the repository at this point in the history
  • Loading branch information
jmacdonald committed Oct 16, 2024
1 parent 597066d commit d9744f6
Show file tree
Hide file tree
Showing 12 changed files with 349 additions and 15 deletions.
1 change: 1 addition & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod cursor;
pub mod git;
pub mod jump;
pub mod line_jump;
pub mod open;
pub mod path;
pub mod preferences;
pub mod search;
Expand Down
11 changes: 11 additions & 0 deletions src/commands/open.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use crate::commands::Result;
use crate::models::application::{Application, Mode};

pub fn pin_query(app: &mut Application) -> Result {
match app.mode {
Mode::Open(ref mut mode) => mode.pin_query(),
_ => bail!("Can't pin queries outside of open mode."),
}

Ok(())
}
2 changes: 1 addition & 1 deletion src/commands/search_select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::commands::{self, application, Result};
use crate::errors::*;
use crate::input::Key;
use crate::models::application::modes::open::DisplayablePath;
use crate::models::application::modes::SearchSelectMode;
use crate::models::application::modes::{PopSearchToken, SearchSelectMode};
use crate::models::application::{Application, Mode, ModeKey};

pub fn accept(app: &mut Application) -> Result {
Expand Down
1 change: 1 addition & 0 deletions src/input/key_map/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ search_select_insert:
_: search_select::push_search_char
enter: search_select::accept
backspace: search_select::pop_search_token
tab: open::pin_query
escape: search_select::step_back
down: search_select::select_next
up: search_select::select_previous
Expand Down
2 changes: 1 addition & 1 deletion src/models/application/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ impl Application {
}
Mode::Insert => presenters::modes::insert::display(&mut self.workspace, &mut self.view),
Mode::Open(ref mut mode) => {
presenters::modes::search_select::display(&mut self.workspace, mode, &mut self.view)
presenters::modes::open::display(&mut self.workspace, mode, &mut self.view)
}
Mode::Search(ref mode) => {
presenters::modes::search::display(&mut self.workspace, mode, &mut self.view)
Expand Down
2 changes: 1 addition & 1 deletion src/models/application/modes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ pub use self::line_jump::LineJumpMode;
pub use self::open::OpenMode;
pub use self::path::PathMode;
pub use self::search::SearchMode;
pub use self::search_select::{SearchSelectConfig, SearchSelectMode};
pub use self::search_select::{PopSearchToken, SearchSelectConfig, SearchSelectMode};
pub use self::select::SelectMode;
pub use self::select_line::SelectLineMode;
pub use self::symbol_jump::SymbolJumpMode;
Expand Down
180 changes: 176 additions & 4 deletions src/models/application/modes/open/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ mod displayable_path;
pub mod exclusions;

pub use self::displayable_path::DisplayablePath;
use crate::models::application::modes::{SearchSelectConfig, SearchSelectMode};
use crate::models::application::modes::{PopSearchToken, SearchSelectConfig, SearchSelectMode};
use crate::models::application::Event;
use crate::util::SelectableVec;
use bloodhound::ExclusionPattern;
Expand All @@ -21,7 +21,8 @@ pub enum OpenModeIndex {

pub struct OpenMode {
pub insert: bool,
pub input: String,
input: String,
pinned_input: String,
index: OpenModeIndex,
pub results: SelectableVec<DisplayablePath>,
config: SearchSelectConfig,
Expand All @@ -32,6 +33,7 @@ impl OpenMode {
OpenMode {
insert: true,
input: String::new(),
pinned_input: String::new(),
index: OpenModeIndex::Indexing(path),
results: SelectableVec::new(Vec::new()),
config,
Expand Down Expand Up @@ -62,6 +64,49 @@ impl OpenMode {
let _ = events.send(Event::OpenModeIndexComplete(index));
});
}

pub fn pinned_query(&self) -> &str {
&self.pinned_input
}

pub fn pin_query(&mut self) {
// Normalize whitespace between tokens
for token in self.input.split_whitespace() {
if !self.pinned_input.is_empty() {
self.pinned_input.push(' ');
}

self.pinned_input.push_str(token);
}

self.input.truncate(0);
}

pub fn pop_search_token(&mut self) {
if self.input.is_empty() {
if self.pinned_input.is_empty() {
return;
}

// Find the last word boundary (transition to/from whitespace), using
// using fold to carry the previous character's type forward.
let mut boundary_index = 0;
self.pinned_input
.char_indices()
.fold(true, |was_whitespace, (index, c)| {
if index > 0 && c.is_whitespace() != was_whitespace {
boundary_index = index - 1;
}

c.is_whitespace()
});

self.pinned_input.truncate(boundary_index);
} else {
// Call the default implementation
PopSearchToken::pop_search_token(self);
}
}
}

impl fmt::Display for OpenMode {
Expand All @@ -76,7 +121,14 @@ impl SearchSelectMode for OpenMode {
fn search(&mut self) {
let results = if let OpenModeIndex::Complete(ref index) = self.index {
index
.find(&self.input.to_lowercase(), self.config.max_results)
.find(
&format!(
"{} {}",
self.pinned_input.to_lowercase(),
self.input.to_lowercase()
),
self.config.max_results,
)
.into_iter()
.map(|path| DisplayablePath(path.to_path_buf()))
.collect()
Expand Down Expand Up @@ -126,7 +178,7 @@ impl SearchSelectMode for OpenMode {
fn message(&mut self) -> Option<String> {
if let OpenModeIndex::Indexing(ref path) = self.index {
Some(format!("Indexing {}", path.to_string_lossy()))
} else if self.query().is_empty() {
} else if self.pinned_query().is_empty() && self.query().is_empty() {
Some(String::from("Enter a search query to start."))
} else if self.results().count() == 0 {
Some(String::from("No matching entries found."))
Expand All @@ -135,3 +187,123 @@ impl SearchSelectMode for OpenMode {
}
}
}

#[cfg(test)]
mod tests {
use super::OpenMode;
use crate::models::application::modes::{SearchSelectConfig, SearchSelectMode};
use crate::models::application::Event;
use std::env;
use std::sync::mpsc::channel;

#[test]
fn search_uses_the_query() {
let path = env::current_dir().expect("can't get current directory/path");
let config = SearchSelectConfig::default();
let mut mode = OpenMode::new(path.clone(), config.clone());
let (sender, receiver) = channel();

// Populate the index
mode.reset(path, None, sender, config);
if let Ok(Event::OpenModeIndexComplete(index)) = receiver.recv() {
mode.set_index(index);
}

mode.query().push_str("Cargo.");
mode.search();

let results: Vec<String> = mode.results().map(|r| r.to_string()).collect();
assert_eq!(results, vec!["Cargo.toml", "Cargo.lock"]);
}

#[test]
fn pin_query_transfers_content() {
let path = env::current_dir().expect("can't get current directory/path");
let config = SearchSelectConfig::default();
let mut mode = OpenMode::new(path.clone(), config.clone());

mode.query().push_str("Cargo");
mode.pin_query();

assert_eq!(mode.query(), "");
assert_eq!(mode.pinned_query(), "Cargo");
}

#[test]
fn pin_query_trims_content() {
let path = env::current_dir().expect("can't get current directory/path");
let config = SearchSelectConfig::default();
let mut mode = OpenMode::new(path.clone(), config.clone());

mode.query().push_str(" Cargo ");
mode.pin_query();

assert_eq!(mode.query(), "");
assert_eq!(mode.pinned_query(), "Cargo");
}

#[test]
fn pin_query_normalizes_whitespace() {
let path = env::current_dir().expect("can't get current directory/path");
let config = SearchSelectConfig::default();
let mut mode = OpenMode::new(path.clone(), config.clone());

mode.query().push_str("amp editor");
mode.pin_query();

assert_eq!(mode.query(), "");
assert_eq!(mode.pinned_query(), "amp editor");
}

#[test]
fn subsequent_pin_query_accumulates_content() {
let path = env::current_dir().expect("can't get current directory/path");
let config = SearchSelectConfig::default();
let mut mode = OpenMode::new(path.clone(), config.clone());

mode.query().push_str("Cargo");
mode.pin_query();
mode.query().push_str("toml");
mode.pin_query();

assert_eq!(mode.query(), "");
assert_eq!(mode.pinned_query(), "Cargo toml"); // space is intentional
}

#[test]
fn search_incorporates_pinned_query_content() {
let path = env::current_dir().expect("can't get current directory/path");
let config = SearchSelectConfig::default();
let mut mode = OpenMode::new(path.clone(), config.clone());
let (sender, receiver) = channel();

// Populate the index
mode.reset(path, None, sender, config);
if let Ok(Event::OpenModeIndexComplete(index)) = receiver.recv() {
mode.set_index(index);
}

mode.query().push_str("toml");
mode.pin_query();
mode.query().push_str("Cargo");
mode.search();

let results: Vec<String> = mode.results().map(|r| r.to_string()).collect();
assert_eq!(results, vec!["Cargo.toml"]);
}

#[test]
fn pop_search_token_eats_into_pinned_query_when_query_is_empty() {
let path = env::current_dir().expect("can't get current directory/path");
let config = SearchSelectConfig::default();
let mut mode = OpenMode::new(path.clone(), config.clone());

mode.query().push_str("two tokens");
mode.pin_query();
mode.pop_search_token();

assert_eq!(mode.pinned_query(), "two");
mode.pop_search_token();
assert_eq!(mode.pinned_query(), "");
}
}
7 changes: 6 additions & 1 deletion src/models/application/modes/search_select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ pub trait SearchSelectMode: Display {
fn push_search_char(&mut self, c: char) {
self.query().push(c);
}
}

pub trait PopSearchToken: SearchSelectMode {
fn pop_search_token(&mut self) {
let query = self.query();

Expand All @@ -61,9 +63,12 @@ pub trait SearchSelectMode: Display {
}
}

// SearchSelectMode gives PopSearchToken for free
impl<T: SearchSelectMode + Display> PopSearchToken for T {}

#[cfg(test)]
mod tests {
use super::{SearchSelectConfig, SearchSelectMode};
use super::{PopSearchToken, SearchSelectConfig, SearchSelectMode};
use std::fmt;
use std::slice::Iter;

Expand Down
1 change: 1 addition & 0 deletions src/presenters/modes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub mod insert;
pub mod jump;
pub mod line_jump;
pub mod normal;
pub mod open;
pub mod path;
pub mod search;
pub mod search_select;
Expand Down
Loading

0 comments on commit d9744f6

Please sign in to comment.