Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat command mode #58

Merged
merged 33 commits into from
May 22, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
61a815b
Add vim like command mode
NickHackman May 17, 2019
4d6fad8
Add cmd mode for infobar, cmd mode help and quit
NickHackman May 17, 2019
5eab4dd
Add Command mode
NickHackman May 17, 2019
3a9f4a4
Add command mode m/max, takes new max image length
NickHackman May 17, 2019
5ae1ab1
Refractored code, fixed bug in :max
NickHackman May 17, 2019
553e0a5
Add force render, added docs to command_mode_run
NickHackman May 17, 2019
9fa525a
Refractor name change in ui.rs
NickHackman May 18, 2019
1cbce37
Add newglob Err msg if file doesn't exist, bug fix
NickHackman May 18, 2019
f84d117
Add Commands enum for command mode
NickHackman May 19, 2019
5bbf788
Fix bug in newglob, add dep shellexpand and regex
NickHackman May 19, 2019
0c266b6
Fixed Bugs, empty images didn't render bar command
NickHackman May 19, 2019
66af23a
Merge branch 'development' into feat-redefine-glob
NickHackman May 19, 2019
7be5457
Refractored code in response to clippy suggestions
NickHackman May 19, 2019
0a12516
Merge remote-tracking branch 'refs/remotes/origin/feat-redefine-glob'…
NickHackman May 19, 2019
cc6e324
Rewrite of Modal system, add Error
NickHackman May 20, 2019
d0908dc
Refractored code in command_mode.rs
NickHackman May 21, 2019
4799c82
Add lazy_static!. Make path_regex compile once
gurgalex May 21, 2019
ad6bbc3
Remove unneeded lifetimes due to cloning
gurgalex May 21, 2019
5fb3e7e
Refractored command_mode.rs
NickHackman May 21, 2019
e02ab69
Merge remote-tracking branch 'refs/remotes/origin/feat-redefine-glob'…
NickHackman May 21, 2019
b085855
Fixed issue with merge
NickHackman May 21, 2019
fb30e21
Fixed bug in sort on empty, and when merging
NickHackman May 21, 2019
0819674
Added virutal mapping for ':'
NickHackman May 21, 2019
396fef9
Changed Event Handling for Normal mode
NickHackman May 21, 2019
fa916a7
Fixed bug max_viewable, would be overwritten
NickHackman May 21, 2019
e25df1c
Updated README with Command mode options
NickHackman May 21, 2019
bcfa4c2
Fixed bug in command mode destfolder
NickHackman May 21, 2019
a5b0f1a
Removed unecessary L/R shift in ui.rs
NickHackman May 21, 2019
b12894d
Entering command mode toggles bar on by default
NickHackman May 21, 2019
a0db6e8
Fixed bugs delete, move, and command mode df
NickHackman May 22, 2019
5e548a4
Fixed bug, program returns an error
NickHackman May 22, 2019
2ea2226
Command mode newglob, reset keep folder
NickHackman May 22, 2019
42941ac
Paths::curent_dir -> base_dir, better track of df
NickHackman May 22, 2019
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
37 changes: 21 additions & 16 deletions src/infobar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//! Module InfoBar provides structures and functions for building and rendering an infobar

use crate::paths::Paths;
use crate::ui::Mode;

