From eaf0d0ef38305451643218e8b70853910057a759 Mon Sep 17 00:00:00 2001 From: Albin Ekblom Date: Sun, 25 Dec 2022 21:01:39 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=89=20Add=20editor=20input=20navigatio?= =?UTF-8?q?n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 4 +- Cargo.toml | 2 +- src/input_string.rs | 176 ++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 21 ++++-- 4 files changed, 193 insertions(+), 10 deletions(-) create mode 100644 src/input_string.rs diff --git a/Cargo.lock b/Cargo.lock index a239993..3da5bf9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -366,9 +366,9 @@ dependencies = [ [[package]] name = "termion" -version = "1.5.6" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e" +checksum = "659c1f379f3408c7e5e84c7d0da6d93404e3800b6b9d063ba24436419302ec90" dependencies = [ "libc", "numtoa", diff --git a/Cargo.toml b/Cargo.toml index 6350cfe..a0b2d7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ readme = "readme.md" license = "MIT" [dependencies] -termion = "1.0" +termion = "2.0.1" log-update = "~0.1.0" default-editor = "~0.1.0" emoji-commit-type = "~0.1.1" diff --git a/src/input_string.rs b/src/input_string.rs new file mode 100644 index 0000000..fbbc04c --- /dev/null +++ b/src/input_string.rs @@ -0,0 +1,176 @@ +use ansi_term::Colour::White; + +pub struct InputString { + value: String, + position: usize, + is_control_input: bool, +} + +impl InputString { + pub fn new() -> Self { + Self { + value: String::new(), + position: 0, + is_control_input: false, + } + } + + pub fn as_str(&self) -> &str { + self.value.as_str() + } + + pub fn trim(&self) -> &str { + self.value.trim() + } + pub fn push(&mut self, c: char) { + if self.is_control_input { + self.handle_control(c); + return; + } + if self.position == self.value.len() { + self.value.push(c); + } else { + let mut new_string = String::new(); + new_string.push_str(&self.value.as_str()[0..self.position]); + new_string.push(c); + new_string.push_str(&self.value.as_str()[self.position..]); + self.value = new_string; + } + self.position += 1; + } + + pub fn delete(&mut self) { + if self.position == self.value.len() { + return; + } + if self.position == 0 { + self.value.remove(0); + } else { + let mut new_string = String::new(); + new_string.push_str(&self.value.as_str()[0..self.position]); + new_string.push_str(&self.value.as_str()[self.position + 1..]); + self.value = new_string; + } + } + + pub fn backspace(&mut self) { + if self.position == 0 { + return; + } + if self.position == self.value.len() { + self.value.pop(); + } else { + let mut new_string = String::new(); + new_string.push_str(&self.value.as_str()[0..self.position - 1]); + new_string.push_str(&self.value.as_str()[self.position..]); + self.value = new_string; + } + self.position -= 1; + } + + pub fn go_char_left(&mut self) { + if self.position > 1 { + self.position -= 1; + } else { + self.position = 0; + } + } + pub fn go_char_right(&mut self) { + if self.position < self.value.len() { + self.position += 1; + } else { + self.position = self.value.len(); + } + } + pub fn handle_control(&mut self, c: char) { + if !self.is_control_input && c.is_control() { + self.is_control_input = true + } + if self.is_control_input { + match c { + 'D' => self.go_word_left(), + 'C' => self.go_word_right(), + _ => { return; } + } + self.is_control_input = false + } + + match c { + 'b' => self.go_word_left(), + 'f' => self.go_word_right(), + _ => {} + } + } + pub fn go_word_left(&mut self) { + if self.position == 0 { + return; + } + let mut new_position = self.position; + while new_position > 0 + && self + .value + .as_str() + .chars() + .nth(new_position - 1) + .unwrap() + .is_whitespace() + { + new_position -= 1; + } + while new_position > 0 + && !self + .value + .as_str() + .chars() + .nth(new_position - 1) + .unwrap() + .is_whitespace() + { + new_position -= 1; + } + self.position = new_position; + } + pub fn go_word_right(&mut self) { + if self.position == self.value.len() { + return; + } + let mut new_position = self.position; + while new_position < self.value.len() + && self + .value + .as_str() + .chars() + .nth(new_position) + .unwrap() + .is_whitespace() + { + new_position += 1; + } + while new_position < self.value.len() + && !self + .value + .as_str() + .chars() + .nth(new_position) + .unwrap() + .is_whitespace() + { + new_position += 1; + } + self.position = new_position; + } + pub fn format(&self) -> String { + if self.position == self.value.len() { + format!("{}{}", &self.value.as_str(), White.underline().paint(" ")) + } else { + format!( + "{}{}{}", + &self.value.as_str()[0..self.position], + White + .underline() + .paint(&self.value.as_str()[self.position..self.position + 1]), + &self.value.as_str()[self.position + 1..] + ) + } + } +} diff --git a/src/main.rs b/src/main.rs index 11ce90d..0eff58c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,10 +14,14 @@ use termion::raw::IntoRawMode; use emoji_commit_type::CommitType; use log_update::LogUpdate; use structopt::StructOpt; -use ansi_term::Colour::{RGB, Green, Red, White}; +use ansi_term::Colour::{RGB, Green, Red}; + mod commit_rules; mod git; +mod input_string; + +use crate::input_string::InputString; impl fmt::Display for commit_rules::CommitRuleValidationResult { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -80,20 +84,19 @@ fn collect_commit_message(selected_emoji: &'static str, launch_editor: &mut bool let mut key_stream = stdin().keys(); let mut aborted = false; - let mut input = String::new(); + let mut input = InputString::new(); loop { - let rule_text = commit_rules::check_message(&input) + let rule_text = commit_rules::check_message(&input.as_str()) .map(|result| format!("{}", result)) .collect::>() .join("\r\n"); let text = format!( - "\r\nRemember the seven rules of a great Git commit message:\r\n\r\n{}\r\n\r\n{}\r\n{} {}{}", + "\r\nRemember the seven rules of a great Git commit message:\r\n\r\n{}\r\n\r\n{}\r\n{} {}", rule_text, RGB(105, 105, 105).paint("Enter - finish, Ctrl-C - abort, Ctrl-E - continue editing in $EDITOR"), selected_emoji, - input, - White.underline().paint(" ") + input.format() ); log_update.render(&text).unwrap(); @@ -101,8 +104,12 @@ fn collect_commit_message(selected_emoji: &'static str, launch_editor: &mut bool match key_stream.next().unwrap().unwrap() { Key::Ctrl('c') => { aborted = true; break }, Key::Char('\n') => break, + Key::Alt(c) => input.handle_control(c), Key::Char(c) => input.push(c), - Key::Backspace => { input.pop(); }, + Key::Backspace => input.backspace(), + Key::Delete => input.delete(), + Key::Left => input.go_char_left(), + Key::Right => input.go_char_right(), Key::Ctrl('e') => { *launch_editor = true; break }, _ => {}, }