Skip to content
Merged
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
79 changes: 40 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,45 +173,46 @@ The tool has built-in help that can be accessed by using the `?` key.

Key bindings can be customized, see [configuration](readme/customization.md#key-bindings) for all key bindings and information on configuring.

| Key | Mode | Description |
|-------------|-------------|-------------------------------------------|
| `?` | All | Show help |
| Up | Normal/Diff | Move selection up |
| Down | Normal/Diff | Move selection down |
| Page Up | Normal/Diff | Move selection up five lines |
| Page Down | Normal/Diff | Move selection down five lines |
| Home | Normal/Diff | Move selection to start of list |
| End | Normal/Diff | Move selection to home of list |
| `q` | Normal/Diff | Abort interactive rebase |
| `Q` | Normal/Diff | Immediately abort interactive rebase |
| `w` | Normal/Diff | Write interactive rebase file |
| `W` | Normal/Diff | Immediately write interactive rebase file |
| `j` | Normal/Diff | Move selected commit(s) down |
| `k` | Normal/Diff | Move selected commit(s) up |
| `b` | Normal | Toggle break action |
| `p` | Normal/Diff | Set selected commit(s) to be picked |
| `r` | Normal/Diff | Set selected commit(s) to be reworded |
| `e` | Normal/Diff | Set selected commit(s) to be edited |
| `s` | Normal/Diff | Set selected commit(s) to be squashed |
| `f` | Normal/Diff | Set selected commit(s) to be fixed-up |
| `d` | Normal | Set selected commit(s) to be dropped |
| `d` | Diff | Show full commit diff |
| `E` | Normal | Edit the command of an editable action |
| `v` | Normal/Diff | Enter and exit visual mode (for selection)|
| `I` | Normal | Insert a new line |
| `Delete` | Normal/Diff | Remove selected lines |
| `!` | Normal/Diff | Open todo file in external editor |
| `Control+z` | Normal/Diff | Undo the previous change |
| `Control+y` | Normal/Diff | Redo the previously undone change |
| `c` | Normal/Diff | Show commit information |
| Down | Diff | Scroll view down |
| Up | Diff | Scroll view up |
| Left | Diff | Scroll view left |
| Right | Diff | Scroll view right |
| Home | Diff | Scroll view to the top |
| End | Diff | Scroll view to the end |
| PageUp | Diff | Scroll view a step up |
| PageDown | Diff | Scroll view a step down |
| Key | Mode | Description |
|-------------|-------------|--------------------------------------------|
| `?` | All | Show help |
| Up | Normal/Diff | Move selection up |
| Down | Normal/Diff | Move selection down |
| Page Up | Normal/Diff | Move selection up five lines |
| Page Down | Normal/Diff | Move selection down five lines |
| Home | Normal/Diff | Move selection to start of list |
| End | Normal/Diff | Move selection to home of list |
| `q` | Normal/Diff | Abort interactive rebase |
| `Q` | Normal/Diff | Immediately abort interactive rebase |
| `w` | Normal/Diff | Write interactive rebase file |
| `W` | Normal/Diff | Immediately write interactive rebase file |
| `j` | Normal/Diff | Move selected commit(s) down |
| `k` | Normal/Diff | Move selected commit(s) up |
| `b` | Normal | Toggle break action |
| `p` | Normal/Diff | Set selected commit(s) to be picked |
| `r` | Normal/Diff | Set selected commit(s) to be reworded |
| `e` | Normal/Diff | Set selected commit(s) to be edited |
| `s` | Normal/Diff | Set selected commit(s) to be squashed |
| `f` | Normal/Diff | Set selected commit(s) to be fixed-up |
| `d` | Normal | Set selected commit(s) to be dropped |
| `d` | Diff | Show full commit diff |
| `E` | Normal | Edit the command of an editable action |
| `v` | Normal/Diff | Enter and exit visual mode (for selection) |
| `I` | Normal | Insert a new line |
| `Control+d` | Normal | Duplicate the selected line |
| `Delete` | Normal/Diff | Remove selected lines |
| `!` | Normal/Diff | Open todo file in external editor |
| `Control+z` | Normal/Diff | Undo the previous change |
| `Control+y` | Normal/Diff | Redo the previously undone change |
| `c` | Normal/Diff | Show commit information |
| Down | Diff | Scroll view down |
| Up | Diff | Scroll view up |
| Left | Diff | Scroll view left |
| Right | Diff | Scroll view right |
| Home | Diff | Scroll view to the top |
| End | Diff | Scroll view to the end |
| PageUp | Diff | Scroll view a step up |
| PageDown | Diff | Scroll view a step down |

## Supported Platforms

Expand Down
1 change: 1 addition & 0 deletions readme/customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ Most keys can be changed to any printable character or supported special charact
| `inputForceRebase` | W | String | Key for forcing a rebase |
| `inputHelp` | ? | String | Key for showing the help |
| `inputInsertLine` | I | String | Key for inserting a new line |
| `inputDuplicateLine` | Control+d | String | Key for duplicating the selected line |
| `inputMoveDown` | Down | String | Key for moving the cursor down |
| `inputMoveEnd` | End | String | Key for moving the cursor to the end of the list |
| `inputMoveHome` | Home | String | Key for moving the cursor to the top of the list |
Expand Down
4 changes: 4 additions & 0 deletions src/config/key_bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ pub(crate) struct KeyBindings {
pub(crate) help: Vec<String>,
/// Key bindings for inserting a line.
pub(crate) insert_line: Vec<String>,
/// Key bindings for duplicating a line.
pub(crate) duplicate_line: Vec<String>,

/// Key bindings for moving down.
pub(crate) move_down: Vec<String>,
Expand Down Expand Up @@ -139,6 +141,7 @@ impl KeyBindings {
force_rebase: get_input(git_config, "interactive-rebase-tool.inputForceRebase", "W")?,
help: get_input(git_config, "interactive-rebase-tool.inputHelp", "?")?,
insert_line: get_input(git_config, "interactive-rebase-tool.insertLine", "I")?,
duplicate_line: get_input(git_config, "interactive-rebase-tool.inputDuplicateLine", "control+d")?,
move_down: get_input(git_config, "interactive-rebase-tool.inputMoveDown", "Down")?,
move_end: get_input(git_config, "interactive-rebase-tool.inputMoveEnd", "End")?,
move_home: get_input(git_config, "interactive-rebase-tool.inputMoveHome", "Home")?,
Expand Down Expand Up @@ -253,6 +256,7 @@ mod tests {
config_test!(force_rebase, "inputForceRebase", "W");
config_test!(help, "inputHelp", "?");
config_test!(insert_line, "insertLine", "I");
config_test!(duplicate_line, "inputDuplicateLine", "Controld");
config_test!(move_down, "inputMoveDown", "Down");
config_test!(move_end, "inputMoveEnd", "End");
config_test!(move_home, "inputMoveHome", "Home");
Expand Down
3 changes: 3 additions & 0 deletions src/input/key_bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ pub(crate) struct KeyBindings {
pub(crate) force_rebase: Vec<Event>,
/// Key bindings for inserting a line.
pub(crate) insert_line: Vec<Event>,
/// Key bindings for inserting a line.
pub(crate) duplicate_line: Vec<Event>,
/// Key bindings for moving down.
pub(crate) move_down: Vec<Event>,
/// Key bindings for moving down a step.
Expand Down Expand Up @@ -131,6 +133,7 @@ impl KeyBindings {
force_abort: map_keybindings(&key_bindings.force_abort),
force_rebase: map_keybindings(&key_bindings.force_rebase),
insert_line: map_keybindings(&key_bindings.insert_line),
duplicate_line: map_keybindings(&key_bindings.duplicate_line),
move_down: map_keybindings(&key_bindings.move_down),
move_down_step: map_keybindings(&key_bindings.move_down_step),
move_end: map_keybindings(&key_bindings.move_end),
Expand Down
2 changes: 2 additions & 0 deletions src/input/standard_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ pub(crate) enum StandardEvent {
ToggleVisualMode,
/// The insert line meta event.
InsertLine,
/// The duplicate line meta event.
DuplicateLine,
/// Fixup specific action to toggle the c option.
FixupKeepMessage,
/// Fixup specific action to toggle the C option.
Expand Down
14 changes: 14 additions & 0 deletions src/modules/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,18 @@ impl List {
results.state(State::Insert);
}

fn duplicate_line(&mut self) {
let mut todo_file = self.todo_file.lock();

if let Some(selected_line) = todo_file.get_selected_line() {
if selected_line.is_duplicatable() {
let new_line = selected_line.clone();
let selected_line_index = todo_file.get_selected_line_index();
todo_file.add_line(selected_line_index + 1, new_line);
}
}
}

fn update_list_view_data(&mut self, context: &RenderContext) -> &ViewData {
let todo_file = self.todo_file.lock();
let is_visual_mode = self.state == ListState::Visual;
Expand Down Expand Up @@ -558,6 +570,7 @@ impl List {
e if key_bindings.force_abort.contains(&e) => Event::from(StandardEvent::ForceAbort),
e if key_bindings.force_rebase.contains(&e) => Event::from(StandardEvent::ForceRebase),
e if key_bindings.insert_line.contains(&e) => Event::from(StandardEvent::InsertLine),
e if key_bindings.duplicate_line.contains(&e) => Event::from(StandardEvent::DuplicateLine),
e if key_bindings.move_down.contains(&e) => Event::from(StandardEvent::MoveCursorDown),
e if key_bindings.move_down_step.contains(&e) => Event::from(StandardEvent::MoveCursorPageDown),
e if key_bindings.move_end.contains(&e) => Event::from(StandardEvent::MoveCursorEnd),
Expand Down Expand Up @@ -695,6 +708,7 @@ impl List {
StandardEvent::ActionBreak => self.action_break(),
StandardEvent::Edit => self.edit(),
StandardEvent::InsertLine => self.insert_line(&mut results),
StandardEvent::DuplicateLine => self.duplicate_line(),
StandardEvent::ShowCommit => self.show_commit(&mut results),
StandardEvent::FixupKeepMessage => self.toggle_option("-C"),
StandardEvent::FixupKeepMessageWithEditor => self.toggle_option("-c"),
Expand Down
1 change: 1 addition & 0 deletions src/modules/list/tests.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod abort_and_rebase;
mod activate;
mod change_action;
mod duplicate_line;
mod edit_mode;
mod external_editor;
mod help;
Expand Down
43 changes: 43 additions & 0 deletions src/modules/list/tests/duplicate_line.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use super::*;
use crate::{action_line, assert_rendered_output, assert_results, process::Artifact};

#[test]
fn duplicate_line_duplicatable() {
testers::module(
&["pick aaa c1"],
&[Event::from(StandardEvent::DuplicateLine)],
None,
|mut test_context| {
let mut module = List::new(&test_context.app_data());
assert_results!(
test_context.handle_event(&mut module),
Artifact::Event(Event::from(StandardEvent::DuplicateLine))
);
assert_rendered_output!(
Body test_context.build_view_data(&mut module),
action_line!(Selected Pick "aaa", "c1"),
action_line!(Pick "aaa", "c1")
);
},
);
}

#[test]
fn duplicate_line_not_duplicatable() {
testers::module(
&["break"],
&[Event::from(StandardEvent::DuplicateLine)],
None,
|mut test_context| {
let mut module = List::new(&test_context.app_data());
assert_results!(
test_context.handle_event(&mut module),
Artifact::Event(Event::from(StandardEvent::DuplicateLine))
);
assert_rendered_output!(
Body test_context.build_view_data(&mut module),
action_line!(Selected Break)
);
},
);
}
1 change: 1 addition & 0 deletions src/modules/list/tests/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ fn normal_mode_help() {
" d |Set selected commits to be dropped",
" E |Edit an exec, label, reset or merge action's content",
" I |Insert a new line",
" Controld|Duplicate selected line",
" Delete |Completely remove the selected lines",
" Controlz|Undo the last change",
" Controly|Redo the previous undone change",
Expand Down
5 changes: 5 additions & 0 deletions src/modules/list/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ fn build_help_lines(key_bindings: &KeyBindings, selector: HelpLinesSelector) ->
"Insert a new line",
HelpLinesSelector::Normal,
),
(
&key_bindings.duplicate_line,
"Duplicate selected line",
HelpLinesSelector::Normal,
),
(
&key_bindings.remove_line,
"Completely remove the selected lines",
Expand Down
1 change: 1 addition & 0 deletions src/test_helpers/create_test_keybindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pub(crate) fn create_test_keybindings() -> KeyBindings {
force_abort: map_keybindings(&[String::from("Q")]),
force_rebase: map_keybindings(&[String::from("W")]),
insert_line: map_keybindings(&[String::from("I")]),
duplicate_line: map_keybindings(&[String::from("ControlD")]),
move_down: map_keybindings(&[String::from("Down")]),
move_down_step: map_keybindings(&[String::from("PageDown")]),
move_end: map_keybindings(&[String::from("End")]),
Expand Down
38 changes: 38 additions & 0 deletions src/todo_file/line.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,25 @@ impl Line {
}
}

/// Can this line be duplicated.
#[must_use]
pub(crate) const fn is_duplicatable(&self) -> bool {
match self.action {
Action::Exec
| Action::Label
| Action::Reset
| Action::Merge
| Action::UpdateRef
| Action::Drop
| Action::Edit
| Action::Fixup
| Action::Pick
| Action::Reword
| Action::Squash => true,
Action::Break | Action::Noop => false,
}
}

/// Has this line been modified
#[must_use]
pub(crate) fn is_modified(&self) -> bool {
Expand Down Expand Up @@ -563,6 +582,25 @@ mod tests {
assert_eq!(line.is_editable(), editable);
}

#[rstest]
#[case::drop(Action::Break, false)]
#[case::drop(Action::Drop, true)]
#[case::edit(Action::Edit, true)]
#[case::exec(Action::Exec, true)]
#[case::fixup(Action::Fixup, true)]
#[case::pick(Action::Noop, false)]
#[case::pick(Action::Pick, true)]
#[case::reword(Action::Reword, true)]
#[case::squash(Action::Squash, true)]
#[case::label(Action::Label, true)]
#[case::reset(Action::Reset, true)]
#[case::merge(Action::Merge, true)]
#[case::update_ref(Action::UpdateRef, true)]
fn is_duplicatable(#[case] from: Action, #[case] duplicatable: bool) {
let line = Line::parse(format!("{from} aaa bbb").as_str()).unwrap();
assert_eq!(line.is_duplicatable(), duplicatable);
}

#[rstest]
#[case::break_action("break")]
#[case::drop("drop aaa comment")]
Expand Down
Loading