Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
764 changes: 675 additions & 89 deletions Cargo.lock

Large diffs are not rendered by default.

18 changes: 17 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,29 @@ readme = "readme.md"
license = "MIT"

[dependencies]
termion = "1.0"
termion = "4"
log-update = "~0.1.0"
default-editor = "~0.1.0"
emoji-commit-type = "~0.1.1"
git2 = "~0.15.0"
structopt = "~0.3"
ansi_term = "~0.12"
# tui-input = { version = "0.11.1", features = ["termion"], default-features = false }
rustyline = { git = "https://github.com/kkawakam/rustyline.git", branch = "master", features = [
"derive",
] }
rustyline-derive = { git = "https://github.com/kkawakam/rustyline.git", branch = "master" }
tui-input = { version = "0.11.1", features = [
"termion",
], default-features = false }
input-string = { path = "../input-string", features = [
"termion",
"log-update",
], default-features = false }


[dev-dependencies]
tempfile = "~3.14.0"

[profile.release]
codegen-units = 1
Expand Down
113 changes: 113 additions & 0 deletions src/crossterm_input.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
use std::io::{stdin, stdout, Result, Write};
use termion::cursor::{Hide, Show};
use termion::event::{Event, Key};
use termion::input::{TermRead, TermReadEventsAndRaw};
use termion::raw::IntoRawMode;
use termion::screen::IntoAlternateScreen;
use tui_input::backend::termion as backend;
use tui_input::backend::termion::EventHandler;
use tui_input::{Input, InputRequest, InputResponse};

use crate::commit_rules;
use ansi_term::Color::RGB;

pub fn to_input_request(evt: &Event) -> Option<InputRequest> {
use InputRequest::*;
match *evt {

Event::Key(Key::Backspace) => Some(DeletePrevChar),
Event::Key(Key::Ctrl('h')) => Some(DeletePrevWord),
Event::Key(Key::Delete) => Some(DeleteNextChar),
Event::Key(Key::AltLeft) | Event::Key(Key::ShiftLeft) => Some(GoToPrevWord),
Event::Key(Key::AltRight) | Event::Key(Key::ShiftRight) => Some(GoToNextWord),

Event::Key(Key::Left) | Event::Key(Key::Ctrl('b')) => Some(GoToPrevChar),
Event::Key(Key::Right) | Event::Key(Key::Ctrl('f')) => Some(GoToNextChar),
// Event::Key(Key::AltRight) => Some(GoToNextWord),
Event::Key(Key::Ctrl('u')) => Some(DeleteLine),
Event::Key(Key::Ctrl('w')) => Some(DeletePrevWord),
// Event::Key(Key::Ctrl(Key::Delete)) => Some(DeleteNextWord),
Event::Key(Key::Ctrl('a')) | Event::Key(Key::Home) => Some(GoToStart),
Event::Key(Key::Ctrl('e')) | Event::Key(Key::End) => Some(GoToEnd),
Event::Key(Key::Char('\t')) => None,
Event::Key(Key::Char(c)) => Some(InsertChar(c)),
_ => None,
}
}

#[derive(Default)]
struct CrosstermInput(Input);

impl From<&str> for CrosstermInput {
fn from(s: &str) -> Self {
CrosstermInput(Input::new(s.to_string()))
}
}

impl std::fmt::Display for CrosstermInput {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}

impl CrosstermInput {
fn value(&self) -> &str {
&self.0.value()
}

fn cursor(&self) -> usize {
self.0.cursor()
}
}

impl EventHandler for CrosstermInput {
fn handle_event(&mut self, evt: &Event) -> InputResponse {
let request = to_input_request(evt)?;
self.0.handle(request)
}
}

fn format_message(selected_emoji: &'static str, input: &str) -> String {
let rule_text = commit_rules::check_message(input)
.map(|result| format!("{}", result))
.collect::<Vec<_>>()
.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{}",
rule_text,
RGB(105, 105, 105).paint("Enter - finish, Ctrl-C - abort, Ctrl-E - continue editing in $EDITOR"),
selected_emoji,
);

return text;
}

pub fn crossterm_input() -> Result<()> {
let mut input: CrosstermInput = "Hello ".into();
{
let stdin = stdin();
let mut stdout = stdout().into_raw_mode()?;//.into_alternate_screen()?;

// write!(&mut stdout, "{}", Hide)?;
backend::write(&mut stdout, input.value(), input.cursor(), (0, 0), 15)?;
stdout.flush()?;

for events_and_raw in stdin.events_and_raw() {
let (evt, raw) = events_and_raw?;
println!("evt: {:?}, raw: {:?}", evt, raw);
if evt == Event::Key(Key::Esc) || evt == Event::Key(Key::Char('\n')) {
break;
}

if input.handle_event(&evt).is_some() {
backend::write(&mut stdout, input.value(), input.cursor(), (0, 0), 15)?;
stdout.flush()?;
}
}

write!(stdout, "{}", Show)?;
}

println!("{}", input);
Ok(())
}
249 changes: 249 additions & 0 deletions src/input_string.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
use ansi_term::Colour::White;

pub struct InputString {
value: String,
position: usize,
is_control_input: bool,
}

impl Default for InputString {
fn default() -> Self {
Self {
value: String::new(),
position: 0,
is_control_input: false,
}
}
}

impl InputString {
pub fn new(initial_value: Option<String>) -> Self {
Self {
value: initial_value.unwrap_or_default(),
..Default::default()
}
}

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 go_to_start(&mut self) {
self.position = 0;
}

pub fn go_to_end(&mut self) {
self.position = self.value.len();
}

pub fn delete_word_left(&mut self) {
if self.position == 0 {
return;
}

let mut pos = self.position;
let mut found_word = false;

while pos > 0 {
pos -= 1;
let c = self.value.chars().nth(pos).unwrap();
if c.is_whitespace() {
if found_word {
pos += 1;
break;
}
} else {
found_word = true;
}
}

let mut new_string = String::new();
new_string.push_str(&self.value.as_str()[0..pos]);
new_string.push_str(&self.value.as_str()[self.position..]);
self.value = new_string;
self.position = pos;
}

pub fn delete_word_right(&mut self) {
if self.position >= self.value.len() {
return;
}

let mut pos = self.position;
let mut found_word = false;

while pos < self.value.len() {
let c = self.value.chars().nth(pos).unwrap();
if c.is_whitespace() {
if found_word {
break;
}
} else {
found_word = true;
}
pos += 1;
}

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()[pos..]);
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..]
)
}
}
}
Loading