summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTim Oram <dev@mitmaro.ca>2019-07-02 22:20:51 -0230
committerGitHub <noreply@github.com>2019-07-02 22:20:51 -0230
commit39138ea76182cca1b8ee0fb9a5687296630caaa4 (patch)
tree292b4157bafe31ffc7520da3c6c1f17fb9266844
parent549cc55083748fe341b1ef564efb2f6896dc486a (diff)
parent8947002607684d6ba7fa6d99511800efe82c1c26 (diff)
Merge pull request #146 from MitMaro/tim/move-to-modules
Move the rest of the code into modules
-rw-r--r--src/application.rs250
-rw-r--r--src/confirm_abort/confirm_abort.rs2
-rw-r--r--src/confirm_rebase/confirm_rebase.rs2
-rw-r--r--src/constants.rs16
-rw-r--r--src/edit/edit.rs2
-rw-r--r--src/error/error.rs2
-rw-r--r--src/exiting/exiting.rs17
-rw-r--r--src/exiting/mod.rs4
-rw-r--r--src/external_editor/external_editor.rs4
-rw-r--r--src/git_interactive.rs2
-rw-r--r--src/help/help.rs105
-rw-r--r--src/help/mod.rs4
-rw-r--r--src/list/list.rs299
-rw-r--r--src/list/mod.rs6
-rw-r--r--src/list/utils.rs15
-rw-r--r--src/main.rs4
-rw-r--r--src/process/process.rs2
-rw-r--r--src/process/state.rs3
-rw-r--r--src/show_commit/show_commit.rs4
-rw-r--r--src/view/view.rs265
-rw-r--r--src/window_size_error/mod.rs4
-rw-r--r--src/window_size_error/window_size_error.rs61
22 files changed, 585 insertions, 488 deletions
diff --git a/src/application.rs b/src/application.rs
index 5278bc4..e58eb6e 100644
--- a/src/application.rs
+++ b/src/application.rs
@@ -1,30 +1,33 @@
-use crate::action::Action;
-use crate::git_interactive::GitInteractive;
-
use crate::config::Config;
use crate::confirm_abort::ConfirmAbort;
use crate::confirm_rebase::ConfirmRebase;
-use crate::constants::{LIST_HELP_LINES, VISUAL_MODE_HELP_LINES};
use crate::edit::Edit;
use crate::error::Error;
+use crate::exiting::Exiting;
use crate::external_editor::ExternalEditor;
-use crate::input::{Input, InputHandler};
-use crate::process::{ExitStatus, HandleInputResult, HandleInputResultBuilder, ProcessModule, ProcessResult, State};
+use crate::git_interactive::GitInteractive;
+use crate::help::Help;
+use crate::input::InputHandler;
+use crate::list::List;
+use crate::process::{HandleInputResult, ProcessModule, ProcessResult, State};
use crate::show_commit::ShowCommit;
use crate::view::View;
-use core::borrow::Borrow;
+use crate::window_size_error::WindowSizeError;
pub struct Application<'a> {
- config: &'a Config,
confirm_abort: ConfirmAbort,
confirm_rebase: ConfirmRebase,
edit: Edit,
error: Error,
+ exiting: Exiting,
external_editor: ExternalEditor<'a>,
git_interactive: GitInteractive,
+ help: Help,
input_handler: &'a InputHandler<'a>,
+ list: List<'a>,
show_commit: ShowCommit,
view: View<'a>,
+ window_size_error: WindowSizeError,
}
impl<'a> Application<'a> {
@@ -36,36 +39,34 @@ impl<'a> Application<'a> {
) -> Self
{
Self {
- config,
confirm_abort: ConfirmAbort::new(),
confirm_rebase: ConfirmRebase::new(),
edit: Edit::new(),
error: Error::new(),
+ exiting: Exiting::new(),
external_editor: ExternalEditor::new(config),
git_interactive,
+ help: Help::new(),
input_handler,
+ list: List::new(config),
show_commit: ShowCommit::new(),
view,
+ window_size_error: WindowSizeError::new(),
}
}
- fn get_cursor_index(&self) -> usize {
- *self.git_interactive.get_selected_line_index() - 1
- }
-
pub fn activate(&mut self, state: State) {
match state {
State::ConfirmAbort => self.confirm_abort.activate(state, &self.git_interactive),
State::ConfirmRebase => self.confirm_rebase.activate(state, &self.git_interactive),
State::Edit => self.edit.activate(state, &self.git_interactive),
State::Error { .. } => self.error.activate(state, &self.git_interactive),
- State::Exiting => {},
+ State::Exiting => self.exiting.activate(state, &self.git_interactive),
State::ExternalEditor => self.external_editor.activate(state, &self.git_interactive),
- State::Help(_) => {},
- State::List => {},
+ State::Help(_) => self.help.activate(state, &self.git_interactive),
+ State::List(_) => self.list.activate(state, &self.git_interactive),
State::ShowCommit => self.show_commit.activate(state, &self.git_interactive),
- State::VisualMode => {},
- State::WindowSizeError(_) => {},
+ State::WindowSizeError(_) => self.window_size_error.activate(state, &self.git_interactive),
}
}
@@ -75,13 +76,12 @@ impl<'a> Application<'a> {
State::ConfirmRebase => self.confirm_rebase.deactivate(),
State::Edit => self.edit.deactivate(),
State::Error { .. } => self.error.deactivate(),
- State::Exiting => {},
+ State::Exiting => self.exiting.deactivate(),
State::ExternalEditor => self.external_editor.deactivate(),
- State::Help(_) => {},
- State::List => {},
+ State::Help(_) => self.help.deactivate(),
+ State::List(_) => self.list.deactivate(),
State::ShowCommit => self.show_commit.deactivate(),
- State::VisualMode => {},
- State::WindowSizeError(_) => {},
+ State::WindowSizeError(_) => self.window_size_error.deactivate(),
}
}
@@ -91,23 +91,15 @@ impl<'a> Application<'a> {
State::ConfirmRebase => self.confirm_rebase.process(&mut self.git_interactive),
State::Edit => self.edit.process(&mut self.git_interactive),
State::Error { .. } => self.error.process(&mut self.git_interactive),
- State::Exiting => ProcessResult::new(),
+ State::Exiting => self.exiting.process(&mut self.git_interactive),
State::ExternalEditor => self.external_editor.process(&mut self.git_interactive),
- State::Help(_) => ProcessResult::new(),
- State::List => self.process_list(),
+ State::Help(_) => self.help.process(&mut self.git_interactive),
+ State::List(_) => self.list.process_with_view(&mut self.git_interactive, &self.view),
State::ShowCommit => self.show_commit.process(&mut self.git_interactive),
- State::VisualMode => self.process_list(),
- State::WindowSizeError(_) => ProcessResult::new(),
+ State::WindowSizeError(_) => self.window_size_error.process(&mut self.git_interactive),
}
}
- pub fn process_list(&mut self) -> ProcessResult {
- let lines = self.git_interactive.get_lines();
- let selected_index = self.get_cursor_index();
- self.view.update_main_top(lines.len(), selected_index);
- ProcessResult::new()
- }
-
pub fn check_window_size(&self) -> bool {
self.view.check_window_size()
}
@@ -119,53 +111,16 @@ impl<'a> Application<'a> {
State::ConfirmRebase => self.confirm_rebase.render(&self.view, &self.git_interactive),
State::Edit => self.edit.render(&self.view, &self.git_interactive),
State::Error { .. } => self.error.render(&self.view, &self.git_interactive),
- State::Exiting => self.draw_exiting(),
+ State::Exiting => self.exiting.render(&self.view, &self.git_interactive),
State::ExternalEditor => self.external_editor.render(&self.view, &self.git_interactive),
- State::Help(help_state) => self.draw_help(help_state.borrow()),
- State::List => self.draw_main(false),
- State::VisualMode => self.draw_main(true),
+ State::Help(_) => self.help.render(&self.view, &self.git_interactive),
+ State::List(_) => self.list.render(&self.view, &self.git_interactive),
State::ShowCommit => self.show_commit.render(&self.view, &self.git_interactive),
- State::WindowSizeError(_) => self.draw_window_size_error(),
+ State::WindowSizeError(_) => self.window_size_error.render(&self.view, &self.git_interactive),
}
self.view.refresh()
}
- fn draw_main(&self, visual_mode: bool) {
- self.view.draw_main(
- self.git_interactive.get_lines(),
- self.get_cursor_index(),
- if visual_mode {
- Some(self.git_interactive.get_visual_start_index() - 1)
- }
- else {
- None
- },
- );
- }
-
- fn draw_help(&self, help_state: &State) {
- self.view.draw_help(
- if *help_state == State::List {
- LIST_HELP_LINES
- }
- else {
- VISUAL_MODE_HELP_LINES
- },
- );
- }
-
- fn draw_exiting(&self) {
- self.view.draw_exiting();
- }
-
- fn draw_window_size_error(&self) {
- self.view.draw_window_size_error();
- }
-
- pub fn get_input(&self) -> Input {
- self.input_handler.get_input()
- }
-
pub fn handle_input(&mut self, state: State) -> HandleInputResult {
match state {
State::ConfirmAbort => {
@@ -178,150 +133,31 @@ impl<'a> Application<'a> {
},
State::Edit => self.edit.handle_input(&self.input_handler, &mut self.git_interactive),
State::Error { .. } => self.error.handle_input(&self.input_handler, &mut self.git_interactive),
- State::Exiting => HandleInputResult::new(Input::Other),
+ State::Exiting => {
+ self.exiting
+ .handle_input(&self.input_handler, &mut self.git_interactive)
+ },
State::ExternalEditor => {
self.external_editor
.handle_input(&self.input_handler, &mut self.git_interactive)
},
- State::Help(help_state) => self.handle_help_input(help_state.borrow()),
- State::List => self.handle_list_input(),
- State::VisualMode => self.handle_visual_mode_input(),
+ State::Help(_) => {
+ self.help
+ .handle_input_with_view(&self.input_handler, &mut self.git_interactive, &self.view)
+ },
+ State::List(_) => self.list.handle_input(&self.input_handler, &mut self.git_interactive),
State::ShowCommit => {
self.show_commit
.handle_input_with_view(&self.input_handler, &mut self.git_interactive, &self.view)
},
- State::WindowSizeError(_) => self.handle_window_size_error_input(),
- }
- }
-
- fn handle_help_input(&mut self, help_state: &State) -> HandleInputResult {
- let help_lines = if *help_state == State::List {
- LIST_HELP_LINES
- }
- else {
- VISUAL_MODE_HELP_LINES
- };
- let input = self.get_input();
- let mut result = HandleInputResultBuilder::new(input);
- match input {
- Input::MoveCursorDown => {
- self.view.update_help_top(false, false, help_lines);
- },
- Input::MoveCursorUp => {
- self.view.update_help_top(true, false, help_lines);
- },
- Input::Resize => {
- self.view.update_help_top(true, true, help_lines);
- },
- _ => {
- result = result.state(help_state.clone());
- },
- }
- result.build()
- }
-
- fn handle_visual_mode_input(&mut self) -> HandleInputResult {
- let input = self.get_input();
- let mut result = HandleInputResultBuilder::new(input);
- match input {
- Input::MoveCursorDown => {
- self.git_interactive.move_cursor_down(1);
- },
- Input::MoveCursorUp => {
- self.git_interactive.move_cursor_up(1);
- },
- Input::MoveCursorPageDown => {
- self.git_interactive.move_cursor_down(5);
- },
- Input::MoveCursorPageUp => {
- self.git_interactive.move_cursor_up(5);
- },
- Input::ActionDrop => self.git_interactive.set_visual_range_action(Action::Drop),
- Input::ActionEdit => self.git_interactive.set_visual_range_action(Action::Edit),
- Input::ActionFixup => self.git_interactive.set_visual_range_action(Action::Fixup),
- Input::ActionPick => self.git_interactive.set_visual_range_action(Action::Pick),
- Input::ActionReword => self.git_interactive.set_visual_range_action(Action::Reword),
- Input::ActionSquash => self.git_interactive.set_visual_range_action(Action::Squash),
- Input::SwapSelectedDown => self.git_interactive.swap_visual_range_down(),
- Input::SwapSelectedUp => self.git_interactive.swap_visual_range_up(),
- Input::ToggleVisualMode => {
- result = result.state(State::List);
- },
- Input::Help => {
- self.view.update_help_top(false, true, VISUAL_MODE_HELP_LINES);
- result = result.help(State::VisualMode);
- },
- _ => {},
- }
- result.build()
- }
-
- pub fn handle_list_input(&mut self) -> HandleInputResult {
- let input = self.get_input();
- let mut result = HandleInputResultBuilder::new(input);
- match input {
- Input::Help => {
- self.view.update_help_top(false, true, LIST_HELP_LINES);
- result = result.help(State::List);
- },
- Input::ShowCommit => {
- if !self.git_interactive.get_selected_line_hash().is_empty() {
- result = result.state(State::ShowCommit);
- }
- },
- Input::Abort => {
- result = result.state(State::ConfirmAbort);
- },
- Input::ForceAbort => {
- self.git_interactive.clear();
- result = result.exit_status(ExitStatus::Good).state(State::Exiting);
- },
- Input::Rebase => {
- result = result.state(State::ConfirmRebase);
- },
- Input::ForceRebase => {
- result = result.exit_status(ExitStatus::Good).state(State::Exiting);
- },
- Input::ActionBreak => self.git_interactive.toggle_break(),
- Input::ActionDrop => self.set_selected_line_action(Action::Drop),
- Input::ActionEdit => self.set_selected_line_action(Action::Edit),
- Input::ActionFixup => self.set_selected_line_action(Action::Fixup),
- Input::ActionPick => self.set_selected_line_action(Action::Pick),
- Input::ActionReword => self.set_selected_line_action(Action::Reword),
- Input::ActionSquash => self.set_selected_line_action(Action::Squash),
- Input::Edit => {
- if *self.git_interactive.get_selected_line_action() == Action::Exec {
- result = result.state(State::Edit);
- }
- },
- Input::SwapSelectedDown => self.git_interactive.swap_selected_down(),
- Input::SwapSelectedUp => self.git_interactive.swap_selected_up(),
- Input::MoveCursorDown => self.git_interactive.move_cursor_down(1),
- Input::MoveCursorUp => self.git_interactive.move_cursor_up(1),
- Input::MoveCursorPageDown => self.git_interactive.move_cursor_down(5),
- Input::MoveCursorPageUp => self.git_interactive.move_cursor_up(5),
- Input::ToggleVisualMode => {
- self.git_interactive.start_visual_mode();
- result = result.state(State::VisualMode);
+ State::WindowSizeError(_) => {
+ self.window_size_error
+ .handle_input(&self.input_handler, &mut self.git_interactive)
},
- Input::OpenInEditor => result = result.state(State::ExternalEditor),
- _ => {},
}
- result.build()
- }
-
- pub fn handle_window_size_error_input(&mut self) -> HandleInputResult {
- HandleInputResult::new(self.get_input())
}
pub fn write_file(&self) -> Result<(), String> {
self.git_interactive.write_file()
}
-
- fn set_selected_line_action(&mut self, action: Action) {
- self.git_interactive.set_selected_line_action(action);
- if self.config.auto_select_next {
- self.git_interactive.move_cursor_down(1);
- }
- }
}
diff --git a/src/confirm_abort/confirm_abort.rs b/src/confirm_abort/confirm_abort.rs
index 0caa8c9..9fe3464 100644
--- a/src/confirm_abort/confirm_abort.rs
+++ b/src/confirm_abort/confirm_abort.rs
@@ -20,7 +20,7 @@ impl ProcessModule for ConfirmAbort {
result = result.exit_status(ExitStatus::Good).state(State::Exiting);
},
Input::No => {
- result = result.state(State::List);
+ result = result.state(State::List(false));
},
_ => {},
}
diff --git a/src/confirm_rebase/confirm_rebase.rs b/src/confirm_rebase/confirm_rebase.rs
index 1189ff2..f341429 100644
--- a/src/confirm_rebase/confirm_rebase.rs
+++ b/src/confirm_rebase/confirm_rebase.rs
@@ -19,7 +19,7 @@ impl ProcessModule for ConfirmRebase {
result = result.exit_status(ExitStatus::Good).state(State::Exiting);
},
Input::No => {
- result = result.state(State::List);
+ result = result.state(State::List(false));
},
_ => {},
}
diff --git a/src/constants.rs b/src/constants.rs
index a51ef37..1b6e40e 100644
--- a/src/constants.rs
+++ b/src/constants.rs
@@ -6,22 +6,22 @@ pub const TITLE_HELP_INDICATOR: &str = "Help: ?";
pub const TITLE_HELP_INDICATOR_LENGTH: i32 = 7;
pub const LIST_FOOTER_FULL: &str = " up, down, q/Q, w/W, c, j, k, b, p, r, e, s, f, d, E, !, ?";
-pub const LIST_FOOTER_FULL_WIDTH: i32 = 58;
+pub const LIST_FOOTER_FULL_WIDTH: usize = 58;
pub const LIST_FOOTER_COMPACT: &str = "up,dn.q/Q,w/W,c,j,k,b,p,r,e,s,f,d,E,!,?";
-pub const LIST_FOOTER_COMPACT_WIDTH: i32 = 39;
+pub const LIST_FOOTER_COMPACT_WIDTH: usize = 39;
pub const VISUAL_MODE_FOOTER_FULL: &str = "(VISUAL) up, down, j, k, p, r, e, s, f, d, ?";
-pub const VISUAL_MODE_FOOTER_FULL_WIDTH: i32 = 44;
+pub const VISUAL_MODE_FOOTER_FULL_WIDTH: usize = 44;
pub const VISUAL_MODE_FOOTER_COMPACT: &str = "(V) up,down,j,k,p,r,e,s,f,d,?";
-pub const VISUAL_MODE_FOOTER_COMPACT_WIDTH: i32 = 29;
+pub const VISUAL_MODE_FOOTER_COMPACT_WIDTH: usize = 29;
pub const HEIGHT_ERROR_MESSAGE: &str = "Window too small, increase height to continue\n";
-pub const MINIMUM_WINDOW_HEIGHT_ERROR_WIDTH: i32 = 45;
+pub const MINIMUM_WINDOW_HEIGHT_ERROR_WIDTH: usize = 45;
pub const SHORT_ERROR_MESSAGE: &str = "Window too small\n";
-pub const SHORT_ERROR_MESSAGE_WIDTH: i32 = 16;
+pub const SHORT_ERROR_MESSAGE_WIDTH: usize = 16;
-pub const MINIMUM_WINDOW_HEIGHT: i32 = 5; // title + pad top + line + pad bottom + help
-pub const MINIMUM_COMPACT_WINDOW_WIDTH: i32 = 20; //">s ccc mmmmmmmmmmmmm".len()
+pub const MINIMUM_WINDOW_HEIGHT: usize = 5; // title + pad top + line + pad bottom + help
+pub const MINIMUM_COMPACT_WINDOW_WIDTH: usize = 20; //">s ccc mmmmmmmmmmmmm".len()
pub const MINIMUM_FULL_WINDOW_WIDTH: usize = 34; // " > squash cccccccc mmmmmmmmmmmmm %".len()
pub const NAME: &str = "interactive-rebase-tool";
diff --git a/src/edit/edit.rs b/src/edit/edit.rs
index e7a6fa9..d1dfd6f 100644
--- a/src/edit/edit.rs
+++ b/src/edit/edit.rs
@@ -35,7 +35,7 @@ impl ProcessModule for Edit {
EditState::Active => {},
EditState::Finish => {
git_interactive.edit_selected_line(self.content.as_str());
- result = result.state(State::List);
+ result = result.state(State::List(false));
},
};
result.build()
diff --git a/src/error/error.rs b/src/error/error.rs
index 2ae68c6..84c4eca 100644
--- a/src/error/error.rs
+++ b/src/error/error.rs
@@ -49,7 +49,7 @@ impl Error {
pub fn new() -> Self {
Self {
error_message: String::from(""),
- return_state: State::List,
+ return_state: State::List(false),
}
}
}
diff --git a/src/exiting/exiting.rs b/src/exiting/exiting.rs
new file mode 100644
index 0000000..ef13fa4
--- /dev/null
+++ b/src/exiting/exiting.rs
@@ -0,0 +1,17 @@
+use crate::git_interactive::GitInteractive;
+use crate::process::ProcessModule;
+use crate::view::View;
+
+pub struct Exiting {}
+
+impl ProcessModule for Exiting {
+ fn render(&self, view: &View, _git_interactive: &GitInteractive) {
+ view.draw_str("Exiting...")
+ }
+}
+
+impl Exiting {
+ pub fn new() -> Self {
+ Self {}
+ }
+}
diff --git a/src/exiting/mod.rs b/src/exiting/mod.rs
new file mode 100644
index 0000000..2d7e7f4
--- /dev/null
+++ b/src/exiting/mod.rs
@@ -0,0 +1,4 @@
+#[allow(clippy::module_inception)]
+mod exiting;
+
+pub use self::exiting::Exiting;
diff --git a/src/external_editor/external_editor.rs b/src/external_editor/external_editor.rs
index b7fd186..fe793e8 100644
--- a/src/external_editor/external_editor.rs
+++ b/src/external_editor/external_editor.rs
@@ -110,7 +110,7 @@ impl<'e> ExternalEditor<'e> {
self.state = ExternalEditorState::Error;
}
else {
- result = result.state(State::List);
+ result = result.state(State::List(false));
}
result.build()
}
@@ -133,7 +133,7 @@ impl<'e> ExternalEditor<'e> {
match input {
Input::Resize => {},
_ => {
- result = result.state(State::List);
+ result = result.state(State::List(false));
},
}
result.build()
diff --git a/src/git_interactive.rs b/src/git_interactive.rs
index 5099751..3a86501 100644
--- a/src/git_interactive.rs
+++ b/src/git_interactive.rs
@@ -59,7 +59,7 @@ impl GitInteractive {
lines,
selected_commit_stats: None,
selected_line_index: 1,
- visual_index_start: 0,
+ visual_index_start: 1,
})
}
diff --git a/src/help/help.rs b/src/help/help.rs
new file mode 100644
index 0000000..3eda151
--- /dev/null
+++ b/src/help/help.rs
@@ -0,0 +1,105 @@
+use crate::constants::{LIST_HELP_LINES, VISUAL_MODE_HELP_LINES};
+use crate::git_interactive::GitInteractive;
+use crate::input::{Input, InputHandler};
+use crate::process::{HandleInputResult, HandleInputResultBuilder, ProcessModule, State};
+use crate::scroll::ScrollPosition;
+use crate::view::{LineSegment, View, ViewLine};
+use crate::window::WindowColor;
+
+fn get_help_lines(return_state: &State) -> &[(&str, &str)] {
+ if let State::List(visual_mode) = *return_state {
+ if visual_mode {
+ VISUAL_MODE_HELP_LINES
+ }
+ else {
+ LIST_HELP_LINES
+ }
+ }
+ else {
+ &[]
+ }
+}
+
+pub struct Help {
+ scroll_position: ScrollPosition,
+ return_state: State,
+}
+
+impl ProcessModule for Help {
+ fn activate(&mut self, state: State, _git_interactive: &GitInteractive) {
+ self.scroll_position.reset();
+ if let State::Help(return_state) = state {
+ self.return_state = *return_state;
+ }
+ else {
+ panic!("Help module activated when not expected");
+ }
+ }
+
+ fn render(&self, view: &View, _git_interactive: &GitInteractive) {
+ let (view_width, view_height) = view.get_view_size();
+
+ let mut view_lines: Vec<ViewLine> = vec![];
+
+ for line in get_help_lines(&self.return_state) {
+ view_lines.push(ViewLine::new(vec![
+ LineSegment::new_with_color(format!(" {:4} ", line.0).as_str(), WindowColor::IndicatorColor),
+ LineSegment::new(line.1),
+ ]));
+ }
+
+ view.draw_title(false);
+
+ view.set_color(WindowColor::Foreground);
+ view.set_style(false, true, false);
+ view.draw_str(" Key Action");
+ if view_width > 13 {
+ let padding = " ".repeat(view_width - 13);
+ view.draw_str(padding.as_str());
+ }
+
+ view.draw_view_lines(view_lines, self.scroll_position.get_position(), view_height - 3);
+
+ view.set_color(WindowColor::IndicatorColor);
+ view.draw_str("Any key to close");
+ }
+}
+
+impl Help {
+ pub fn new() -> Self {
+ Self {
+ return_state: State::List(false),
+ scroll_position: ScrollPosition::new(3, 6, 3),
+ }
+ }
+
+ // TODO refactor to remove need for view
+ pub fn handle_input_with_view(
+ &mut self,
+ input_handler: &InputHandler,
+ _git_interactive: &mut GitInteractive,
+ view: &View,
+ ) -> HandleInputResult
+ {
+ let (_, window_height) = view.get_view_size();
+ let input = input_handler.get_input();
+ let mut result = HandleInputResultBuilder::new(input);
+ match input {
+ Input::MoveCursorDown => {
+ self.scroll_position
+ .scroll_down(window_height, get_help_lines(&self.return_state).len());
+ },
+ Input::MoveCursorUp => {
+ self.scroll_position
+ .scroll_up(window_height, get_help_lines(&self.return_state).len());
+ },
+ Input::Resize => {
+ self.scroll_position.reset();
+ },
+ _ => {
+ result = result.state(self.return_state.clone());
+ },
+ }
+ result.build()
+ }
+}
diff --git a/src/help/mod.rs b/src/help/mod.rs
new file mode 100644
index 0000000..013e492
--- /dev/null
+++ b/src/help/mod.rs
@@ -0,0 +1,4 @@
+#[allow(clippy::module_inception)]
+mod help;
+
+pub use self::help::Help;
diff --git a/src/list/list.rs b/src/list/list.rs
new file mode 100644
index 0000000..7676837
--- /dev/null
+++ b/src/list/list.rs
@@ -0,0 +1,299 @@
+use crate::action::Action;
+use crate::config::Config;
+use crate::constants::{
+ LIST_FOOTER_COMPACT,
+ LIST_FOOTER_COMPACT_WIDTH,
+ LIST_FOOTER_FULL,
+ LIST_FOOTER_FULL_WIDTH,
+ MINIMUM_FULL_WINDOW_WIDTH,
+ VISUAL_MODE_FOOTER_COMPACT,
+ VISUAL_MODE_FOOTER_COMPACT_WIDTH,
+ VISUAL_MODE_FOOTER_FULL,
+ VISUAL_MODE_FOOTER_FULL_WIDTH,
+};
+use crate::git_interactive::GitInteractive;
+use crate::input::{Input, InputHandler};
+use crate::line::Line;
+use crate::list::get_action_color;
+use crate::process::{ExitStatus, HandleInputResult, HandleInputResultBuilder, ProcessModule, ProcessResult, State};
+use crate::scroll::ScrollPosition;
+use crate::view::{LineSegment, View, ViewLine};
+use crate::window::WindowColor;
+use std::cmp;
+
+#[derive(Debug, PartialEq)]
+enum ListState {
+ Normal,
+ Visual,
+}
+
+pub struct List<'l> {
+ config: &'l Config,
+ scroll_position: ScrollPosition,
+ state: ListState,
+}
+
+impl<'l> ProcessModule for List<'l> {
+ fn handle_input(
+ &mut self,
+ input_handler: &InputHandler,
+ git_interactive: &mut GitInteractive,
+ ) -> HandleInputResult
+ {
+ match self.state {
+ ListState::Normal => self.handle_normal_mode_input(input_handler, git_interactive),
+ ListState::Visual => self.handle_visual_mode_input(input_handler, git_interactive),
+ }
+ }
+
+ #[allow(clippy::nonminimal_bool)]
+ fn render(&self, view: &View, git_interactive: &GitInteractive) {
+ let (view_width, view_height) = view.get_view_size();
+
+ let is_visual_mode = self.state == ListState::Visual;
+ let visual_index = git_interactive.get_visual_start_index() - 1;
+
+ let mut view_lines: Vec<ViewLine> = vec![];
+
+ let selected_index = *git_interactive.get_selected_line_index() - 1;
+
+ for (index, line) in git_interactive.get_lines().iter().enumerate() {
+ view_lines.push(ViewLine::new(self.get_todo_line_segments(
+ line,
+ selected_index == index,
+ is_visual_mode
+ && ((visual_index <= selected_index && index >= visual_index && index <= selected_index)
+ || (visual_index > selected_index && index >= selected_index && index <= visual_index)),
+ view_width,
+ )));
+ }
+
+ view.draw_title(true);
+
+ view.draw_view_lines(view_lines, self.scroll_position.get_position(), view_height - 2);
+
+ view.set_color(WindowColor::Foreground);
+ view.set_style(true, false, false);
+ if is_visual_mode {
+ if view_width >= VISUAL_MODE_FOOTER_FULL_WIDTH {
+ view.draw_str(VISUAL_MODE_FOOTER_FULL);
+ }
+ else if view_width >= VISUAL_MODE_FOOTER_COMPACT_WIDTH {
+ view.draw_str(VISUAL_MODE_FOOTER_COMPACT);
+ }
+ else {
+ view.draw_str("(Visual) Help: ?");
+ }
+ }
+ else if view_width >= LIST_FOOTER_FULL_WIDTH {
+ view.draw_str(LIST_FOOTER_FULL);
+ }
+ else if view_width >= LIST_FOOTER_COMPACT_WIDTH {
+ view.draw_str(LIST_FOOTER_COMPACT);
+ }
+ else {
+ view.draw_str("Help: ?");
+ }
+ view.set_style(false, false, false);
+ }
+}
+
+impl<'l> List<'l> {
+ pub fn new(config: &'l Config) -> Self {
+ Self {
+ config,
+ scroll_position: ScrollPosition::new(2, 1, 1),
+ state: ListState::Normal,
+ }
+ }
+
+ fn set_selected_line_action(&self, git_interactive: &mut GitInteractive, action: Action) {
+ git_interactive.set_selected_line_action(action);
+ if self.config.auto_select_next {
+ git_interactive.move_cursor_down(1);
+ }
+ }
+
+ pub fn process_with_view(&mut self, git_interactive: &mut GitInteractive, view: &View) -> ProcessResult {
+ let (_, view_height) = view.get_view_size();
+ let lines = git_interactive.get_lines();
+ let selected_index = *git_interactive.get_selected_line_index() - 1;
+ self.scroll_position
+ .ensure_cursor_visible(selected_index, view_height, lines.len());
+ ProcessResult::new()
+ }
+
+ fn handle_normal_mode_input(
+ &mut self,
+ input_handler: &InputHandler,
+ git_interactive: &mut GitInteractive,
+ ) -> HandleInputResult
+ {
+ let input = input_handler.get_input();
+ let mut result = HandleInputResultBuilder::new(input);
+ match input {
+ Input::Help => {
+ result = result.help(State::List(false));
+ },
+ Input::ShowCommit => {
+ if !git_interactive.get_selected_line_hash().is_empty() {
+ result = result.state(State::ShowCommit);
+ }
+ },
+ Input::Abort => {
+ result = result.state(State::ConfirmAbort);
+ },
+ Input::ForceAbort => {
+ git_interactive.clear();
+ result = result.exit_status(ExitStatus::Good).state(State::Exiting);
+ },
+ Input::Rebase => {
+ result = result.state(State::ConfirmRebase);
+ },
+ Input::ForceRebase => {
+ result = result.exit_status(ExitStatus::Good).state(State::Exiting);
+ },
+ Input::ActionBreak => git_interactive.toggle_break(),
+ Input::ActionDrop => self.set_selected_line_action(git_interactive, Action::Drop),
+ Input::ActionEdit => self.set_selected_line_action(git_interactive, Action::Edit),
+ Input::ActionFixup => self.set_selected_line_action(git_interactive, Action::Fixup),
+ Input::ActionPick => self.set_selected_line_action(git_interactive, Action::Pick),
+ Input::ActionReword => self.set_selected_line_action(git_interactive, Action::Reword),
+ Input::ActionSquash => self.set_selected_line_action(git_interactive, Action::Squash),
+ Input::Edit => {
+ if *git_interactive.get_selected_line_action() == Action::Exec {
+ result = result.state(State::Edit);
+ }
+ },
+ Input::SwapSelectedDown => git_interactive.swap_selected_down(),
+ Input::SwapSelectedUp => git_interactive.swap_selected_up(),
+ Input::MoveCursorDown => git_interactive.move_cursor_down(1),
+ Input::MoveCursorUp => git_interactive.move_cursor_up(1),
+ Input::MoveCursorPageDown => git_interactive.move_cursor_down(5),
+ Input::MoveCursorPageUp => git_interactive.move_cursor_up(5),
+ Input::ToggleVisualMode => {
+ git_interactive.start_visual_mode();
+ self.state = ListState::Visual;
+ result = result.state(State::List(true));
+ },
+ Input::OpenInEditor => result = result.state(State::ExternalEditor),
+ _ => {},
+ }
+ result.build()
+ }
+
+ fn handle_visual_mode_input(
+ &mut self,
+ input_handler: &InputHandler,
+ g