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
32 changes: 23 additions & 9 deletions src/commands/interactive/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use ratatui::{
};

use super::{
Action, EventSource, Focus, StatusMessage, WorktreeEntry,
Action, EventSource, Focus, Selection, StatusMessage, WorktreeEntry,
dialog::{CreateDialog, CreateDialogFocus, Dialog},
view::{DetailData, DialogView, Snapshot},
};
Expand Down Expand Up @@ -72,7 +72,7 @@ where
}
}

pub fn run<F, G>(mut self, mut on_remove: F, mut on_create: G) -> Result<Option<String>>
pub fn run<F, G>(mut self, mut on_remove: F, mut on_create: G) -> Result<Option<Selection>>
where
F: FnMut(&str) -> Result<()>,
G: FnMut(&str, Option<&str>) -> Result<()>,
Expand All @@ -90,7 +90,11 @@ where
result
}

fn event_loop<F, G>(&mut self, on_remove: &mut F, on_create: &mut G) -> Result<Option<String>>
fn event_loop<F, G>(
&mut self,
on_remove: &mut F,
on_create: &mut G,
) -> Result<Option<Selection>>
where
F: FnMut(&str) -> Result<()>,
G: FnMut(&str, Option<&str>) -> Result<()>,
Expand Down Expand Up @@ -203,7 +207,9 @@ where
Focus::Worktrees => {
if let Some(index) = self.selected {
return Ok(LoopControl::Exit(
self.worktrees.get(index).map(|entry| entry.name.clone()),
self.worktrees
.get(index)
.map(|entry| Selection::Worktree(entry.name.clone())),
));
}
}
Expand All @@ -212,7 +218,9 @@ where
match action {
Action::Open => {
if let Some(entry) = self.current_entry() {
return Ok(LoopControl::Exit(Some(entry.name.clone())));
return Ok(LoopControl::Exit(Some(Selection::Worktree(
entry.name.clone(),
))));
}
self.status = Some(StatusMessage::info("No worktree selected."));
}
Expand All @@ -224,6 +232,14 @@ where
Some(StatusMessage::info("No worktree selected to remove."));
}
}
Action::PrGithub => {
if let Some(entry) = self.current_entry() {
return Ok(LoopControl::Exit(Some(Selection::PrGithub(
entry.name.clone(),
))));
}
self.status = Some(StatusMessage::info("No worktree selected."));
}
}
}
Focus::GlobalActions => match self.global_action_selected {
Expand All @@ -233,9 +249,7 @@ where
self.dialog = Some(Dialog::Create(dialog));
}
1 => {
return Ok(LoopControl::Exit(Some(
super::REPO_ROOT_SELECTION.to_string(),
)));
return Ok(LoopControl::Exit(Some(Selection::RepoRoot)));
}
_ => {}
},
Expand Down Expand Up @@ -641,7 +655,7 @@ where

enum LoopControl {
Continue,
Exit(Option<String>),
Exit(Option<Selection>),
}

fn build_detail_data(entry: &WorktreeEntry) -> DetailData {
Expand Down
14 changes: 11 additions & 3 deletions src/commands/interactive/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,26 +55,34 @@ impl Focus {
}

pub(crate) const GLOBAL_ACTIONS: [&str; 2] = ["Create worktree", "Cd to root dir"];
pub(crate) const REPO_ROOT_SELECTION: &str = "__RSWORKTREE_REPO_ROOT__";

#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum Selection {
Worktree(String),
PrGithub(String),
RepoRoot,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Action {
Open,
Remove,
PrGithub,
}

impl Action {
pub(crate) const ALL: [Action; 2] = [Action::Open, Action::Remove];
pub(crate) const ALL: [Action; 3] = [Action::Open, Action::Remove, Action::PrGithub];

pub(crate) fn label(self) -> &'static str {
match self {
Action::Open => "Open",
Action::Remove => "Remove",
Action::PrGithub => "PR (GitHub)",
}
}

pub(crate) fn requires_selection(self) -> bool {
matches!(self, Action::Open | Action::Remove)
matches!(self, Action::Open | Action::Remove | Action::PrGithub)
}

pub(crate) fn from_index(index: usize) -> Self {
Expand Down
32 changes: 25 additions & 7 deletions src/commands/interactive/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ use crate::{
cd::{CdCommand, shell_command},
create::{CreateCommand, CreateOutcome},
list::{find_worktrees, format_worktree},
pr_github::{PrGithubCommand, PrGithubOptions},
rm::RemoveCommand,
},
};

use super::{EventSource, REPO_ROOT_SELECTION, WorktreeEntry, command::InteractiveCommand};
use super::{EventSource, Selection, WorktreeEntry, command::InteractiveCommand};

pub struct CrosstermEvents;

Expand Down Expand Up @@ -91,12 +92,29 @@ pub fn run(repo: &Repo) -> Result<()> {
}
};

