Skip to content

Commit

Permalink
File rename & other features (#5)
Browse files Browse the repository at this point in the history
* generalize backend

* replace string with pathbuf

* add default impl

* add rename block

* add rename feature

* fix clippy issues

* add script saved notification

* add tick rate arg

* quote args
  • Loading branch information
jgalat committed Nov 23, 2020
1 parent ad6ff98 commit 09dddde
Show file tree
Hide file tree
Showing 6 changed files with 311 additions and 123 deletions.
121 changes: 87 additions & 34 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::{
collections::HashMap,
fs::File,
path::{Path, PathBuf},
time::Instant,
};

use crate::Opt;
Expand All @@ -16,21 +17,57 @@ pub enum TabId {

const TABS: [TabId; 2] = [TabId::Main, TabId::Script];

#[derive(PartialEq)]
#[derive(PartialEq, Clone)]
pub enum Action {
Skip(String),
Move(String, String),
MkDir(String),
Skip(PathBuf),
Move(PathBuf, PathBuf),
Rename(String),
MkDir(PathBuf),
}

impl Action {
pub fn is_poppable(&self) -> bool {
!matches!(self, Action::MkDir(_))
}

pub fn queue_step(&self) -> usize {
match self {
Action::Skip(_) | Action::Move(_, _) => 1,
_ => 0,
}
}
}

pub struct App {
pub tab: usize,
pub script_offset: (u16, u16),
pub images: Vec<String>,
pub images: Vec<PathBuf>,
pub current: usize,
pub key_mapping: HashMap<char, String>,
pub key_mapping: HashMap<char, PathBuf>,
pub actions: Vec<Action>,
pub output: String,
pub enable_input: bool,
pub input: Vec<char>,
pub input_idx: usize,
pub last_save: Option<Instant>,
}

impl Default for App {
fn default() -> Self {
App {
tab: 0,
script_offset: (0, 0),
current: 0,
images: vec![],
key_mapping: HashMap::new(),
actions: vec![],
output: "".to_string(),
enable_input: false,
input: vec![],
input_idx: 0,
last_save: None,
}
}
}

impl App {
Expand All @@ -39,17 +76,15 @@ impl App {
let (key_mapping, actions) = App::parse_key_mapping(opt.bind)?;

Ok(App {
tab: 0,
script_offset: (0, 0),
current: 0,
images,
key_mapping,
actions,
output: opt.output,
..App::default()
})
}

pub fn current_image(&self) -> Option<String> {
pub fn current_image(&self) -> Option<PathBuf> {
if self.current == self.images.len() {
return None;
}
Expand All @@ -58,17 +93,23 @@ impl App {
}

pub fn pop_action(&mut self) {
if self.current > 0 {
self.actions.pop();
self.current -= 1;
let last_action = self.actions.last().cloned();

if let Some(last_action) = last_action {
if last_action.is_poppable() {
self.actions.pop();
}
self.current -= last_action.queue_step();
}
}

pub fn push_action(&mut self, action: Action) {
if self.current < self.images.len() {
self.actions.push(action);
self.current += 1;
if self.current == self.images.len() {
return;
}

self.current += action.queue_step();
self.actions.push(action);
}

pub fn current_tab(&self) -> TabId {
Expand Down Expand Up @@ -106,15 +147,28 @@ impl App {
self.script_offset = (y, x + 1);
}

pub fn write(&self) -> Result<()> {
pub fn rename_current_image(&mut self) {
if let Some(current_image) = self.current_image() {
if let Some(name) = current_image.file_name() {
let name: Vec<char> = name.to_str().unwrap().chars().collect();
self.input_idx = name.len();
self.input = name;
self.enable_input = true;
}
}
}

pub fn write(&mut self) -> Result<()> {
let mut lines: Vec<String> = vec!["#!/bin/sh".to_string()];

for action in self.actions.iter() {
match action {
Action::MkDir(folder) => lines.push(format!("mkdir -p {}", folder)),
Action::Move(image_path, folder) => {
lines.push(format!("mv {} {}", image_path, folder))
}
Action::MkDir(folder) => lines.push(format!("mkdir -p \"{}\"", folder.display())),
Action::Move(image_path, folder) => lines.push(format!(
"mv \"{}\" \"{}\"",
image_path.display(),
folder.display()
)),
_ => {}
}
}
Expand All @@ -123,18 +177,18 @@ impl App {
let mut file = File::create(&self.output)?;
file.write_all(script.as_bytes())?;

self.last_save = Some(Instant::now());
Ok(())
}

pub fn parse_key_mapping(
args: Vec<(char, PathBuf)>,
) -> Result<(HashMap<char, String>, Vec<Action>)> {
) -> Result<(HashMap<char, PathBuf>, Vec<Action>)> {
let mut key_mapping = HashMap::new();
let mut actions = vec![];

for (key, path) in args.into_iter() {
let path = path.as_path();
let path_string = path.display().to_string();
for (key, path_buf) in args.into_iter() {
let path = path_buf.as_path();

if path.exists() && !path.is_dir() {
return Err(anyhow!(
Expand All @@ -144,36 +198,35 @@ impl App {
}

if !path.exists() {
actions.push(Action::MkDir(path_string.clone()));
actions.push(Action::MkDir(path_buf.clone()));
}

key_mapping.insert(key, path_string);
key_mapping.insert(key, path_buf);
}

Ok((key_mapping, actions))
}

pub fn parse_images(args: Vec<PathBuf>) -> Result<Vec<String>> {
let mut images: Vec<String> = vec![];
pub fn parse_images(args: Vec<PathBuf>) -> Result<Vec<PathBuf>> {
let mut images: Vec<PathBuf> = vec![];

for input in args.iter() {
for input in args.into_iter() {
let path = input.as_path();

if path.is_file() && App::is_image(&path) {
images.push(path.display().to_string());
images.push(input.clone());
}

if path.is_dir() {
for entry in path.read_dir()? {
if let Ok(entry) = entry {
let path = entry.path();

if !App::is_image(&path) {
if !App::is_image(path.as_path()) {
continue;
}

let path_str = path.display().to_string();
images.push(path_str);
images.push(path);
}
}
}
Expand Down
6 changes: 0 additions & 6 deletions src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,6 @@ pub struct EventsListener {
rx: Receiver<Event>,
}

impl Default for EventsListener {
fn default() -> Self {
EventsListener::new(Duration::from_millis(500))
}
}

impl EventsListener {
pub fn new(tick_rate: Duration) -> Self {
let (tx, rx) = unbounded::<Event>();
Expand Down
19 changes: 13 additions & 6 deletions src/image_display.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use anyhow::{anyhow, Result};
use std::{env, path::Path};
use std::{
env,
path::{Path, PathBuf},
};
use subprocess::{Popen, PopenConfig, Redirection};
use tui::layout::Rect;

Expand Down Expand Up @@ -39,7 +42,7 @@ impl ImageDisplay {
}
}

pub fn render_image(&mut self, image_path: String, block: Rect, terminal: Rect) -> Result<()> {
pub fn render_image(&mut self, image_path: PathBuf, block: Rect, terminal: Rect) -> Result<()> {
let input = self.w3m_input(image_path, block, terminal)?;
let mut process = Popen::create(
&[&self.path],
Expand All @@ -54,7 +57,7 @@ impl ImageDisplay {
Ok(())
}

fn w3m_input(&mut self, image_path: String, block: Rect, terminal: Rect) -> Result<String> {
fn w3m_input(&mut self, image_path: PathBuf, block: Rect, terminal: Rect) -> Result<String> {
let (fontw, fonth) = self.font_dimensions(terminal)?;

let start_x = (block.x as u32 + 1) * fontw;
Expand All @@ -79,14 +82,18 @@ impl ImageDisplay {

let input = format!(
"0;1;{};{};{};{};;;;;{}\n4;\n3;\n",
start_x, start_y, width, height, image_path
start_x,
start_y,
width,
height,
image_path.display()
);

Ok(input)
}

fn image_dimensions(&mut self, image_path: &str) -> Result<(u32, u32)> {
let input = format!("5;{}\n", image_path);
fn image_dimensions(&mut self, image_path: &PathBuf) -> Result<(u32, u32)> {
let input = format!("5;{}\n", image_path.display());
let mut process = Popen::create(
&[&self.path],
PopenConfig {
Expand Down
76 changes: 75 additions & 1 deletion src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@ fn handle_app_key(key: char, app: &mut App) {
}

fn handle_mapping_key(key: char, app: &mut App) {
if let Some(path) = app.key_mapping.get(&key).cloned() {
if let Some(mut path) = app.key_mapping.get_mut(&key).cloned() {
if let Some(image_path) = app.current_image() {
if let Some(Action::Rename(name)) = app.actions.last() {
path.push(name);
}
app.push_action(Action::Move(image_path, path));
}
}
Expand All @@ -39,3 +42,74 @@ pub fn handle_key_script(key: Key, app: &mut App) {
_ => {}
}
}

pub fn handle_key_input(key: Key, app: &mut App) {
match key {
Key::Ctrl('k') => {
app.input.drain(app.input_idx..app.input.len());
}
Key::Ctrl('u') => {
app.input.drain(..app.input_idx);
app.input_idx = 0;
}
Key::Ctrl('l') => {
app.input = vec![];
app.input_idx = 0;
}
Key::Ctrl('w') => {
if app.input_idx == 0 {
return;
}
let word_end = match app.input[..app.input_idx].iter().rposition(|&x| x != ' ') {
Some(index) => index + 1,
None => 0,
};
let word_start = match app.input[..word_end].iter().rposition(|&x| x == ' ') {
Some(index) => index + 1,
None => 0,
};
app.input.drain(word_start..app.input_idx);
app.input_idx = word_start;
}
Key::End | Key::Ctrl('e') => {
app.input_idx = app.input.len();
}
Key::Home | Key::Ctrl('a') => {
app.input_idx = 0;
}
Key::Left | Key::Ctrl('b') => {
if app.input_idx > 0 {
app.input_idx -= 1;
}
}
Key::Right | Key::Ctrl('f') => {
if app.input_idx < app.input.len() {
app.input_idx += 1;
}
}
Key::Esc => {
app.enable_input = false;
}
Key::Char('\n') => {
let input_str: String = app.input.iter().collect();
app.actions.push(Action::Rename(input_str));
app.enable_input = false;
}
Key::Backspace | Key::Ctrl('h') => {
if !app.input.is_empty() && app.input_idx > 0 {
app.input.remove(app.input_idx - 1);
app.input_idx -= 1;
}
}
Key::Delete | Key::Ctrl('d') => {
if !app.input.is_empty() && app.input_idx < app.input.len() {
app.input.remove(app.input_idx);
}
}
Key::Char(c) => {
app.input.insert(app.input_idx, c);
app.input_idx += 1;
}
_ => {}
}
}

0 comments on commit 09dddde

Please sign in to comment.