/// Text contains the strings required to print the infobar.
pub struct Text {
Expand All @@ -15,21 +16,22 @@ pub struct Text {
}

impl Text {
/// Updates the infobar
/// if cmd isn't empty the user is in command mode and therefore that should be displayed
/// instead of normal mode information. Normal mode information is the index and the current
/// image path
pub fn update(paths: &Paths, msg: Option<&str>) -> Self {
let information: String;
let mode: String;
match msg {
Some(msg) => {
// user is in command mode
information = msg.to_string();
mode = String::from("Command");
}
None => {
// user is in normal mode
/// Updates the infobar based on the current mode of the applicaiton
/// Normal Mode:
/// mode = index of current image
/// information = path to current image
/// Command Mode:
/// mode = "Command"
/// information = curerntly entered user string
/// Error Mode:
/// mode = "Error"
/// information = error message to display
pub fn update(current_mode: &Mode, paths: &Paths) -> Self {
let (mode, information) = match current_mode {
Mode::Command(msg) => ("Command".to_string(), format!(":{}", msg)),
Mode::Normal => {
let information: String;
let mode: String;
information = match paths.images.get(paths.index) {
Some(path) => match path.to_str() {
Some(name) => name.to_string(),
Expand All @@ -42,8 +44,11 @@ impl Text {
} else {
format!("{} of {}", paths.index + 1, paths.max_viewable)
};
(mode, information)
}
}
Mode::Error(msg) => ("Error".to_string(), msg.to_string()),
_ => (String::new(), String::new()),
NickHackman marked this conversation as resolved.
Show resolved Hide resolved
};
Text { information, mode }
}
}
83 changes: 51 additions & 32 deletions src/program/command_mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,15 @@ impl FromStr for Commands {
"r" | "reverse" => Ok(Commands::Reverse),
"df" | "destfolder" => Ok(Commands::DestFolder),
"m" | "max" => Ok(Commands::MaximumImages),
_ => Err(format!("Error: no such command \"{}\"", s)),
_ => Err(format!("No such command \"{}\"", s)),
}
}
}

/// Converts the provided path by user to a path that can be glob'd
/// Directories are changed from /home/etc to /home/etc/*
fn convert_path_to_globable(path: &str) -> Result<String, String> {
let expanded_path =
full(path).map_err(|e| format!("Error: \"{}\": {}", e.var_name, e.cause))?;
let expanded_path = full(path).map_err(|e| format!("\"{}\": {}", e.var_name, e.cause))?;
let mut absolute_path = String::from(expanded_path);
// If path is a dir, add /* to glob
if PathBuf::from(&absolute_path).is_dir() {
Expand All @@ -98,26 +97,32 @@ fn glob_path(path: &str) -> Result<Vec<PathBuf>, String> {
push_image_path(&mut new_images, p);
}
Err(e) => {
let err_msg = format!("Error: Unexpected path {}", e);
let err_msg = format!("Unexpected path {}", e);
return Err(err_msg);
}
}
}
if new_images.is_empty() {
let err_msg = format!("Error: path \"{}\" had no images", path);
let err_msg = format!("Path \"{}\" had no images", path);
return Err(err_msg);
}
Ok(new_images)
}

/// Uses regex to parse user input, then concating all subsequent strings that are escaped together
/// TODO: have regex capture the ending space in case of escaped files
/// Uses regex to parse user input that's provided to command mode into arguments
/// First argument is the command followed by its arguments
fn parse_user_input(input: &str) -> Vec<String> {
let mut input_vec: Vec<String> = Vec::new();
let re: Regex = Regex::new(r"[\x21-\x7E]+(\\)?").unwrap();
// Regex pattern matches all unicode characters there must be at least 1
// and the '\' character may be present at the end of the string
// This is used to rather than split on whitespace, only get the actual arguments
let regex = match Regex::new(r"[\x21-\x7E]+(\\)?") {
NickHackman marked this conversation as resolved.
Show resolved Hide resolved
Ok(regex) => regex,
Err(e) => panic!("Failed to construct Regex in parse_user_input: {}", e),
};
let mut escaped = false;

for regex_match in re.captures_iter(&input) {
for regex_match in regex.captures_iter(&input) {
let value: String = regex_match[0].to_string();
if value.ends_with('\\') && !escaped {
escaped = true;
Expand All @@ -137,7 +142,6 @@ fn parse_user_input(input: &str) -> Vec<String> {
impl<'a> Program<'a> {
/// User input is taken in and displayed on infobar, cmd is either '/' or ':'
/// Returning empty string signifies switching modes back to normal mode
// TODO: autocomplete use fn
fn get_command(&mut self, cmd: &str) -> Result<String, String> {
let mut input = String::new();
let mut events = self.screen.sdl_context.event_pump()?;
Expand All @@ -150,17 +154,17 @@ impl<'a> Program<'a> {
break 'command_loop;
}
input.pop();
let display = format!("{}{}", cmd, input);
self.render_screen(false, Some(&display))?;
self.ui_state.mode = Mode::Command(input.clone());
self.render_screen(false)?;
}
Action::KeyboardInput(text) => {
input.push_str(text);
// Fixes additional ':' in command mode start
if input.starts_with(cmd) {
input = input[1..].to_string();
}
let display = format!("{}{}", cmd, input);
self.render_screen(false, Some(&display))?;
self.ui_state.mode = Mode::Command(input.clone());
self.render_screen(false)?;
}
Action::SwitchNormalMode => break 'command_loop,
_ => continue,
Expand All @@ -175,6 +179,8 @@ impl<'a> Program<'a> {
/// After every command the user is set either into normal mode or the app terminates.
///
/// List of commands provided in `Commands` enum
///
/// Error is returned only in serious cases, for instance if the application fails to render_screen
pub fn run_command_mode(&mut self) -> Result<(), String> {
let input = self.get_command(":")?;
// after evaluating a command always exit to normal mode by default
Expand All @@ -184,16 +190,28 @@ impl<'a> Program<'a> {
return Ok(());
}
let input_vec = parse_user_input(&input);
let command = Commands::from_str(&input_vec[0])?;
let command = match Commands::from_str(&input_vec[0]) {
Ok(command) => command,
Err(e) => {
self.ui_state.mode = Mode::Error(e.to_string());
return Ok(());
}
};
match command {
Commands::NewGlob => {
if input_vec.len() < 2 {
let err_msg =
String::from("Error: command \"newglob\" or \":ng\" requires a glob");
return Err(err_msg);
self.ui_state.mode =
Mode::Error(("Command \"newglob\" or \":ng\" requires a glob").to_string());
return Ok(());
}
let mut new_images: Vec<PathBuf>;
new_images = glob_path(&input_vec[1])?;
new_images = match glob_path(&input_vec[1]) {
Ok(new_images) => new_images,
Err(e) => {
self.ui_state.mode = Mode::Error(e.to_string());
return Ok(());
}
};
let target = if !self.paths.images.is_empty() {
Some(self.paths.images[self.paths.index].to_owned())
} else {
Expand Down Expand Up @@ -236,24 +254,26 @@ impl<'a> Program<'a> {
}
Commands::DestFolder => {
if input_vec.len() < 2 {
let err_msg =
String::from("Error: command \":destfolder\" or \":d\" requires a path");
return Err(err_msg);
self.ui_state.mode = Mode::Error(
"Command \":destfolder\" or \":d\" requires a path".to_string(),
);
return Ok(());
}
self.paths.dest_folder = PathBuf::from(&input_vec[1]);
}
Commands::MaximumImages => {
if input_vec.len() < 2 {
let err_msg =
String::from("Error: command \":max\" or \":m\" requires a new maximum number of files to display");
return Err(err_msg);
self.ui_state.mode = Mode::Error(
"Command \":max\" or \":m\" requires a new maximum number of files to display".to_string(),
);
return Ok(());
}
self.paths.max_viewable = match input_vec[1].parse::<usize>() {
Ok(new_max) => new_max,
Err(_e) => {
let err_msg =
format!("Error: \"{}\" is not a positive integer", input_vec[1]);
return Err(err_msg);
self.ui_state.mode =
Mode::Error(format!("\"{}\" is not a positive integer", input_vec[1]));
return Ok(());
}
};
if self.paths.max_viewable > self.paths.images.len() || self.paths.max_viewable == 0
Expand All @@ -270,10 +290,9 @@ impl<'a> Program<'a> {
let new_sort_order = match SortOrder::from_str(&input_vec[1]) {
Ok(order) => order,
Err(e) => {
return Err(format!(
"Error: invalid value \"{}\". {}",
input_vec[1], e
));
self.ui_state.mode =
Mode::Command(format!("Invalid value \"{}\". {}", input_vec[1], e));
return Ok(());
}
};
self.sorter.set_order(new_sort_order);
Expand Down
44 changes: 24 additions & 20 deletions src/program/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ impl<'a> Program<'a> {
else {
self.paths.index = self.paths.max_viewable - 1;
}
self.render_screen(false, None)
self.render_screen(false)
}

/// Removes an image from tracked images.
Expand All @@ -159,7 +159,7 @@ impl<'a> Program<'a> {
else {
self.paths.index = 0;
}
self.render_screen(false, None)
self.render_screen(false)
}

/// Skips forward by the default skip increment and renders the image
Expand All @@ -177,7 +177,7 @@ impl<'a> Program<'a> {
/// Go to and render first image in list
fn first(&mut self) -> Result<(), String> {
self.paths.index = 0;
self.render_screen(false, None)
self.render_screen(false)
}

/// Go to and render last image in list
Expand All @@ -187,7 +187,7 @@ impl<'a> Program<'a> {
} else {
self.paths.index = self.paths.max_viewable - 1;
}
self.render_screen(false, None)
self.render_screen(false)
}

fn construct_dest_filepath(&self, src_path: &PathBuf) -> Result<PathBuf, String> {
Expand Down Expand Up @@ -259,7 +259,7 @@ impl<'a> Program<'a> {

// Moving the image automatically advanced to next image
// Adjust our view to reflect this
self.render_screen(false, None)
self.render_screen(false)
}

/// Deletes image currently being viewed
Expand Down Expand Up @@ -294,7 +294,7 @@ impl<'a> Program<'a> {

// Removing the image automatically advanced to next image
// Adjust our view to reflect this
self.render_screen(false, None)
self.render_screen(false)
}

/// Toggles fullscreen state of app
Expand All @@ -305,17 +305,21 @@ impl<'a> Program<'a> {
/// Central run function that starts by default in Normal mode
/// Switches modes allowing events to be interpreted in different ways
pub fn run(&mut self) -> Result<(), String> {
self.render_screen(false, None)?;
while self.ui_state.mode != Mode::Exit {
match self.ui_state.mode {
self.render_screen(false)?;
'main_loop: loop {
let mode = &self.ui_state.mode.clone();
match mode {
Mode::Normal => self.run_normal_mode()?,
Mode::Command => match self.run_command_mode() {
// Upon success refresh
Ok(()) => self.render_screen(true, None)?,
// Upon failure refresh and display error
Err(e) => self.render_screen(false, Some(&e))?,
},
Mode::Exit => continue,
Mode::Command(..) => {
self.run_command_mode()?;
// Force renders in order to remove "Command" and other info from bar
self.render_screen(true)?;
}
Mode::Error(..) => {
self.render_screen(false)?;
self.ui_state.mode = Mode::Normal;
}
Mode::Exit => break 'main_loop,
}
}
Ok(())
Expand All @@ -334,16 +338,16 @@ impl<'a> Program<'a> {
Action::ToggleFullscreen => {
self.toggle_fullscreen();
self.screen.update_fullscreen(self.ui_state.fullscreen)?;
self.render_screen(false, None)?
self.render_screen(false)?
}
Action::ReRender => self.render_screen(false, None)?,
Action::ReRender => self.render_screen(false)?,
Action::SwitchCommandMode => {
self.ui_state.mode = Mode::Command;
self.ui_state.mode = Mode::Command(String::new());
break 'mainloop;
}
Action::ToggleFit => {
self.toggle_fit();
self.render_screen(false, None)?
self.render_screen(false)?
}
Action::Next => self.increment(1)?,
Action::Prev => self.decrement(1)?,
Expand Down
18 changes: 7 additions & 11 deletions src/program/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,15 @@ const LINE_PADDING: i32 = 5;
impl<'a> Program<'a> {
/// render_screen is the main render function that delegates rendering every thing that needs be
/// rendered
pub fn render_screen(
&mut self,
force_render: bool,
infobar_msg: Option<&str>,
) -> Result<(), String> {
pub fn render_screen(&mut self, force_render: bool) -> Result<(), String> {
self.screen.canvas.set_draw_color(dark_grey());
if self.paths.images.is_empty() {
return self.render_blank(infobar_msg);
return self.render_blank();
}
self.screen.canvas.clear();
self.render_image(force_render)?;
if self.ui_state.render_infobar {
self.render_infobar(infobar_msg)?;
self.render_infobar()?;
}
if self.ui_state.render_help {
self.render_help()?;
Expand Down Expand Up @@ -108,8 +104,8 @@ impl<'a> Program<'a> {
src_dims.x > dest_dims.x || src_dims.y > dest_dims.y
}

fn render_infobar(&mut self, msg: Option<&str>) -> Result<(), String> {
let text = infobar::Text::update(&self.paths, msg);
fn render_infobar(&mut self) -> Result<(), String> {
let text = infobar::Text::update(&self.ui_state.mode, &self.paths);
NickHackman marked this conversation as resolved.
Show resolved Hide resolved
// Load the filename texture
let filename_surface = self
.screen
Expand Down Expand Up @@ -263,10 +259,10 @@ impl<'a> Program<'a> {
Ok(())
}

fn render_blank(&mut self, infobar_msg: Option<&str>) -> Result<(), String> {
fn render_blank(&mut self) -> Result<(), String> {
self.screen.canvas.clear();
if self.ui_state.render_infobar {
self.render_infobar(infobar_msg)?;
self.render_infobar()?;
}
if self.ui_state.render_help {
self.render_help()?;
Expand Down
Loading