use crossterm::cursor::{Hide, MoveDown, MoveToColumn, MoveUp, Show};
use crossterm::event::{Event, KeyCode, read};
use crossterm::execute;
use crossterm::style::{Attribute, Color::Rgb, Print, SetAttribute, SetForegroundColor};
use crossterm::terminal::{Clear, ClearType, disable_raw_mode, enable_raw_mode};
use std::io;
use std::process::Command;
const MAX_SELECTION_LENGTH: usize = 6;
fn main() -> io::Result<()> {
let emojis: Vec<&'static str> = return_emojis();
let mut selection: usize = 2;
let mut offset: usize = 0;
let mut user_input: String = String::new();
execute!(io::stdout(), Hide).expect("Failed to hide cursor");
let mut filtered_emojis: Vec<&&str> = emojis
.iter()
.filter(|&emoji| emoji.to_lowercase().contains(&user_input.to_lowercase()))
.collect();
draw_menu(&filtered_emojis, offset, selection, &user_input);
enable_raw_mode().expect("Failed to enable raw mode");
loop {
match read()? {
Event::Key(event) => match event.code {
KeyCode::Down => {
let offset_ = offset as isize;
let selection_ = selection as isize;
let max_selection_length_ = MAX_SELECTION_LENGTH as isize;
if offset_ < filtered_emojis.len() as isize - max_selection_length_ {
offset += 1;
}
if selection >= return_length(&filtered_emojis) && filtered_emojis.len() != 0 {
selection = return_length(&filtered_emojis) - 1;
} else if selection_ < return_length(&filtered_emojis) as isize - 1
&& offset_ >= filtered_emojis.len() as isize - max_selection_length_
{
selection += 1;
}
redraw_menu(&filtered_emojis, offset, selection, &user_input);
}
KeyCode::Up => {
if offset > 0 {
offset -= 1;
} else if selection >= 1 {
selection -= 1;
}
redraw_menu(&filtered_emojis, offset, selection, &user_input);
}
KeyCode::Enter => {
if filtered_emojis.len() != 0 {
delete_menu(&filtered_emojis);
break;
}
}
KeyCode::Char(c) => {
offset = 0;
selection = 0;
filtered_emojis = emojis
.iter()
.filter(|&emoji| emoji.to_lowercase().contains(&user_input.to_lowercase()))
.collect();
delete_menu(&filtered_emojis);
user_input += &c.to_string();
filtered_emojis = emojis
.iter()
.filter(|&emoji| emoji.to_lowercase().contains(&user_input.to_lowercase()))
.collect();
draw_menu(&filtered_emojis, offset, selection, &user_input);
}
KeyCode::Backspace => {
filtered_emojis = emojis
.iter()
.filter(|&emoji| emoji.to_lowercase().contains(&user_input.to_lowercase()))
.collect();
delete_menu(&filtered_emojis);
user_input.pop();
filtered_emojis = emojis
.iter()
.filter(|&emoji| emoji.to_lowercase().contains(&user_input.to_lowercase()))
.collect();
draw_menu(&filtered_emojis, offset, selection, &user_input);
}
_ => {}
},
_ => {}
}
}
let gitmoji: Vec<char> = filtered_emojis[offset + selection].chars().collect();
let gitmoji = gitmoji[0].to_string();
let headline = "? Gitmoji: ".to_string() + &gitmoji + "!";
cursor_to_start();
execute!(
io::stdout(),
SetAttribute(Attribute::Bold),
SetForegroundColor(Rgb {
r: 180,
g: 190,
b: 254,
}),
Print(headline),
SetAttribute(Attribute::Reset),
)
.expect("failed to print selected gitmoji");
execute!(io::stdout(), MoveDown(2)).expect("Failed to move cursor down by two lines");
let mut commit_message: String = String::new();
reload_commit_message(&commit_message, false);
loop {
match read()? {
Event::Key(event) => match event.code {
KeyCode::Char(c) => {
commit_message += &c.to_string();
reload_commit_message(&commit_message, false);
}
KeyCode::Backspace => {
commit_message.pop();
reload_commit_message(&commit_message, false);
}
KeyCode::Enter => {
reload_commit_message(&commit_message, true);
break;
}
_ => {}
},
_ => {}
}
}
let final_commit_message = gitmoji + " " + &commit_message;
cursor_to_start();
disable_raw_mode().expect("Failed to disable raw mode");
execute!(io::stdout(), Show).expect("Failed to unhide cursor");
Command::new("git").args(["commit", "-m", final_commit_message.as_str()]).status().expect("Failed to run git");
Ok(())
}
fn reload_commit_message(commit_message: &String, end: bool) {
let text = if end { "? Commit title: " } else { "? Enter commit title: " };
let commit_message = commit_message.to_owned() + if end { "\n" } else { "โ\n" };
cursor_to_start();
execute!(
io::stdout(),
MoveUp(1),
Clear(ClearType::CurrentLine),
SetAttribute(Attribute::Bold),
SetForegroundColor(Rgb {
r: 180,
g: 190,
b: 254,
}),
Print(text),
SetAttribute(Attribute::Reset),
Print(commit_message),
)
.expect("Failed to reload title input");
}
fn redraw_menu(emojis: &Vec<&&str>, offset: usize, selection: usize, user_input: &String) {
delete_menu(emojis);
draw_menu(emojis, offset, selection, user_input);
}
fn draw_menu(emojis: &Vec<&&str>, offset: usize, selection: usize, user_input: &String) {
cursor_to_start();
let user_input: String = user_input.to_string() + "โ\n";
execute!(
io::stdout(),
SetAttribute(Attribute::Bold),
SetForegroundColor(Rgb {
r: 180,
g: 190,
b: 254,
}),
Print("? Choose a gitmoji! ".to_string()),
SetAttribute(Attribute::Reset),
SetForegroundColor(Rgb {
r: 186,
g: 194,
b: 222,
}),
Print(user_input),
SetAttribute(Attribute::Reset),
)
.expect("Failed to print select text");
for i in 0..MAX_SELECTION_LENGTH {
cursor_to_start();
if i == selection {
execute!(
io::stdout(),
SetForegroundColor(Rgb {
r: 180,
g: 190,
b: 254,
}),
Print("โ ".to_string()),
)
.expect("Failed to print 'โ '")
} else {
execute!(io::stdout(), Print(" ".to_string())).expect("Failed to print ' '")
}
if i + offset < emojis.len() {
execute!(
io::stdout(),
Print(emojis[i + offset]),
SetAttribute(Attribute::Reset),
Print("\n".to_string()),
)
.expect("Failed to print menu");
}
}
}
fn delete_menu(emojis: &Vec<&&str>) {
for _i in 0..return_length(emojis) + 1 {
execute!(io::stdout(), MoveUp(1), Clear(ClearType::CurrentLine)).expect("Failed to clear");
}
}
fn return_length(emojis: &Vec<&&str>) -> usize {
if emojis.len() > MAX_SELECTION_LENGTH {
MAX_SELECTION_LENGTH
} else {
emojis.len()
}
}
fn cursor_to_start() {
execute!(io::stdout(), MoveToColumn(0))
.expect("Failed to move cursor to the start of the line");
}
fn return_emojis() -> Vec<&'static str> {
vec![
"๐จ - Improve structure / format of the code",
"โก๏ธ - Improve performance",
"๐ฅ - Remove code or files",
"๐ - Fix a bug",
"๐๏ธ - Critical hotfix",
"โจ - Introduce new features",
"๐ - Add or update documentation",
"๐ - Deploy stuff",
"๐ - Add or update the UI and style files",
"๐ - Begin a project",
"โ
- Add, update, or pass tests",
"๐๏ธ - Fix security or privacy issues",
"๐ - Add or update secrets",
"๐ - Release / Version tags",
"๐จ - Fix compiler / linter warnings",
"๐ง - Work in progress",
"๐ - Fix CI Build",
"โฌ๏ธ - Downgrade dependencies",
"โฌ๏ธ - Upgrade dependencies",
"๐ - Pin dependencies to specific versions",
"๐ท - Add or update CI build system",
"๐ - Add or update analytics or track code",
"โป๏ธ - Refactor code",
"โ - Add a dependency",
"โ - Remove a dependency",
"๐ง - Add or update configuration files",
"๐จ - Add or update development scripts",
"๐ - Internationalization and localization",
"โ๏ธ - Fix typos",
"๐ฉ - Write bad code that needs to be improved",
"โช๏ธ - Revert changes",
"๐ - Merge branches",
"๐ฆ๏ธ - Add or update compiled files or packages",
"๐ฝ๏ธ - Update code due to external API changes",
"๐ - Move or rename resources (e.g.: files, paths, routes)",
"๐ - Add or update license",
"๐ฅ - Introduce breaking changes",
"๐ฑ - Add or update assets",
"โฟ๏ธ - Improve accessibility",
"๐ก - Add or update comments in source code",
"๐ป - Write code drunkenly",
"๐ฌ - Add or update text and literals",
"๐๏ธ - Perform database related changes",
"๐ - Add or update logs",
"๐ - Remove logs",
"๐ฅ - Add or update contributor(s)",
"๐ธ - Improve user experience / usability",
"๐๏ธ - Make architectural changes",
"๐ฑ - Work on responsive design",
"๐คก - Mock things",
"๐ฅ - Add or update an easter egg",
"๐ - Add or update a .gitignore file",
"๐ธ - Add or update snapshots",
"โ๏ธ - Perform experiments",
"๐๏ธ - Improve SEO",
"๐ท๏ธ - Add or update types",
"๐ฑ - Add or update seed files",
"๐ฉ - Add, update, or remove feature flags",
"๐ฅ
- Catch errors",
"๐ซ - Add or update animations and transitions",
"๐๏ธ - Deprecate code that needs to be cleaned up",
"๐ - Work on code related to authorization, roles and permissions",
"๐ฉน - Simple fix for a non-critical issue",
"๐ง - Data exploration/inspection",
"โฐ๏ธ - Remove dead code",
"๐งช - Add a failing test",
"๐ - Add or update business logic",
"๐ฉบ - Add or update healthcheck",
"๐งฑ - Infrastructure related changes",
"๐งโ๐ป Improve developer experience",
"๐ธ - Add sponsorships or money related infrastructure",
"๐งต - Add or update code related to multithreading or concurrency",
"๐ฆบ - Add or update code related to validation",
"โ๏ธ - Improve offline support",
]
}