diff --git a/src/commands/application.rs b/src/commands/application.rs index a6a22251..cc8ebc6a 100644 --- a/src/commands/application.rs +++ b/src/commands/application.rs @@ -1,11 +1,9 @@ use crate::commands::{self, Result}; use crate::errors::*; use crate::input::KeyMap; -use crate::models::application::modes::*; -use crate::models::application::{Application, Mode}; +use crate::models::application::{Application, Mode, ModeKey}; use crate::util; use scribe::Buffer; -use std::mem; pub fn handle_input(app: &mut Application) -> Result { // Listen for and respond to user input. @@ -26,7 +24,7 @@ pub fn handle_input(app: &mut Application) -> Result { pub fn switch_to_normal_mode(app: &mut Application) -> Result { let _ = commands::buffer::end_command_group(app); - app.mode = Mode::Normal; + app.switch_to(ModeKey::Normal); Ok(()) } @@ -34,7 +32,7 @@ pub fn switch_to_normal_mode(app: &mut Application) -> Result { pub fn switch_to_insert_mode(app: &mut Application) -> Result { if app.workspace.current_buffer.is_some() { commands::buffer::start_command_group(app)?; - app.mode = Mode::Insert; + app.switch_to(ModeKey::Insert); commands::view::scroll_to_cursor(app)?; } else { bail!(BUFFER_MISSING); @@ -44,34 +42,18 @@ pub fn switch_to_insert_mode(app: &mut Application) -> Result { } pub fn switch_to_jump_mode(app: &mut Application) -> Result { - let buffer = app + let line = app .workspace .current_buffer .as_ref() - .ok_or(BUFFER_MISSING)?; - - // Initialize a new jump mode and swap - // it with the current application mode. - let jump_mode = Mode::Jump(JumpMode::new(buffer.cursor.line)); - let old_mode = mem::replace(&mut app.mode, jump_mode); - - // If we were previously in a select mode, store it - // in the current jump mode so that we can return to - // it after we've jumped to a location. This is how - // we compose select and jump modes. - match old_mode { - Mode::Select(select_mode) => { - if let Mode::Jump(ref mut mode) = app.mode { - mode.select_mode = jump::SelectModeOptions::Select(select_mode); - } - } - Mode::SelectLine(select_mode) => { - if let Mode::Jump(ref mut mode) = app.mode { - mode.select_mode = jump::SelectModeOptions::SelectLine(select_mode); - } - } - _ => (), - }; + .ok_or(BUFFER_MISSING)? + .cursor + .line; + + app.switch_to(ModeKey::Jump); + if let Mode::Jump(ref mut mode) = app.mode { + mode.reset(line) + } Ok(()) } @@ -81,7 +63,7 @@ pub fn switch_to_second_stage_jump_mode(app: &mut Application) -> Result { if let Mode::Jump(ref mut mode) = app.mode { mode.first_phase = false; } else { - bail!("Failed to switch to jump mode."); + bail!("Cannot enter second stage jump mode from other modes."); }; Ok(()) @@ -89,7 +71,10 @@ pub fn switch_to_second_stage_jump_mode(app: &mut Application) -> Result { pub fn switch_to_line_jump_mode(app: &mut Application) -> Result { if app.workspace.current_buffer.is_some() { - app.mode = Mode::LineJump(LineJumpMode::new()); + app.switch_to(ModeKey::LineJump); + if let Mode::LineJump(ref mut mode) = app.mode { + mode.reset(); + } } else { bail!(BUFFER_MISSING); } @@ -100,12 +85,17 @@ pub fn switch_to_line_jump_mode(app: &mut Application) -> Result { pub fn switch_to_open_mode(app: &mut Application) -> Result { let exclusions = app.preferences.borrow().open_mode_exclusions()?; let config = app.preferences.borrow().search_select_config(); - app.mode = Mode::Open(OpenMode::new( - app.workspace.path.clone(), - exclusions, - app.event_channel.clone(), - config, - )); + + app.switch_to(ModeKey::Open); + if let Mode::Open(ref mut mode) = app.mode { + mode.reset( + app.workspace.path.clone(), + exclusions, + app.event_channel.clone(), + config, + ); + } + commands::search_select::search(app)?; Ok(()) @@ -113,20 +103,30 @@ pub fn switch_to_open_mode(app: &mut Application) -> Result { pub fn switch_to_command_mode(app: &mut Application) -> Result { let config = app.preferences.borrow().search_select_config(); - app.mode = Mode::Command(CommandMode::new(config)); + + app.switch_to(ModeKey::Command); + if let Mode::Command(ref mut mode) = app.mode { + mode.reset(config) + } + commands::search_select::search(app)?; Ok(()) } pub fn switch_to_symbol_jump_mode(app: &mut Application) -> Result { + app.switch_to(ModeKey::SymbolJump); + let token_set = app .workspace .current_buffer_tokens() .chain_err(|| BUFFER_TOKENS_FAILED)?; let config = app.preferences.borrow().search_select_config(); - app.mode = Mode::SymbolJump(SymbolJumpMode::new(&token_set, config)?); + match app.mode { + Mode::SymbolJump(ref mut mode) => mode.reset(&token_set, config), + _ => Ok(()), + }?; commands::search_select::search(app)?; @@ -134,36 +134,53 @@ pub fn switch_to_symbol_jump_mode(app: &mut Application) -> Result { } pub fn switch_to_theme_mode(app: &mut Application) -> Result { + let themes = app + .view + .theme_set + .themes + .keys() + .map(|k| k.to_string()) + .collect(); let config = app.preferences.borrow().search_select_config(); - app.mode = Mode::Theme(ThemeMode::new( - app.view - .theme_set - .themes - .keys() - .map(|k| k.to_string()) - .collect(), - config, - )); + + app.switch_to(ModeKey::Theme); + if let Mode::Theme(ref mut mode) = app.mode { + mode.reset(themes, config) + } + commands::search_select::search(app)?; Ok(()) } pub fn switch_to_select_mode(app: &mut Application) -> Result { - if let Some(buffer) = app.workspace.current_buffer.as_ref() { - app.mode = Mode::Select(SelectMode::new(*buffer.cursor.clone())); - } else { - bail!(BUFFER_MISSING); + let position = *app + .workspace + .current_buffer + .as_ref() + .ok_or(BUFFER_MISSING)? + .cursor; + + app.switch_to(ModeKey::Select); + if let Mode::Select(ref mut mode) = app.mode { + mode.reset(position); } Ok(()) } pub fn switch_to_select_line_mode(app: &mut Application) -> Result { - if let Some(buffer) = app.workspace.current_buffer.as_ref() { - app.mode = Mode::SelectLine(SelectLineMode::new(buffer.cursor.line)); - } else { - bail!(BUFFER_MISSING); + let line = app + .workspace + .current_buffer + .as_ref() + .ok_or(BUFFER_MISSING)? + .cursor + .line; + + app.switch_to(ModeKey::SelectLine); + if let Mode::SelectLine(ref mut mode) = app.mode { + mode.reset(line); } Ok(()) @@ -171,7 +188,7 @@ pub fn switch_to_select_line_mode(app: &mut Application) -> Result { pub fn switch_to_search_mode(app: &mut Application) -> Result { if app.workspace.current_buffer.is_some() { - app.mode = Mode::Search(SearchMode::new(app.search_query.clone())); + app.switch_to(ModeKey::Search); } else { bail!(BUFFER_MISSING); } @@ -193,7 +210,11 @@ pub fn switch_to_path_mode(app: &mut Application) -> Result { .unwrap_or_else(|| // Default to the workspace directory. format!("{}/", app.workspace.path.to_string_lossy())); - app.mode = Mode::Path(PathMode::new(path)); + + app.switch_to(ModeKey::Path); + if let Mode::Path(ref mut mode) = app.mode { + mode.reset(path) + } Ok(()) } @@ -207,16 +228,19 @@ pub fn switch_to_syntax_mode(app: &mut Application) -> Result { .as_ref() .ok_or("Switching syntaxes requires an open buffer")?; + app.switch_to(ModeKey::Syntax); let config = app.preferences.borrow().search_select_config(); - app.mode = Mode::Syntax(SyntaxMode::new( - app.workspace - .syntax_set - .syntaxes() - .iter() - .map(|syntax| syntax.name.clone()) - .collect(), - config, - )); + let syntaxes = app + .workspace + .syntax_set + .syntaxes() + .iter() + .map(|syntax| syntax.name.clone()) + .collect(); + if let Mode::Syntax(ref mut mode) = app.mode { + mode.reset(syntaxes, config) + } + commands::search_select::search(app)?; Ok(()) @@ -282,7 +306,7 @@ pub fn suspend(app: &mut Application) -> Result { } pub fn exit(app: &mut Application) -> Result { - app.mode = Mode::Exit; + app.switch_to(ModeKey::Exit); Ok(()) } @@ -316,24 +340,6 @@ mod tests { assert_eq!(lines.last(), Some("workspace::next_buffer")); } - #[test] - fn switch_to_search_mode_sets_initial_search_query() { - let mut app = Application::new(&Vec::new()).unwrap(); - - // A buffer needs to be open to switch to search mode. - let buffer = Buffer::new(); - app.workspace.add_buffer(buffer); - - app.search_query = Some(String::from("query")); - super::switch_to_search_mode(&mut app).unwrap(); - - let mode_query = match app.mode { - Mode::Search(ref mode) => mode.input.clone(), - _ => None, - }; - assert_eq!(mode_query, Some(String::from("query"))); - } - #[test] fn switch_to_path_mode_inserts_workspace_directory_as_default() { let mut app = Application::new(&Vec::new()).unwrap(); diff --git a/src/commands/buffer.rs b/src/commands/buffer.rs index 7f0f5dfd..8836dbe7 100644 --- a/src/commands/buffer.rs +++ b/src/commands/buffer.rs @@ -1,8 +1,7 @@ use crate::commands::{self, Result}; use crate::errors::*; use crate::input::Key; -use crate::models::application::modes::ConfirmMode; -use crate::models::application::{Application, ClipboardContent, Mode}; +use crate::models::application::{Application, ClipboardContent, Mode, ModeKey}; use crate::util; use crate::util::token::{adjacent_token_position, Direction}; use scribe::buffer::{Buffer, Position, Range, Token}; @@ -204,8 +203,10 @@ pub fn close(app: &mut Application) -> Result { app.workspace.close_current_buffer(); } else { // Display a confirmation prompt before closing a modified buffer. - let confirm_mode = ConfirmMode::new(close); - app.mode = Mode::Confirm(confirm_mode); + app.switch_to(ModeKey::Confirm); + if let Mode::Confirm(ref mut mode) = app.mode { + mode.command = close + } } Ok(()) @@ -249,8 +250,11 @@ pub fn close_others(app: &mut Application) -> Result { if modified_buffer { // Display a confirmation prompt before closing a modified buffer. - let confirm_mode = ConfirmMode::new(close_others_confirm); - app.mode = Mode::Confirm(confirm_mode); + app.switch_to(ModeKey::Confirm); + if let Mode::Confirm(ref mut mode) = app.mode { + mode.command = close_others_confirm + } + break; } diff --git a/src/commands/jump.rs b/src/commands/jump.rs index ab4b26d9..4450a7ec 100644 --- a/src/commands/jump.rs +++ b/src/commands/jump.rs @@ -1,11 +1,9 @@ use crate::commands::Result; use crate::errors::*; use crate::input::Key; -use crate::models::application::modes::jump; use crate::models::application::modes::JumpMode; use crate::models::application::{Application, Mode}; use scribe::Workspace; -use std::mem; pub fn match_tag(app: &mut Application) -> Result { let result = if let Mode::Jump(ref mut jump_mode) = app.mode { @@ -23,7 +21,7 @@ pub fn match_tag(app: &mut Application) -> Result { } else { bail!("Can't match jump tags outside of jump mode."); }; - switch_to_previous_mode(app); + app.switch_to_previous_mode(); result } @@ -45,24 +43,6 @@ fn jump_to_tag(jump_mode: &mut JumpMode, workspace: &mut Workspace) -> Result { Ok(()) } -fn switch_to_previous_mode(app: &mut Application) { - let old_mode = mem::replace(&mut app.mode, Mode::Normal); - - // Now that we own the jump mode, switch to - // the previous select mode, if there was one. - if let Mode::Jump(jump_mode) = old_mode { - match jump_mode.select_mode { - jump::SelectModeOptions::None => (), - jump::SelectModeOptions::Select(select_mode) => { - app.mode = Mode::Select(select_mode); - } - jump::SelectModeOptions::SelectLine(select_mode) => { - app.mode = Mode::SelectLine(select_mode); - } - } - } -} - pub fn push_search_char(app: &mut Application) -> Result { if let Some(ref key) = *app.view.last_key() { if let Mode::Jump(ref mut mode) = app.mode { diff --git a/src/commands/path.rs b/src/commands/path.rs index d72cb4c7..0fdc0149 100644 --- a/src/commands/path.rs +++ b/src/commands/path.rs @@ -1,7 +1,7 @@ use crate::commands::{self, Result}; use crate::errors::*; use crate::input::Key; -use crate::models::application::{Application, Mode}; +use crate::models::application::{Application, Mode, ModeKey}; use std::path::PathBuf; pub fn push_char(app: &mut Application) -> Result { @@ -51,7 +51,7 @@ pub fn accept_path(app: &mut Application) -> Result { app.workspace .update_current_syntax() .chain_err(|| BUFFER_SYNTAX_UPDATE_FAILED)?; - app.mode = Mode::Normal; + app.switch_to(ModeKey::Normal); if save_on_accept { commands::buffer::save(app) diff --git a/src/commands/search.rs b/src/commands/search.rs index 13f308ec..72d88d8e 100644 --- a/src/commands/search.rs +++ b/src/commands/search.rs @@ -67,12 +67,11 @@ pub fn accept_query(app: &mut Application) -> Result { Ok(()) } -pub fn clear_query(app: &mut Application) -> Result { +pub fn reset(app: &mut Application) -> Result { if let Mode::Search(ref mut mode) = app.mode { - mode.input = None; - app.search_query = None; + mode.reset(); } else { - bail!("Can't clear search outside of search mode"); + bail!("Can't reset search outside of search mode"); }; Ok(()) @@ -89,7 +88,6 @@ pub fn push_search_char(app: &mut Application) -> Result { if let Mode::Search(ref mut mode) = app.mode { let query = mode.input.get_or_insert(String::new()); query.push(c); - app.search_query = Some(query.clone()); } else { bail!("Can't push search character outside of search mode"); } @@ -105,7 +103,6 @@ pub fn pop_search_char(app: &mut Application) -> Result { let query = mode.input.as_mut().ok_or(SEARCH_QUERY_MISSING)?; query.pop(); - app.search_query = Some(query.clone()); } else { bail!("Can't pop search character outside of search mode"); }; @@ -280,9 +277,12 @@ mod tests { buffer.cursor.move_to(Position { line: 0, offset: 4 }); app.workspace.add_buffer(buffer); - // Add a search query, enter search mode, and accept the query. - app.search_query = Some(String::from("ed")); + // Enter search mode, add a query, and accept the query. commands::application::switch_to_search_mode(&mut app).unwrap(); + match app.mode { + Mode::Search(ref mut mode) => mode.input = Some("ed".into()), + _ => (), + } commands::search::accept_query(&mut app).unwrap(); // Ensure that we've disabled insert sub-mode. @@ -291,9 +291,6 @@ mod tests { _ => false, }); - // Ensure that the search query is properly set. - assert_eq!(app.search_query, Some("ed".to_string())); - // Ensure the buffer cursor is at the expected position. assert_eq!( *app.workspace.current_buffer.as_ref().unwrap().cursor, diff --git a/src/commands/search_select.rs b/src/commands/search_select.rs index 5e6651bf..57e5931d 100644 --- a/src/commands/search_select.rs +++ b/src/commands/search_select.rs @@ -3,16 +3,10 @@ use crate::errors::*; use crate::input::Key; use crate::models::application::modes::open::DisplayablePath; use crate::models::application::modes::SearchSelectMode; -use crate::models::application::{Application, Mode}; -use std::mem; +use crate::models::application::{Application, Mode, ModeKey}; pub fn accept(app: &mut Application) -> Result { - // Consume the application mode. This is necessary because the selection in - // command mode needs to run against the application, but we can't hold the - // reference to the selection and lend the app mutably to it at the time. - let mut app_mode = mem::replace(&mut app.mode, Mode::Normal); - - match app_mode { + match app.mode { Mode::Command(ref mode) => { let selection = mode.selection().ok_or("No command selected")?; @@ -76,6 +70,7 @@ pub fn accept(app: &mut Application) -> Result { _ => bail!("Can't accept selection outside of search select mode."), } + app.switch_to(ModeKey::Normal); commands::view::scroll_cursor_to_center(app).ok(); Ok(()) diff --git a/src/commands/selection.rs b/src/commands/selection.rs index 34094d79..fb0a7216 100644 --- a/src/commands/selection.rs +++ b/src/commands/selection.rs @@ -222,8 +222,11 @@ mod tests { // Now that we've set up the buffer, add it // to the application and call the command. app.workspace.add_buffer(buffer); - app.search_query = Some(String::from("ed")); commands::application::switch_to_search_mode(&mut app).unwrap(); + match app.mode { + Mode::Search(ref mut mode) => mode.input = Some("ed".into()), + _ => (), + } commands::search::accept_query(&mut app).unwrap(); commands::selection::delete(&mut app).unwrap(); diff --git a/src/input/key_map/default.yml b/src/input/key_map/default.yml index bf8b39e9..83557a79 100644 --- a/src/input/key_map/default.yml +++ b/src/input/key_map/default.yml @@ -58,7 +58,7 @@ normal: "#": application::switch_to_syntax_mode /: - application::switch_to_search_mode - - search::clear_query + - search::reset ",": view::scroll_up ">": buffer::indent_line "<": buffer::outdent_line @@ -125,7 +125,7 @@ search: - search::run /: - application::switch_to_search_mode - - search::clear_query + - search::reset m: view::scroll_down ",": view::scroll_up n: search::move_to_next_result diff --git a/src/models/application/mod.rs b/src/models/application/mod.rs index d4500c30..48b5dc96 100644 --- a/src/models/application/mod.rs +++ b/src/models/application/mod.rs @@ -6,6 +6,7 @@ mod preferences; // Published API pub use self::clipboard::ClipboardContent; pub use self::event::Event; +pub use self::modes::{Mode, ModeKey}; pub use self::preferences::Preferences; use self::clipboard::Clipboard; @@ -15,35 +16,19 @@ use crate::errors::*; use crate::presenters; use crate::view::View; use git2::Repository; +use scribe::buffer::Position; use scribe::{Buffer, Workspace}; use std::cell::RefCell; +use std::collections::HashMap; use std::env; +use std::mem; use std::path::{Path, PathBuf}; use std::rc::Rc; use std::sync::mpsc::{self, Receiver, Sender}; -pub enum Mode { - Confirm(ConfirmMode), - Command(CommandMode), - Exit, - Insert, - Jump(JumpMode), - LineJump(LineJumpMode), - Path(PathMode), - Normal, - Open(OpenMode), - Select(SelectMode), - SelectLine(SelectLineMode), - Search(SearchMode), - SymbolJump(SymbolJumpMode), - Syntax(SyntaxMode), - Theme(ThemeMode), -} - pub struct Application { pub mode: Mode, pub workspace: Workspace, - pub search_query: Option, pub view: View, pub clipboard: Clipboard, pub repository: Option, @@ -51,6 +36,9 @@ pub struct Application { pub preferences: Rc>, pub event_channel: Sender, events: Receiver, + current_mode: ModeKey, + previous_mode: ModeKey, + modes: HashMap, } impl Application { @@ -64,10 +52,12 @@ impl Application { // Set up a workspace in the current directory. let workspace = create_workspace(&mut view, &preferences.borrow(), args)?; - Ok(Application { + let mut app = Application { + current_mode: ModeKey::Normal, + previous_mode: ModeKey::Normal, mode: Mode::Normal, + modes: HashMap::new(), workspace, - search_query: None, view, clipboard, repository: Repository::discover(env::current_dir()?).ok(), @@ -75,7 +65,11 @@ impl Application { preferences, event_channel, events, - }) + }; + + app.create_modes()?; + + Ok(app) } pub fn run(&mut self) -> Result<()> { @@ -230,6 +224,96 @@ impl Application { Mode::Exit => None, } } + + pub fn switch_to(&mut self, mode_key: ModeKey) { + if self.current_mode == mode_key { + return; + } + + // Check out the specified mode. + let mut mode = self.modes.remove(&mode_key).unwrap(); + + // Activate the specified mode. + mem::swap(&mut self.mode, &mut mode); + + // Check in the previous mode. + self.modes.insert(self.current_mode, mode); + + // Track the previous mode. + self.previous_mode = self.current_mode; + + // Track the new active mode. + self.current_mode = mode_key; + } + + pub fn switch_to_previous_mode(&mut self) { + self.switch_to(self.previous_mode); + } + + fn create_modes(&mut self) -> Result<()> { + // Do the easy ones first. + self.modes.insert(ModeKey::Exit, Mode::Exit); + self.modes.insert(ModeKey::Insert, Mode::Insert); + self.modes.insert(ModeKey::Normal, Mode::Normal); + + self.modes.insert( + ModeKey::Command, + Mode::Command(CommandMode::new( + self.preferences.borrow().search_select_config(), + )), + ); + self.modes.insert( + ModeKey::Confirm, + Mode::Confirm(ConfirmMode::new( + commands::application::switch_to_normal_mode, + )), + ); + self.modes + .insert(ModeKey::Jump, Mode::Jump(JumpMode::new(0))); + self.modes + .insert(ModeKey::LineJump, Mode::LineJump(LineJumpMode::new())); + self.modes + .insert(ModeKey::LineJump, Mode::LineJump(LineJumpMode::new())); + self.modes.insert( + ModeKey::Open, + Mode::Open(OpenMode::new( + self.workspace.path.clone(), + self.preferences.borrow().search_select_config(), + )), + ); + self.modes + .insert(ModeKey::Path, Mode::Path(PathMode::new())); + self.modes + .insert(ModeKey::Search, Mode::Search(SearchMode::new(None))); + self.modes.insert( + ModeKey::Select, + Mode::Select(SelectMode::new(Position::default())), + ); + self.modes.insert( + ModeKey::SelectLine, + Mode::SelectLine(SelectLineMode::new(0)), + ); + self.modes.insert( + ModeKey::SymbolJump, + Mode::SymbolJump(SymbolJumpMode::new( + self.preferences.borrow().search_select_config(), + )?), + ); + self.modes.insert( + ModeKey::Syntax, + Mode::Syntax(SyntaxMode::new( + self.preferences.borrow().search_select_config(), + )), + ); + self.modes.insert( + ModeKey::Theme, + Mode::Theme(ThemeMode::new( + self.preferences.borrow().search_select_config(), + )), + ); + + Ok(()) + } } fn initialize_preferences() -> Rc> { @@ -326,7 +410,7 @@ fn user_syntax_path() -> Result> { #[cfg(test)] mod tests { use super::preferences::Preferences; - use super::Application; + use super::{Application, Mode, ModeKey}; use crate::view::View; use scribe::Buffer; @@ -401,4 +485,66 @@ mod tests { "Rust" ); } + + #[test] + fn switch_to_activates_the_specified_mode() { + let mut app = Application::new(&Vec::new()).unwrap(); + + assert_eq!(app.current_mode, ModeKey::Normal); + assert!(matches!(app.mode, Mode::Normal)); + + app.switch_to(ModeKey::Exit); + + assert_eq!(app.current_mode, ModeKey::Exit); + assert!(matches!(app.mode, Mode::Exit)); + } + + #[test] + fn switch_to_retains_state_from_previous_modes() { + let mut app = Application::new(&Vec::new()).unwrap(); + + app.switch_to(ModeKey::Search); + match app.mode { + Mode::Search(ref mut s) => s.input = Some(String::from("state")), + _ => panic!("switch_to didn't change app mode"), + } + + app.switch_to(ModeKey::Normal); + app.switch_to(ModeKey::Search); + match app.mode { + Mode::Search(ref s) => assert_eq!(s.input, Some(String::from("state"))), + _ => panic!("switch_to didn't change app mode"), + } + } + + #[test] + fn switch_to_previous_mode_works() { + let mut app = Application::new(&Vec::new()).unwrap(); + + app.switch_to(ModeKey::Insert); + app.switch_to(ModeKey::Exit); + + assert_eq!(app.current_mode, ModeKey::Exit); + assert!(matches!(app.mode, Mode::Exit)); + + app.switch_to_previous_mode(); + + assert_eq!(app.current_mode, ModeKey::Insert); + assert!(matches!(app.mode, Mode::Insert)); + } + + #[test] + fn switch_to_handles_switching_to_current_mode() { + let mut app = Application::new(&Vec::new()).unwrap(); + + app.switch_to(ModeKey::Insert); + + assert_eq!(app.current_mode, ModeKey::Insert); + assert!(matches!(app.mode, Mode::Insert)); + + app.switch_to(ModeKey::Insert); + + assert_eq!(app.current_mode, ModeKey::Insert); + assert!(matches!(app.mode, Mode::Insert)); + } } diff --git a/src/models/application/modes/command/mod.rs b/src/models/application/modes/command/mod.rs index 5f68f6b4..d27c3388 100644 --- a/src/models/application/modes/command/mod.rs +++ b/src/models/application/modes/command/mod.rs @@ -27,6 +27,10 @@ impl CommandMode { config, } } + + pub fn reset(&mut self, config: SearchSelectConfig) { + self.config = config; + } } impl fmt::Display for CommandMode { diff --git a/src/models/application/modes/jump/mod.rs b/src/models/application/modes/jump/mod.rs index b1d477ac..ce82421f 100644 --- a/src/models/application/modes/jump/mod.rs +++ b/src/models/application/modes/jump/mod.rs @@ -3,22 +3,12 @@ mod tag_generator; use self::single_character_tag_generator::SingleCharacterTagGenerator; use self::tag_generator::TagGenerator; -use crate::models::application::modes::select::SelectMode; -use crate::models::application::modes::select_line::SelectLineMode; use crate::util::movement_lexer; use crate::view::{LexemeMapper, MappedLexeme}; use luthor::token::Category; use scribe::buffer::{Distance, Position}; use std::collections::HashMap; -/// Used to compose select and jump modes, allowing jump mode -/// to be used for cursor navigation (to select a range of text). -pub enum SelectModeOptions { - None, - Select(SelectMode), - SelectLine(SelectLineMode), -} - enum MappedLexemeValue { Tag((String, Position)), Text((String, Position)), @@ -28,7 +18,6 @@ pub struct JumpMode { pub input: String, pub first_phase: bool, cursor_line: usize, - pub select_mode: SelectModeOptions, tag_positions: HashMap, tag_generator: TagGenerator, single_characters: SingleCharacterTagGenerator, @@ -42,11 +31,10 @@ impl JumpMode { input: String::new(), first_phase: true, cursor_line, - select_mode: SelectModeOptions::None, tag_positions: HashMap::new(), tag_generator: TagGenerator::new(), single_characters: SingleCharacterTagGenerator::new(), - current_position: Position { line: 0, offset: 0 }, + current_position: Position::default(), mapped_lexeme_values: Vec::new(), } } @@ -60,6 +48,15 @@ impl JumpMode { self.tag_generator.reset(); self.single_characters.reset(); } + + pub fn reset(&mut self, cursor_line: usize) { + self.input = String::new(); + self.first_phase = true; + self.current_position = Position::default(); + self.cursor_line = cursor_line; + self.mapped_lexeme_values = Vec::new(); + self.reset_display(); + } } impl LexemeMapper for JumpMode { diff --git a/src/models/application/modes/line_jump.rs b/src/models/application/modes/line_jump.rs index f46c07fd..860daac5 100644 --- a/src/models/application/modes/line_jump.rs +++ b/src/models/application/modes/line_jump.rs @@ -7,4 +7,8 @@ impl LineJumpMode { pub fn new() -> LineJumpMode { LineJumpMode::default() } + + pub fn reset(&mut self) { + self.input = String::new(); + } } diff --git a/src/models/application/modes/mod.rs b/src/models/application/modes/mod.rs index 12c55a76..7dc4fb4a 100644 --- a/src/models/application/modes/mod.rs +++ b/src/models/application/modes/mod.rs @@ -12,6 +12,43 @@ mod symbol_jump; mod syntax; mod theme; +pub enum Mode { + Command(CommandMode), + Confirm(ConfirmMode), + Exit, + Insert, + Jump(JumpMode), + LineJump(LineJumpMode), + Normal, + Open(OpenMode), + Path(PathMode), + Search(SearchMode), + Select(SelectMode), + SelectLine(SelectLineMode), + SymbolJump(SymbolJumpMode), + Syntax(SyntaxMode), + Theme(ThemeMode), +} + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum ModeKey { + Command, + Confirm, + Exit, + Insert, + Jump, + LineJump, + Normal, + Open, + Path, + Search, + Select, + SelectLine, + SymbolJump, + Syntax, + Theme, +} + pub use self::command::CommandMode; pub use self::confirm::ConfirmMode; pub use self::jump::JumpMode; diff --git a/src/models/application/modes/open/mod.rs b/src/models/application/modes/open/mod.rs index 841e731b..c596e2b8 100644 --- a/src/models/application/modes/open/mod.rs +++ b/src/models/application/modes/open/mod.rs @@ -28,20 +28,7 @@ pub struct OpenMode { } impl OpenMode { - pub fn new( - path: PathBuf, - exclusions: Option>, - events: Sender, - config: SearchSelectConfig, - ) -> OpenMode { - // Build and populate the index in a separate thread. - let index_path = path.clone(); - thread::spawn(move || { - let mut index = Index::new(index_path); - index.populate(exclusions, false); - let _ = events.send(Event::OpenModeIndexComplete(index)); - }); - + pub fn new(path: PathBuf, config: SearchSelectConfig) -> OpenMode { OpenMode { insert: true, input: String::new(), @@ -54,6 +41,27 @@ impl OpenMode { pub fn set_index(&mut self, index: Index) { self.index = OpenModeIndex::Complete(index) } + + pub fn reset( + &mut self, + path: PathBuf, + exclusions: Option>, + events: Sender, + config: SearchSelectConfig, + ) { + self.insert = true; + self.input = String::new(); + self.config = config; + self.index = OpenModeIndex::Indexing(path.clone()); + self.results = SelectableVec::new(Vec::new()); + + // Build and populate the index in a separate thread. + thread::spawn(move || { + let mut index = Index::new(path); + index.populate(exclusions, false); + let _ = events.send(Event::OpenModeIndexComplete(index)); + }); + } } impl fmt::Display for OpenMode { diff --git a/src/models/application/modes/path.rs b/src/models/application/modes/path.rs index af4378d4..758fd146 100644 --- a/src/models/application/modes/path.rs +++ b/src/models/application/modes/path.rs @@ -1,23 +1,28 @@ use std::fmt; +#[derive(Default)] pub struct PathMode { pub input: String, pub save_on_accept: bool, } impl PathMode { - pub fn new(initial_path: String) -> PathMode { - PathMode { - input: initial_path, - save_on_accept: false, - } + pub fn new() -> PathMode { + PathMode::default() } + pub fn push_char(&mut self, c: char) { self.input.push(c); } + pub fn pop_char(&mut self) { self.input.pop(); } + + pub fn reset(&mut self, initial_path: String) { + self.input = initial_path; + self.save_on_accept = false; + } } impl fmt::Display for PathMode { diff --git a/src/models/application/modes/search.rs b/src/models/application/modes/search.rs index d21de0e1..e508aaf6 100644 --- a/src/models/application/modes/search.rs +++ b/src/models/application/modes/search.rs @@ -18,6 +18,12 @@ impl SearchMode { } } + pub fn reset(&mut self) { + self.insert = true; + self.input = None; + self.results = None; + } + pub fn insert_mode(&self) -> bool { self.insert } diff --git a/src/models/application/modes/select.rs b/src/models/application/modes/select.rs index 604125c0..77314b6d 100644 --- a/src/models/application/modes/select.rs +++ b/src/models/application/modes/select.rs @@ -8,4 +8,8 @@ impl SelectMode { pub fn new(anchor: Position) -> SelectMode { SelectMode { anchor } } + + pub fn reset(&mut self, anchor: Position) { + self.anchor = anchor; + } } diff --git a/src/models/application/modes/select_line.rs b/src/models/application/modes/select_line.rs index bab71a54..428c91bd 100644 --- a/src/models/application/modes/select_line.rs +++ b/src/models/application/modes/select_line.rs @@ -9,6 +9,10 @@ impl SelectLineMode { SelectLineMode { anchor } } + pub fn reset(&mut self, anchor: usize) { + self.anchor = anchor; + } + pub fn to_range(&self, cursor: &Position) -> Range { LineRange::new(self.anchor, cursor.line).to_inclusive_range() } diff --git a/src/models/application/modes/symbol_jump.rs b/src/models/application/modes/symbol_jump.rs index a421f08f..b9781c38 100644 --- a/src/models/application/modes/symbol_jump.rs +++ b/src/models/application/modes/symbol_jump.rs @@ -52,17 +52,22 @@ impl AsStr for Symbol { } impl SymbolJumpMode { - pub fn new(tokens: &TokenSet, config: SearchSelectConfig) -> Result { - let symbols = symbols(tokens.iter().chain_err(|| BUFFER_PARSE_FAILED)?); - + pub fn new(config: SearchSelectConfig) -> Result { Ok(SymbolJumpMode { insert: true, input: String::new(), - symbols, + symbols: Vec::new(), results: SelectableVec::new(Vec::new()), config, }) } + + pub fn reset(&mut self, tokens: &TokenSet, config: SearchSelectConfig) -> Result<()> { + self.symbols = symbols(tokens.iter().chain_err(|| BUFFER_PARSE_FAILED)?); + self.config = config; + + Ok(()) + } } impl fmt::Display for SymbolJumpMode { diff --git a/src/models/application/modes/syntax.rs b/src/models/application/modes/syntax.rs index 50b94e5e..e106073c 100644 --- a/src/models/application/modes/syntax.rs +++ b/src/models/application/modes/syntax.rs @@ -13,15 +13,20 @@ pub struct SyntaxMode { } impl SyntaxMode { - pub fn new(syntaxes: Vec, config: SearchSelectConfig) -> SyntaxMode { + pub fn new(config: SearchSelectConfig) -> SyntaxMode { SyntaxMode { insert: true, input: String::new(), - syntaxes, + syntaxes: Vec::new(), results: SelectableVec::new(Vec::new()), config, } } + + pub fn reset(&mut self, syntaxes: Vec, config: SearchSelectConfig) { + self.syntaxes = syntaxes; + self.config = config; + } } impl fmt::Display for SyntaxMode { diff --git a/src/models/application/modes/theme.rs b/src/models/application/modes/theme.rs index d093fd36..a01d7b8c 100644 --- a/src/models/application/modes/theme.rs +++ b/src/models/application/modes/theme.rs @@ -4,6 +4,7 @@ use fragment; use std::fmt; use std::slice::Iter; +#[derive(Default)] pub struct ThemeMode { insert: bool, input: String, @@ -13,15 +14,22 @@ pub struct ThemeMode { } impl ThemeMode { - pub fn new(themes: Vec, config: SearchSelectConfig) -> ThemeMode { + pub fn new(config: SearchSelectConfig) -> ThemeMode { ThemeMode { - insert: true, - input: String::new(), - themes, - results: SelectableVec::new(Vec::new()), config, + insert: true, + ..Default::default() } } + + pub fn reset(&mut self, themes: Vec, config: SearchSelectConfig) { + *self = ThemeMode { + config, + insert: true, + themes, + ..Default::default() + }; + } } impl fmt::Display for ThemeMode { diff --git a/src/util/selectable_vec.rs b/src/util/selectable_vec.rs index 0f0855f2..9e8d93d1 100644 --- a/src/util/selectable_vec.rs +++ b/src/util/selectable_vec.rs @@ -3,6 +3,7 @@ use std::ops::Deref; /// A simple decorator around a Vec that allows a single element to be selected. /// The selection can be incremented/decremented in single steps, and the /// selected value wraps when moved beyond either edge of the set. +#[derive(Default)] pub struct SelectableVec { set: Vec, selected_index: usize,