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 8 commits
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
2 changes: 1 addition & 1 deletion src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ pub fn cli() -> Result<Args, String> {
})
}

fn push_image_path(v: &mut Vec<PathBuf>, p: PathBuf) {
pub(crate) fn push_image_path(v: &mut Vec<PathBuf>, p: PathBuf) {
if let Some(ext) = p.extension() {
if let Some(ext_str) = ext.to_str() {
let low = ext_str.to_string().to_lowercase();
Expand Down
57 changes: 36 additions & 21 deletions src/infobar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,44 @@ use crate::paths::Paths;

/// Text contains the strings required to print the infobar.
pub struct Text {
/// current image is the string of the current image path
pub current_image: String,
/// index is the string represention of the index.
pub index: String,
/// Either displays the name of the current image or the current command the user is typing in
/// command mode
pub information: String,
/// In normal mode this is the string represention of the index, in command mode this is
/// "Command"
pub mode: String,
}

impl From<&Paths> for Text {
fn from(p: &Paths) -> Self {
let current_image = match p.images.get(p.index) {
Some(path) => match path.to_str() {
Some(name) => name.to_string(),
None => "No file".to_string(),
},
None => "No file selected".to_string(),
};
let index = if p.images.is_empty() {
"No files in path".to_string()
} else {
format!("{} of {}", p.index + 1, p.max_viewable)
};
Text {
current_image,
index,
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");
NickHackman marked this conversation as resolved.
Show resolved Hide resolved
}
None => {
// user is in normal mode
information = match paths.images.get(paths.index) {
Some(path) => match path.to_str() {
Some(name) => name.to_string(),
None => "No file".to_string(),
},
None => "No file selected".to_string(),
};
mode = if paths.images.is_empty() {
"No files in path".to_string()
} else {
format!("{} of {}", paths.index + 1, paths.max_viewable)
};
}
}
Text { information, mode }
}
}
263 changes: 263 additions & 0 deletions src/program/command_mode.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
//! File that contains Command mode functionality, command mode in this case dictates anything that
//! queries the user for input during run time
use super::Program;
use crate::sort::SortOrder;
use crate::ui::{process_command_mode, Action, Mode};
use std::path::Path;
use std::path::PathBuf;
use std::time::Duration;

/// Finds the provided path in paths returning the usize or error
/// Returns 0 if not found
fn find_path_in_paths(paths: &Vec<PathBuf>, current_path: &PathBuf) -> Option<usize> {
match paths.iter().position(|path| path == current_path) {
Some(i) => Some(i),
None => None,
}
}

/// Converts the provided path by user to a path that can be glob'd
/// Changes $HOME and ~ to their expanded home path
/// If the path is to a directory add glob to the end to catch the directories files
fn convert_path_to_globable(path: &str) -> Result<String, String> {
let mut absolute_path = String::from(path);
// Get HOME environment variable
let home = match std::env::var("HOME") {
Ok(home) => home,
Err(e) => return Err(e.to_string()),
};

// create path_buf from path to get parents
let mut path_buf = PathBuf::from(path);

// ~ constant used to check if any parent is '~'
let tilda = Path::new("~");
// $HOME constant used to check if any parent is "$HOME"
let home_shell = Path::new("$HOME");
// replace all ~ and $HOME with home env variable
for parent in path_buf.ancestors() {
if parent == tilda {
absolute_path = absolute_path.replace("~", &home);
} else if parent == home_shell {
absolute_path = absolute_path.replace("$HOME", &home);
}
}

path_buf = PathBuf::from(&absolute_path);
if !path_buf.exists() {
return Err(format!(
"Error: Cannot access \"{}\": No such file or directory",
path
));
}
// If path is a dir, add /* to glob
if path_buf.is_dir() {
if !absolute_path.ends_with("/") {
absolute_path.push('/');
}
absolute_path.push('*');
}
Ok(absolute_path)
}

/// Globs the passed path, returning an error if no images are in that path, glob::glob fails, or
/// path is unexpected
fn glob_path(path: &str) -> Result<Vec<PathBuf>, String> {
use crate::cli::push_image_path;

let mut new_images: Vec<PathBuf> = Vec::new();
let globable_path = convert_path_to_globable(path)?;
let path_matches = glob::glob(&globable_path).map_err(|e| e.to_string())?;
for path in path_matches {
match path {
Ok(p) => {
push_image_path(&mut new_images, p);
}
Err(e) => {
let err_msg = format!("Error: Unexpected path {}", e);
return Err(err_msg);
}
}
}
if new_images.is_empty() {
let err_msg = format!("Error: path \"{}\" had no images", path);
return Err(err_msg);
}
Ok(new_images)
}

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
NickHackman marked this conversation as resolved.
Show resolved Hide resolved
fn get_command(&mut self, cmd: &str) -> Result<String, String> {
let mut input = String::new();
let mut events = self.screen.sdl_context.event_pump()?;
'command_loop: loop {
for event in events.poll_iter() {
let action = process_command_mode(&event);
match action {
Action::Backspace => {
if input.len() < 1 {
break 'command_loop;
}
input.pop();
let display = format!("{}{}", cmd, input);
self.render_screen(false, Some(&display))?;
}
Action::KeyboardInput(text) => {
input.push_str(text);
// Fixes ':' 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))?;
}
Action::SwitchNormalMode => break 'command_loop,
_ => continue,
}
}
std::thread::sleep(Duration::from_millis(1000 / 60));
}
Ok(input)
}

/// Enters command mode that gets user input and runs a set of possible commands based on user
/// input. After every command the user is set either into normal mode again or the app
/// terminates
///
/// Commands:
/// * ng/newglob requires only one addition parameter, the new current_dir.
/// If the current image prior exists in the new glob move to that index
///
/// * h/help switches to normal mode and displays help info
///
/// * q/quit terminates the application
///
/// * r/reverse reverses current images, moving to index of current image prior to reverse
///
/// * df/destfolder sets the new destination folder
///
/// * sort no argument: performs the selected sort on images, keeps
/// moves to index of current image prior to reverse
/// one argument: performs selected sort
///
/// * m/max set new maximum amount of images to display
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
self.ui_state.mode = Mode::Normal;
// Empty input means switch back to normal mode
if input.is_empty() {
return Ok(());
}
let input_vec: Vec<&str> = input.split_whitespace().collect();

match input_vec[0] {
"ng" | "newglob" => {
if input_vec.len() < 2 {
let err_msg =
String::from("Error: command \"newglob\" or \":ng\" requires a glob");
return Err(err_msg);
}
let mut new_images: Vec<PathBuf>;
new_images = glob_path(input_vec[1])?;
NickHackman marked this conversation as resolved.
Show resolved Hide resolved
// the path to find in order to maintain that it is the current image
let target = self.paths.images[self.paths.index].to_owned();
self.paths.images = new_images;
self.sorter.sort(&mut self.paths.images);
match find_path_in_paths(&self.paths.images, &target) {
Some(new_index) => self.paths.index = new_index,
None => {
self.paths.index = 0;
}
}
self.paths.max_viewable = if self.paths.max_viewable > 0
&& self.paths.max_viewable <= self.paths.images.len()
{
self.paths.max_viewable
} else {
self.paths.images.len()
};
}
"h" | "help" => {
self.ui_state.render_help = !self.ui_state.render_help;
}
"q" | "quit" => {
self.ui_state.mode = Mode::Exit;
}
"r" | "reverse" => {
self.paths.images.reverse();
self.paths.index = self.paths.max_viewable - self.paths.index - 1;
}
"df" | "destfolder" => {
if input_vec.len() < 2 {
let err_msg =
String::from("Error: command \":destfolder\" or \":d\" requires a path");
return Err(err_msg);
}
self.paths.dest_folder = PathBuf::from(input_vec[1]);
}
"m" | "max" => {
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.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);
}
};
if self.paths.max_viewable > self.paths.images.len() || self.paths.max_viewable == 0
{
self.paths.max_viewable = self.paths.images.len();
}
if self.paths.max_viewable < self.paths.index {
self.paths.index = self.paths.max_viewable - 1;
}
}
"sort" => {
use std::str::FromStr;

// Allow both just calling "sort" and allow providing the new sort
if input_vec.len() >= 2 {
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.sorter.set_order(new_sort_order);
}
// the path to find in order to maintain that it is the current image
let target = self.paths.images[self.paths.index].to_owned();
self.sorter.sort(&mut self.paths.images);
match find_path_in_paths(&self.paths.images, &target) {
Some(new_index) => {
if new_index < self.paths.max_viewable {
self.paths.index = new_index;
} else {
self.paths.index = self.paths.max_viewable - 1;
}
}
None => {
self.paths.index = 0;
}
}
}
_ => {
let err_msg = format!("Error: \"{}\" is not a command", input_vec[0]);
return Err(err_msg);
}
}
Ok(())
}
}
Loading