summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTim Oram <me@mitmaro.ca>2017-02-11 18:17:42 -0330
committerGitHub <noreply@github.com>2017-02-11 18:17:42 -0330
commite96410defd555fd452f89faf02ed994bbc5d8f77 (patch)
tree5d7279c5564131a577a1cf91f79ecbd0ee708c21
parent59c8a523ae4f8a59d9c66b155a5b2aad6c8234f3 (diff)
parentc45dd804de56be92af698b8721402f0aad7cebd2 (diff)
Merge pull request #22 from MitMaro/module-refactor
Refactor into modules
-rw-r--r--.travis.yml9
-rw-r--r--src/action.rs69
-rw-r--r--src/application.rs304
-rw-r--r--src/git_interactive.rs134
-rw-r--r--src/line.rs99
-rw-r--r--src/main.rs516
-rw-r--r--src/mocks.rs55
-rw-r--r--src/utils.rs14
-rw-r--r--src/window.rs243
-rw-r--r--test/git-rebase-todo-all-actions.in14
-rw-r--r--test/git-rebase-todo-long.in21
-rw-r--r--test/git-rebase-todo-short.in3
12 files changed, 996 insertions, 485 deletions
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..c2512c1
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,9 @@
+language: rust
+rust:
+ - stable
+ - beta
+ - nightly
+matrix:
+ allow_failures:
+ - rust: nightly
+
diff --git a/src/action.rs b/src/action.rs
new file mode 100644
index 0000000..8ecafad
--- /dev/null
+++ b/src/action.rs
@@ -0,0 +1,69 @@
+
+#[derive(PartialEq, Debug)]
+pub enum Action {
+ Pick,
+ Reword,
+ Edit,
+ Squash,
+ Fixup,
+ Drop
+}
+
+pub fn action_from_str(s: &str) -> Result<Action, String> {
+ match s {
+ "pick" | "p" => Ok(Action::Pick),
+ "reword" | "r" => Ok(Action::Reword),
+ "edit" | "e" => Ok(Action::Edit),
+ "squash" | "s" => Ok(Action::Squash),
+ "fixup" | "f" => Ok(Action::Fixup),
+ "drop" | "d" => Ok(Action::Drop),
+ _ => Err(format!("Invalid action: {}", s))
+ }
+}
+
+pub fn action_to_str(action: &Action) -> String {
+ String::from(match *action {
+ Action::Pick => "pick",
+ Action::Reword => "reword",
+ Action::Edit => "edit",
+ Action::Squash => "squash",
+ Action::Fixup => "fixup",
+ Action::Drop => "drop"
+ })
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{
+ Action,
+ action_from_str,
+ action_to_str
+ };
+
+ #[test]
+ fn action_to_str_all() {
+ assert_eq!(action_to_str(&Action::Pick), "pick");
+ assert_eq!(action_to_str(&Action::Reword), "reword");
+ assert_eq!(action_to_str(&Action::Edit), "edit");
+ assert_eq!(action_to_str(&Action::Squash), "squash");
+ assert_eq!(action_to_str(&Action::Fixup), "fixup");
+ assert_eq!(action_to_str(&Action::Drop), "drop");
+ }
+
+ #[test]
+ fn action_from_str_all() {
+ assert_eq!(action_from_str("pick"), Ok(Action::Pick));
+ assert_eq!(action_from_str("p"), Ok(Action::Pick));
+ assert_eq!(action_from_str("reword"), Ok(Action::Reword));
+ assert_eq!(action_from_str("r"), Ok(Action::Reword));
+ assert_eq!(action_from_str("edit"), Ok(Action::Edit));
+ assert_eq!(action_from_str("e"), Ok(Action::Edit));
+ assert_eq!(action_from_str("squash"), Ok(Action::Squash));
+ assert_eq!(action_from_str("s"), Ok(Action::Squash));
+ assert_eq!(action_from_str("fixup"), Ok(Action::Fixup));
+ assert_eq!(action_from_str("f"), Ok(Action::Fixup));
+ assert_eq!(action_from_str("drop"), Ok(Action::Drop));
+ assert_eq!(action_from_str("d"), Ok(Action::Drop));
+ }
+}
+
diff --git a/src/application.rs b/src/application.rs
new file mode 100644
index 0000000..ad61617
--- /dev/null
+++ b/src/application.rs
@@ -0,0 +1,304 @@
+use action::Action;
+use git_interactive::GitInteractive;
+
+use window::{
+ Input,
+ Window
+};
+
+const EXIT_CODE_GOOD: i32 = 0;
+const EXIT_CODE_WRITE_ERROR: i32 = 8;
+
+pub enum State {
+ List,
+ Help
+}
+
+pub struct Application {
+ pub exit_code: Option<i32>,
+ window: Window,
+ git_interactive: GitInteractive,
+ state: State
+}
+
+impl Application {
+ pub fn new(git_interactive: GitInteractive, window: Window) -> Self {
+ Application {
+ git_interactive: git_interactive,
+ window: window,
+ exit_code: None,
+ state: State::List
+ }
+ }
+
+ pub fn process_input(&mut self) {
+ match self.state {
+ State::List => self.process_list_input(),
+ State::Help => self.process_help_input()
+ }
+ }
+
+ pub fn draw(&self) {
+ match self.state {
+ State::List => {
+ self.window.draw(
+ self.git_interactive.get_lines(),
+ self.git_interactive.get_selected_line_index()
+ );
+ },
+ State::Help => {
+ self.window.draw_help();
+ }
+ }
+ }
+
+ fn abort(&mut self) {
+ self.git_interactive.clear();
+ self.finish();
+ }
+
+ fn finish(&mut self) {
+ self.exit_code = Some(EXIT_CODE_GOOD);
+ }
+
+ pub fn end(&mut self) -> Result<(), String>{
+ self.window.end();
+ match self.git_interactive.write_file() {
+ Ok(_) => {},
+ Err(msg) => {
+ self.exit_code = Some(EXIT_CODE_WRITE_ERROR);
+ return Err(msg)
+ }
+ }
+ Ok(())
+ }
+
+ fn process_help_input(&mut self) {
+ self.window.window.getch();
+ self.state = State::List;
+ }
+
+ fn process_list_input(&mut self) {
+ match self.window.window.getch() {
+ Some(Input::Character(c)) if c == '?' => {
+ self.state = State::Help;
+ },
+ Some(Input::Character(c))
+ if (c == 'Q') || (c == 'q' && self.window.confirm("Are you sure you want to abort"))
+ => self.abort(),
+ Some(Input::Character(c))
+ if (c == 'W') || (c == 'w' && self.window.confirm("Are you sure you want to rebase"))
+ => self.finish(),
+ Some(Input::Character(c))
+ if c == 'p' => self.git_interactive.set_selected_line_action(Action::Pick),
+ Some(Input::Character(c))
+ if c == 'r' => self.git_interactive.set_selected_line_action(Action::Reword),
+ Some(Input::Character(c))
+ if c == 'e' => self.git_interactive.set_selected_line_action(Action::Edit),
+ Some(Input::Character(c))
+ if c == 's' => self.git_interactive.set_selected_line_action(Action::Squash),
+ Some(Input::Character(c))
+ if c == 'f' => self.git_interactive.set_selected_line_action(Action::Fixup),
+ Some(Input::Character(c))
+ if c == 'd' => self.git_interactive.set_selected_line_action(Action::Drop),
+ Some(Input::Character(c)) if c == 'j' => {
+ self.git_interactive.swap_selected_down();
+ self.reset_top();
+ },
+ Some(Input::Character(c)) if c == 'k' => {
+ self.git_interactive.swap_selected_up();
+ self.reset_top();
+ },
+ Some(Input::KeyDown) => {
+ self.git_interactive.move_cursor_down(1);
+ self.reset_top();
+ },
+ Some(Input::KeyUp) => {
+ self.git_interactive.move_cursor_up(1);
+ self.reset_top();
+ },
+ Some(Input::KeyPPage) => {
+ self.git_interactive.move_cursor_up(5);
+ self.reset_top();
+ },
+ Some(Input::KeyNPage) => {
+ self.git_interactive.move_cursor_down(5);
+ self.reset_top();
+ },
+ Some(Input::KeyResize) => self.reset_top(),
+ _ => {}
+ }
+ ()
+ }
+
+ fn reset_top(&mut self) {
+ self.window.set_top(
+ self.git_interactive.get_lines().len(),
+ *self.git_interactive.get_selected_line_index()
+ )
+ }
+}
+
+
+#[cfg(test)]
+mod tests {
+ use super::{
+ Application,
+ };
+ use git_interactive::GitInteractive;
+ use window::{
+ Window,
+ Input
+ };
+ use action::Action;
+
+ #[test]
+ fn application_read_all_actions() {
+ let gi = GitInteractive::new_from_filepath("test/git-rebase-todo-all-actions.in").unwrap();
+ let window = Window::new();
+ let mut app = Application::new(gi, window);
+ assert_eq!(app.git_interactive.get_lines().len(), 12);
+ }
+
+ #[test]
+ fn application_scroll_basic() {
+ let gi = GitInteractive::new_from_filepath("test/git-rebase-todo-long.in").unwrap();
+ let window = Window::new();
+ let mut app = Application::new(gi, window);
+ app.window.window.next_char = Input::KeyDown;
+ app.process_input();
+ assert_eq!(*app.git_interactive.get_selected_line_index(), 2);
+ app.window.window.next_char = Input::KeyUp;
+ app.process_input();
+ assert_eq!(*app.git_interactive.get_selected_line_index(), 1);
+ app.window.window.next_char = Input::KeyNPage;
+ app.process_input();
+ assert_eq!(*app.git_interactive.get_selected_line_index(), 6);
+ app.window.window.next_char = Input::KeyPPage;
+ app.process_input();
+ assert_eq!(*app.git_interactive.get_selected_line_index(), 1);
+ }
+
+ #[test]
+ fn application_scroll_limits() {
+ let gi = GitInteractive::new_from_filepath("test/git-rebase-todo-short.in").unwrap();
+ let window = Window::new();
+ let mut app = Application::new(gi, window);
+ app.window.window.next_char = Input::KeyUp;
+ app.process_input();
+ app.process_input();
+ app.window.window.next_char = Input::KeyPPage;
+ app.process_input();
+ assert_eq!(*app.git_interactive.get_selected_line_index(), 1);
+ app.window.window.next_char = Input::KeyDown;
+ app.process_input();
+ app.process_input();
+ app.process_input();
+ app.process_input();
+ app.process_input();
+ app.window.window.next_char = Input::KeyNPage;
+ app.process_input();
+ assert_eq!(*app.git_interactive.get_selected_line_index(), 3);
+ }
+
+ #[test]
+ fn application_set_pick() {
+ let gi = GitInteractive::new_from_filepath("test/git-rebase-todo-all-actions.in").unwrap();
+ let window = Window::new();
+ let mut app = Application::new(gi, window);
+ // first item is already pick
+ app.window.window.next_char = Input::KeyDown;
+ app.process_input();
+ app.window.window.next_char = Input::Character('p');
+ app.process_input();
+ assert_eq!(*app.git_interactive.get_lines()[1].get_action(), Action::Pick);
+ }
+
+ #[test]
+ fn application_set_reword() {
+ let gi = GitInteractive::new_from_filepath("test/git-rebase-todo-all-actions.in").unwrap();
+ let window = Window::new();
+ let mut app = Application::new(gi, window);
+ app.window.window.next_char = Input::Character('r');
+ app.process_input();
+ assert_eq!(*app.git_interactive.get_lines()[0].get_action(), Action::Reword);
+ }
+
+ #[test]
+ fn application_set_edit() {
+ let gi = GitInteractive::new_from_filepath("test/git-rebase-todo-all-actions.in").unwrap();
+ let window = Window::new();
+ let mut app = Application::new(gi, window);
+ app.window.window.next_char = Input::Character('e');
+ app.process_input();
+ assert_eq!(*app.git_interactive.get_lines()[0].get_action(), Action::Edit);
+ }
+
+ #[test]
+ fn application_set_squash() {
+ let gi = GitInteractive::new_from_filepath("test/git-rebase-todo-all-actions.in").unwrap();
+ let window = Window::new();
+ let mut app = Application::new(gi, window);
+ app.window.window.next_char = Input::Character('s');
+ app.process_input();
+ assert_eq!(*app.git_interactive.get_lines()[0].get_action(), Action::Squash);
+ }
+
+ #[test]
+ fn application_set_drop() {
+ let gi = GitInteractive::new_from_filepath("test/git-rebase-todo-all-actions.in").unwrap();
+ let window = Window::new();
+ let mut app = Application::new(gi, window);
+ app.window.window.next_char = Input::Character('d');
+ app.process_input();
+ assert_eq!(*app.git_interactive.get_lines()[0].get_action(), Action::Drop);
+ }
+
+ #[test]
+ fn application_swap_down() {
+ let gi = GitInteractive::new_from_filepath("test/git-rebase-todo-all-actions.in").unwrap();
+ let window = Window::new();
+ let mut app = Application::new(gi, window);
+ app.window.window.next_char = Input::Character('j');
+ app.process_input();
+ assert_eq!(*app.git_interactive.get_lines()[0].get_hash(), "bbb");
+ assert_eq!(*app.git_interactive.get_lines()[1].get_hash(), "aaa");
+ assert_eq!(*app.git_interactive.get_selected_line_index(), 2);
+ }
+
+ #[test]
+ fn application_swap_up() {
+ let gi = GitInteractive::new_from_filepath("test/git-rebase-todo-all-actions.in").unwrap();
+ let window = Window::new();
+ let mut app = Application::new(gi, window);
+ app.window.window.next_char = Input::KeyDown;
+ app.process_input();
+ app.window.window.next_char = Input::Character('k');
+ app.process_input();
+ assert_eq!(*app.git_interactive.get_lines()[0].get_hash(), "bbb");
+ assert_eq!(*app.git_interactive.get_lines()[1].get_hash(), "aaa");
+ assert_eq!(*app.git_interactive.get_selected_line_index(), 1);
+ }
+
+ #[test]
+ fn application_quit() {
+ let gi = GitInteractive::new_from_filepath("test/git-rebase-todo-all-actions.in").unwrap();
+ let window = Window::new();
+ let mut app = Application::new(gi, window);
+ app.window.window.next_char = Input::Character('Q');
+ app.process_input();
+ assert_eq!(app.exit_code.unwrap(), 0);
+ assert!(app.git_interactive.get_lines().is_empty());
+ }
+
+ #[test]
+ fn application_finish() {
+ let gi = GitInteractive::new_from_filepath("test/git-rebase-todo-all-actions.in").unwrap();
+ let window = Window::new();
+ let mut app = Application::new(gi, window);
+ app.window.window.next_char = Input::Character('W');
+ app.process_input();
+ assert_eq!(app.exit_code.unwrap(), 0);
+ assert!(!app.git_interactive.get_lines().is_empty());
+ }
+}
diff --git a/src/git_interactive.rs b/src/git_interactive.rs
new file mode 100644
index 0000000..65ae357
--- /dev/null
+++ b/src/git_interactive.rs
@@ -0,0 +1,134 @@
+use std::cmp;
+use std::fs::File;
+use std::path::PathBuf;
+use std::error::Error;
+use std::io::Read;
+use std::io::Write;
+
+use action::Action;
+use line::Line;
+
+pub struct GitInteractive {
+ filepath: PathBuf,
+ lines: Vec<Line>,
+ selected_line_index: usize
+}
+
+impl GitInteractive {
+ pub fn new_from_filepath(filepath: &str) -> Result<Self, String> {
+ let path = PathBuf::from(filepath);
+
+ let mut file = match File::open(&path) {
+ Ok(file) => file,
+ Err(why) => {
+ return Err(format!(
+ "Error opening file, {}\n\
+ Reason: {}", path.display(), why.description()
+ ));
+ }
+ };
+
+ let mut s = String::new();
+ match file.read_to_string(&mut s) {
+ Ok(_) => {},
+ Err(why) => {
+ return Err(format!(
+ "Error reading file, {}\n\
+ Reason: {}", path.display(), why.description()
+ ));
+ }
+ }
+
+ // catch noop rebases
+ let parsed_result = match s.lines().nth(0) {
+ Some("noop") => Ok(Vec::new()),
+ _ => {
+ s.lines()
+ .filter(|l| !l.starts_with('#') && !l.is_empty())
+ .map(|l| Line::new(l))
+ .collect()
+ }
+ };
+
+ match parsed_result {
+ Ok(lines) => Ok(
+ GitInteractive {
+ filepath: path,
+ lines: lines,
+ selected_line_index: 1
+ }
+ ),
+ Err(e) => Err(format!(
+ "Error reading file, {}\n\
+ Reason: {}", path.display(), e
+ ))
+ }
+ }
+
+ pub fn write_file(&self) -> Result<(), String> {
+ let mut file = match File::create(&self.filepath) {
+ Ok(file) => file,
+ Err(why) => {
+ return Err(format!(
+ "Error opening file, {}\n\
+ Reason: {}", self.filepath.display(), why.description()
+ ));
+ }
+ };
+
+ for line in &self.lines {
+ match writeln!(file, "{}", line.to_text()) {
+ Ok(_) => {},
+ Err(why) => {
+ return Err(format!(
+ "Error writing to file, {}", why.description()
+ ));
+ }
+ }
+ }
+ Ok(())
+ }
+
+ pub fn clear(&mut self) {
+ self.lines.clear();
+ }
+
+ pub fn move_cursor_up(&mut self, amount: usize) {
+ self.selected_line_index = match amount {
+ a if a >= self.selected_line_index => 1,
+ _ => self.selected_line_index - amount
+ }
+ }
+
+ pub fn move_cursor_down(&mut self, amount: usize) {
+ self.selected_line_index = cmp::min(self.selected_line_index + amount, self.lines.len());
+ }
+
+ pub fn swap_selected_up(&mut self) {
+ if self.selected_line_index == 1 {
+ return
+ }
+ self.lines.swap(self.selected_line_index - 1, self.selected_line_index - 2);
+ self.move_cursor_up(1);
+ }
+
+ pub fn swap_selected_down(&mut self) {
+ if self.selected_line_index == self.lines.len() {
+ return
+ }
+ self.lines.swap(self.selected_line_index - 1, self.selected_line_index);
+ self.move_cursor_down(1);
+ }
+
+ pub fn set_selected_line_action(&mut self, action: Action) {
+ self.lines[self.selected_line_index - 1].set_action(action);
+ }
+
+ pub fn get_selected_line_index(&self) -> &usize {
+ &self.selected_line_index
+ }
+
+ pub fn get_lines(&self) -> &Vec<Line> {
+ &self.lines
+ }
+}
diff --git a/src/line.rs b/src/line.rs
new file mode 100644
index 0000000..c8fe94e
--- /dev/null
+++ b/src/line.rs
@@ -0,0 +1,99 @@
+use action::{
+ Action,
+ action_from_str,
+ action_to_str
+};
+
+#[derive(PartialEq, Debug)]
+pub struct Line {
+ action: Action,
+ hash: String,
+ comment: String,
+ mutated: bool
+}
+
+impl Line {
+ pub fn new(input_line: &str) -> Result<Self, String> {
+ let input: Vec<&str> = input_line.splitn(3, ' ').collect();
+ match input.len() {
+ 3 => Ok(Line {
+ action: action_from_str(input[0])?,
+ hash: String::from(input[1]),
+ comment: String::from(input[2]),
+ mutated: false
+ }),
+ _ => Err(format!(
+ "Invalid line: {}", input_line
+ ))
+ }
+ }
+
+ pub fn set_action(&mut self, action: Action) {
+ if self.action != action {
+ self.mutated = true;
+ self.action = action;
+ }
+ }
+
+ pub fn get_action(&self) -> &Action {
+ &self.action
+ }
+ pub fn get_hash(&self) -> &String {
+ &self.hash
+ }
+ pub fn get_comment(&self) -> &String {
+ &self.comment
+ }
+
+ pub fn to_text(&self) -> String {
+ format!("{} {} {}", action_to_str(&self.action), self.hash, self.comment)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::Line;
+ use action::Action;
+
+ #[test]
+ fn new_with_valid_line() {
+ let line = Line::new("pick aaa comment").unwrap();
+ assert_eq!(line.action, Action::Pick);
+ assert_eq!(line.hash, "aaa");
+ assert_eq!(line.comment, "comment");
+ assert_eq!(line.mutated, false);
+ }
+
+ #[test]
+ fn new_with_invalid_action() {
+ assert_eq!(Line::new("invalid aaa comment").unwrap_err(), "Invalid action: invalid");
+ }
+
+ #[test]
+ fn new_with_invalid_line() {
+ assert_eq!(Line::new("invalid").unwrap_err(), "Invalid line: invalid");
+ }
+
+ #[test]
+ fn set_to_new_action() {
+ let mut line = Line::new("pick aaa comment").unwrap();
+ line.set_action(Action::Fixup);
+ assert_eq!(line.action, Action::Fixup);
+ assert_eq!(line.mutated, true);
+ }
+
+ #[test]
+ fn getters() {
+ let line = Line::new("pick aaa comment").unwrap();
+ assert_eq!(line.get_action(), &Action::Pick);
+ assert_eq!(line.get_hash(), &"aaa");
+ assert_eq!(line.get_comment(), &"comment");
+ }
+
+ #[test]
+ fn to_text() {
+ let line = Line::new("pick aaa comment").unwrap();
+ assert_eq!(line.to_text(), "pick aaa comment");
+ }
+}
+
diff --git a/src/main.rs b/src/main.rs
index cb0de7a..b237ac8 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,426 +2,22 @@
// - Add execute command
extern crate pancurses;
-use std::cmp;
use std::env;
-use std::error::Error;
-use std::fs::File;
-use std::io::prelude::*;
-use std::path::Path;
use std::process;
-use pancurses::Input;
-macro_rules! print_err {
- ($($arg:tt)*) => (
- {
- use std::io::prelude::*;
- if let Err(e) = write!(&mut ::std::io::stderr(), "{}\n", format_args!($($arg)*)) {
- panic!(
- "Failed to write to stderr.\n\
- Original error output: {}\n\
- Secondary error writing to stderr: {}", format!($($arg)*), e
- );
- }
- }
- )
-}
-
-enum Action {
- Pick,
- Reword,
- Edit,
- Squash,
- Fixup,
- Drop
-}
-
-
-fn action_from_str(s: &str) -> Action {
- match s {
- "pick" | "p" => Action::Pick,
- "reword" | "r" => Action::Reword,
- "edit" | "e" => Action::Edit,
- "squash" | "s" => Action::Squash,
- "fixup" | "f" => Action::Fixup,
- "drop" | "d" => Action::Drop,
- _ => Action::Pick
- }
-}
-
-fn action_to_str(action: &Action) -> String {
- String::from(match action {
- &Action::Pick => "pick",
- &Action::Reword => "reword",
- &Action::Edit => "edit",
- &Action::Squash => "squash",
- &Action::Fixup => "fixup",
- &Action::Drop => "drop"
- })
-}
-
-struct Line {
- action: Action,
- hash: String,
- comment: String
-}
+mod action;
+mod application;
+mod git_interactive;
+mod line;
+#[macro_use]
+mod utils;
+mod window;
+#[cfg(test)]
+mod mocks;
-impl Line {
- fn new(input_line: &str) -> Result<Self, String> {
- let input: Vec<&str> = input_line.splitn(3, " ").collect();
- match input.len() {
- 3 => Ok(Line {
- action: action_from_str(input[0]),
- hash: String::from(input[1]),
- comment: String::from(input[2])
- }),
- _ => Err(format!(
- "Invalid line {}", input_line
- ))
- }
- }
-}
-
-struct GitInteractive<'a> {
- filepath: &'a Path,
- lines: Vec<Line>,
- selected_line: usize
-}
-
-impl<'a> GitInteractive<'a> {
- fn from_filepath(filepath: &'a str) -> Result<Option<Self>, String> {
- let path = Path::new(filepath);
-
- let mut file = match File::open(path) {
- Ok(file) => file,
- Err(why) => {
- return Err(format!(
- "Error opening file, {}\n\
- Reason: {}", path.display(), why.description()
- ));
- }
- };
-
- let mut s = String::new();
- match file.read_to_string(&mut s) {
- Ok(_) => {},
- Err(why) => {
- return Err(format!(
- "Error reading file, {}\n\
- Reason: {}", path.display(), why.description()
- ));
- }
- }
-
- if s.starts_with("noop") {
- return Ok(None)
- }
-
- let parsed_result: Result<Vec<Line>, String> = s
- .lines()
- .filter(|l| !l.starts_with("#") && !l.is_empty())
- .map(|l| Line::new(l))
- .collect();
-
- match parsed_result {
- Ok(lines) => Ok(
- Some(GitInteractive {
- filepath: path,
- lines: lines,
- selected_line: 1
- })
- ),
- Err(e) => Err(format!(
- "Error reading file, {}\n\
- Reason: {}", path.display(), e
- ))
- }
- }
-
- fn write_file(&self) -> Result<(), String> {
- let path = Path::new(self.filepath);
-
- let mut file = match File::create(path) {
- Ok(file) => file,
- Err(why) => {
- return Err(format!(
- "Error opening file, {}\n\
- Reason: {}", path.display(), why.description()
- ));
- }
- };
-
- for line in &self.lines {
- match writeln!(file, "{} {} {}", action_to_str(&line.action), line.hash, line.comment) {
- Ok(_) => {},
- Err(why) => {
- return Err(format!(
- "Error writing to file, {}", why.description()
- ));
- }
- }
- }
- Ok(())
- }
-
- fn move_cursor_up(&mut self, amount: usize) {
- self.selected_line = match amount {
- a if a >= self.selected_line => 1,
- _ => self.selected_line - amount
- }
- }
-
- fn move_cursor_down(&mut self, amount: usize) {
- self.selected_line = cmp::min(self.selected_line + amount, self.lines.len());
- }
-
- fn swap_selected_up(&mut self) {
- if self.selected_line == 1 {
- return
- }
- self.lines.swap(self.selected_line - 1, self.selected_line - 2);
- self.move_cursor_up(1);
- }
-
- fn swap_selected_down(&mut self) {
- if self.selected_line == self.lines.len() {
- return
- }
- self.lines.swap(self.selected_line - 1, self.selected_line);
- self.move_cursor_down(1);
- }
-
- fn set_selected_line_action(&mut self, action: Action) {
- self.lines[self.selected_line - 1].action = action;
- }
-}
-
-const COLOR_TABLE: [i16; 7] = [
- pancurses::COLOR_WHITE,
- pancurses::COLOR_YELLOW,
- pancurses::COLOR_BLUE,
- pancurses::COLOR_GREEN,
- pancurses::COLOR_CYAN,
- pancurses::COLOR_MAGENTA,
- pancurses::COLOR_RED
-];
-
-enum Color {
- White,
- Yellow,
- Blue,
- Green,
- Cyan,
- Magenta,
- Red
-}
-
-struct Window {
- window: pancurses::Window,
- top: usize
-}
-
-impl Window {
- fn new() -> Self {
- let window = pancurses::initscr();
-
- pancurses::curs_set(0);
- pancurses::noecho();
-
- window.keypad(true);
-
- if pancurses::has_colors() {
- pancurses::start_color();
- }
- pancurses::use_default_colors();
- for (i, color) in COLOR_TABLE.into_iter().enumerate() {
- pancurses::init_pair(i as i16, *color, -1);
- }
-
- Window{
- window: window,
- top: 0
- }
- }
- fn draw(&self, git_interactive: &GitInteractive) {
- self.window.clear();
- self.draw_title();
- let window_height = self.get_window_height();
-
- if self.top > 0 {
- self.draw_more_indicator(self.top);
- }
- self.window.addstr(&"\n");
-
- let mut index: usize = self.top + 1;
- for line in git_interactive
- .lines
- .iter()
- .skip(self.top)
- .take(window_height)
- {
- self.draw_line(&line, index == git_interactive.selected_line);
- index += 1;
- }
- if window_height < git_interactive.lines.len() - self.top {
- self.draw_more_indicator((git_interactive.lines.len() - window_height - self.top) as usize);
- }
- self.window.addstr(&"\n");
- self.draw_footer();
- self.window.refresh();
- }
-
- fn draw_more_indicator(&self, remaining: usize) {
- self.set_color(Color::White);
- self.window.attron(pancurses::A_DIM);
- self.window.attron(pancurses::A_REVERSE);
- self.window.addstr(&format!(" -- {} -- ", remaining));
- self.window.attroff(pancurses::A_REVERSE);
- self.window.attroff(pancurses::A_DIM);
- }
-
- fn draw_title(&self) {
- self.set_color(Color::White);
- self.set_dim(true);
- self.set_underline(true);
- self.window.addstr("Git Interactive Rebase ? for help\n");
- self.set_underline(false);
- self.set_dim(false);
- }
-
- fn draw_line(&self, line: &Line, selected: bool) {
- self.set_color(Color::White);
- if selected {
- self.window.addstr(" > ");
- }
- else {
- self.window.addstr(" ");
- }
- match line.action {
- Action::Pick => self.set_color(Color::Green),
- Action::Reword => self.set_color(Color::Yellow),
- Action::Edit => self.set_color(Color::Blue),
- Action::Squash => self.set_color(Color::Cyan),
- Action::Fixup => self.set_color(Color::Magenta),
- Action::Drop => self.set_color(Color::Red)
- }
- self.window.addstr(&format!("{:6}", action_to_str(&line.action)).as_ref());
- self.set_color(Color::White);
- self.window.addstr(&format!(" {} {}\n", line.hash, line.comment).as_ref());
- }
-
- fn draw_footer(&self) {
- self.set_color(Color::White);
- self.set_dim(true);
- self.window.mvaddstr(
- self.window.get_max_y() - 1,
- 0,
- "Actions: [ up, down, q/Q, w/W, j, k, p, r, e, s, f, d, ? ]"
- );
- self.set_dim(false);
- }
-
- fn draw_help(&self) {
- self.window.clear();
- self.draw_title();
- self.set_color(Color::White);
- self.window.addstr(" Key Action\n");
- self.window.addstr(" --------------------------------------------------\n");
- self.draw_help_command("Up", "Move selection up");
- self.draw_help_command("Down", "Move selection down");
- self.draw_help_command("Page Up", "Move selection up 5 lines");
- self.draw_help_command("Page Down", "Move selection down 5 lines");
- self.draw_help_command("q", "Abort interactive rebase");
- self.draw_help_command("Q", "Immediately abort interactive rebase");
- self.draw_help_command("w", "Write interactive rebase file");
- self.draw_help_command("W", "Immediately write interactive rebase file");
- self.draw_help_command("?", "Show help");
- self.draw_help_command("j", "Move selected commit down");
- self.draw_help_command("k", "Move selected commit up");
- self.draw_help_command("p", "Set selected commit to be picked");
- self.draw_help_command("r", "Set selected commit to be reworded");
- self.draw_help_command("e", "Set selected commit to be edited");
- self.draw_help_command("s", "Set selected commit to be squashed");
- self.draw_help_command("f", "Set selected commit to be fixed-up");
- self.draw_help_command("d", "Set selected commit to be dropped");
- self.window.addstr("\n\nHit any key to close help");
- self.window.refresh();
- self.window.getch();
- }
-
- fn draw_help_command(&self, command: &str, help: &str) {
- self.set_color(Color::Blue);
- self.window.addstr(&format!(" {:4} ", command));
- self.set_color(Color::White);
- self.window.addstr(&format!("{}\n", help));
- }
-
- fn set_color(&self, color: Color) {
- match color {
- Color::White => self.window.attrset(pancurses::COLOR_PAIR(0)),
- Color::Yellow => self.window.attrset(pancurses::COLOR_PAIR(1)),
- Color::Blue => self.window.attrset(pancurses::COLOR_PAIR(2)),
- Color::Green => self.window.attrset(pancurses::COLOR_PAIR(3)),
- Color::Cyan => self.window.attrset(pancurses::COLOR_PAIR(4)),
- Color::Magenta => self.window.attrset(pancurses::COLOR_PAIR(5)),
- Color::Red => self.window.attrset(pancurses::COLOR_PAIR(6))
- };
- }
-
- fn set_dim(&self, on: bool) {
- if on {
- self.window.attron(pancurses::A_DIM);
- }
- else {
- self.window.attroff(pancurses::A_DIM);
- }
- }
-
- fn set_underline(&self, on: bool) {
- if on {
- self.window.attron(pancurses::A_UNDERLINE);
- }
- else {
- self.window.attroff(pancurses::A_UNDERLINE);
- }
- }
-
- fn confirm(&self, message: &str) -> bool {
- self.window.clear();
- self.draw_title();
- self.window.addstr(&format!("{} (y/n)?", message));
- match self.window.getch() {
- Some(pancurses::Input::Character(c)) if c == 'y' || c == 'Y' => true,
- _ => false
- }
- }
-
- fn set_top(&mut self, git_interactive: &GitInteractive) {
- let window_height = self.get_window_height();
-
- self.top = match git_interactive.selected_line {
- _ if git_interactive.lines.len() <= window_height => 0,
- s if s == git_interactive.lines.len() => git_interactive.lines.len() - window_height,
- s if self.top + 1 > s => s - 1,
- s if self.top + window_height <= s => s - window_height + 1,
- _ => self.top
- };
- }
-
- fn get_window_height(&self) -> usize {
- return match self.window.get_max_y() {
- // 4 removed for other UI lines
- x if x >= 4 => x - 4,
- _ => 4
- } as usize;
- }
-
- fn endwin(&self) {
- self.window.clear();
- self.window.refresh();
- pancurses::curs_set(1);
- pancurses::endwin();
- }
-}
+use application::Application;
+use git_interactive::GitInteractive;
+use window::Window;
fn main() {
let filepath = match env::args().nth(1) {
@@ -434,88 +30,38 @@ fn main() {