if let Some(name) = selection {
if name == REPO_ROOT_SELECTION {
cd_repo_root(repo)?;
} else {
let command = CdCommand::new(name, false);
command.execute(repo)?;
if let Some(selection) = selection {
match selection {
Selection::RepoRoot => {
cd_repo_root(repo)?;
}
Selection::Worktree(name) => {
let command = CdCommand::new(name, false);
command.execute(repo)?;
}
Selection::PrGithub(name) => {
let options = PrGithubOptions {
name,
push: true,
draft: false,
fill: false,
web: false,
remote: String::from("origin"),
reviewers: Vec::new(),
extra_args: Vec::new(),
};
let mut command = PrGithubCommand::new(options);
command.execute(repo)?;
}
}
}

Expand Down
45 changes: 40 additions & 5 deletions src/commands/interactive/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ fn returns_first_worktree_when_enter_pressed_immediately() -> Result<()> {
let selection = command
.run(|_| Ok(()), |_, _| panic!("create should not be called"))?
.expect("expected selection");
assert_eq!(selection, "alpha");
assert_eq!(selection, Selection::Worktree(String::from("alpha")));

Ok(())
}
Expand All @@ -81,7 +81,42 @@ fn navigates_down_before_selecting() -> Result<()> {
let selection = command
.run(|_| Ok(()), |_, _| panic!("create should not be called"))?
.expect("expected selection");
assert_eq!(selection, "beta");
assert_eq!(selection, Selection::Worktree(String::from("beta")));

Ok(())
}

#[test]
fn selecting_pr_github_action_exits_with_pr_variant() -> Result<()> {
let backend = TestBackend::new(40, 12);
let terminal = Terminal::new(backend)?;
let events = StubEvents::new(vec![
key(KeyCode::Tab),
key(KeyCode::Down),
key(KeyCode::Down),
key(KeyCode::Enter),
]);
let worktrees = entries(&["alpha", "beta"]);
let command = InteractiveCommand::new(
terminal,
events,
PathBuf::from("/tmp/worktrees"),
worktrees,
vec![String::from("main")],
Some(String::from("main")),
);

let mut removed = Vec::new();
let result = command.run(
|name| {
removed.push(name.to_owned());
Ok(())
},
|_, _| panic!("create should not be called"),
)?;

assert!(removed.is_empty(), "remove should not be triggered");
assert_eq!(result, Some(Selection::PrGithub(String::from("alpha"))));

Ok(())
}
Expand Down Expand Up @@ -199,7 +234,7 @@ fn create_action_adds_new_worktree() -> Result<()> {
},
)?;

assert_eq!(result, Some(String::from("new")));
assert_eq!(result, Some(Selection::Worktree(String::from("new"))));
assert_eq!(
created,
vec![(String::from("new"), Some(String::from("main")))]
Expand Down Expand Up @@ -260,7 +295,7 @@ fn cd_to_root_global_action_exits() -> Result<()> {

let result = command.run(|_| Ok(()), |_, _| Ok(()))?;

assert_eq!(result, Some(String::from(super::REPO_ROOT_SELECTION)));
assert_eq!(result, Some(Selection::RepoRoot));

Ok(())
}
Expand All @@ -287,7 +322,7 @@ fn up_from_top_moves_to_global_actions() -> Result<()> {

let result = command.run(|_| Ok(()), |_, _| Ok(()))?;

assert_eq!(result, Some(String::from(super::REPO_ROOT_SELECTION)));
assert_eq!(result, Some(Selection::RepoRoot));

Ok(())
}