diff --git a/radicle-cli/examples/rad-patch.md b/radicle-cli/examples/rad-patch.md index d55ed55e..00e06fbd 100644 --- a/radicle-cli/examples/rad-patch.md +++ b/radicle-cli/examples/rad-patch.md @@ -46,17 +46,13 @@ It will now be listed as one of the project's open patches. ``` $ rad patch - -❲YOU PROPOSED❳ - -Define power requirements 191a14e520f R0 3e674d1 (flux-capacitor-power) ahead 1, behind 0 -└─ * opened by did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) [..] -└─ * patch id 191a14e520f2eeff7c0e3ee0a5523c5217eecb89 - -❲OTHERS PROPOSED❳ - -Nothing to show. - +╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ Define power requirements 191a14e520f2eeff7c0e3ee0a5523c5217eecb89 R0 3e674d1 (flux-capacitor-power) ahead 1, behind 0 │ +├────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ ● opened by did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) 3 months ago │ +╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ +``` +``` $ rad patch show 191a14e520f2eeff7c0e3ee0a5523c5217eecb89 ╭────────────────────────────────────────────────────────────────────╮ │ Title Define power requirements │ @@ -118,3 +114,22 @@ Now, let's checkout the patch that we just created: $ rad patch checkout 191a14e520f2eeff7c0e3ee0a5523c5217eecb89 ✓ Switched to branch patch/191a14e520f ``` + +We can also add a review verdict as such: + +``` +$ rad review 191a14e520f2eeff7c0e3ee0a5523c5217eecb89 --accept --no-confirm --no-message --no-sync +✓ Patch 191a14e520f accepted +``` + +Showing the patch list now will reveal the favorable verdict: + +``` +$ rad patch +╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ Define power requirements 191a14e520f2eeff7c0e3ee0a5523c5217eecb89 R1 27857ec (flux-capacitor-power, patch/191a14e520f) ahead 2, behind 0 │ +├───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ ● opened by did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) 3 months ago │ +│ ✓ accepted by z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) 3 months ago │ +╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ +``` diff --git a/radicle-cli/examples/workflow/4-patching-contributor.md b/radicle-cli/examples/workflow/4-patching-contributor.md index dac34859..b8f96d4d 100644 --- a/radicle-cli/examples/workflow/4-patching-contributor.md +++ b/radicle-cli/examples/workflow/4-patching-contributor.md @@ -46,17 +46,11 @@ It will now be listed as one of the project's open patches. ``` $ rad patch - -❲YOU PROPOSED❳ - -Define power requirements a07ef7743a3 R0 3e674d1 (flux-capacitor-power) ahead 1, behind 0 -└─ * opened by did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (you) [..] -└─ * patch id a07ef7743a32a2e902672ea3526d1db6ee08108a - -❲OTHERS PROPOSED❳ - -Nothing to show. - +╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ Define power requirements a07ef7743a32a2e902672ea3526d1db6ee08108a R0 3e674d1 (flux-capacitor-power) ahead 1, behind 0 │ +├────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ ● opened by did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (you) 3 months ago │ +╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ $ rad patch show a07ef7743a32a2e902672ea3526d1db6ee08108a ╭────────────────────────────────────────────────────────────────────╮ │ Title Define power requirements │ diff --git a/radicle-cli/examples/workflow/5-patching-maintainer.md b/radicle-cli/examples/workflow/5-patching-maintainer.md index ff25a8d8..3efe1d65 100644 --- a/radicle-cli/examples/workflow/5-patching-maintainer.md +++ b/radicle-cli/examples/workflow/5-patching-maintainer.md @@ -27,17 +27,11 @@ $ git branch -r bob/master rad/master $ rad patch - -❲YOU PROPOSED❳ - -Nothing to show. - -❲OTHERS PROPOSED❳ - -Define power requirements a07ef7743a3 R1 27857ec ahead 2, behind 0 -└─ * opened by did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk [..] -└─ * patch id a07ef7743a32a2e902672ea3526d1db6ee08108a - +╭─────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ Define power requirements a07ef7743a32a2e902672ea3526d1db6ee08108a R1 27857ec ahead 2, behind 0 │ +├─────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ ● opened by did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk 3 months ago │ +╰─────────────────────────────────────────────────────────────────────────────────────────────────╯ ``` Wait! There's a mistake. The REQUIREMENTS should be a markdown file. Let's diff --git a/radicle-cli/src/commands/patch/common.rs b/radicle-cli/src/commands/patch/common.rs index 1c426b95..ad972085 100644 --- a/radicle-cli/src/commands/patch/common.rs +++ b/radicle-cli/src/commands/patch/common.rs @@ -80,16 +80,20 @@ pub fn pretty_sync_status( repo: &git::raw::Repository, revision_oid: Oid, head_oid: Oid, -) -> anyhow::Result { +) -> anyhow::Result { let (a, b) = repo.graph_ahead_behind(revision_oid, head_oid)?; if a == 0 && b == 0 { - return Ok(term::format::dim("up to date").to_string()); + return Ok(term::Line::new(term::format::dim("up to date"))); } let ahead = term::format::positive(a); let behind = term::format::negative(b); - Ok(format!("ahead {ahead}, behind {behind}")) + Ok(term::Line::default() + .item("ahead ") + .item(ahead) + .item(", behind ") + .item(behind)) } /// Make a human friendly string for commit version information. @@ -98,8 +102,9 @@ pub fn pretty_sync_status( pub fn pretty_commit_version( revision_oid: &Oid, repo: &Option, -) -> anyhow::Result { - let mut oid = term::format::secondary(term::format::oid(*revision_oid)).to_string(); +) -> anyhow::Result { + let oid = term::format::secondary(term::format::oid(*revision_oid)); + let mut line = term::Line::new(oid); let mut branches: Vec = vec![]; if let Some(repo) = repo { @@ -115,14 +120,11 @@ pub fn pretty_commit_version( } }; if !branches.is_empty() { - oid = format!( - "{} {}", - oid, - term::format::yellow(format!("({})", branches.join(", "))), - ); + line.push(term::Label::space()); + line.push(term::format::yellow(format!("({})", branches.join(", ")))); } - Ok(oid) + Ok(line) } #[inline] diff --git a/radicle-cli/src/commands/patch/list.rs b/radicle-cli/src/commands/patch/list.rs index 146ca539..d3166dd2 100644 --- a/radicle-cli/src/commands/patch/list.rs +++ b/radicle-cli/src/commands/patch/list.rs @@ -7,7 +7,7 @@ use radicle::profile::Profile; use radicle::storage::git::Repository; use crate::terminal as term; -use term::cell::Cell as _; +use term::Element as _; use super::common; @@ -33,33 +33,18 @@ pub fn run( other.push((id, patch)); } } - term::blank(); - term::print(term::format::badge_secondary("YOU PROPOSED")); - if own.is_empty() { - term::blank(); + if own.is_empty() && other.is_empty() { term::print(term::format::italic("Nothing to show.")); - } else { - for (id, patch) in &mut own { - term::blank(); - - print(&me, id, patch, &workdir, repository)?; - } + return Ok(()); } - term::blank(); - term::print(term::format::badge_secondary("OTHERS PROPOSED")); - if other.is_empty() { - term::blank(); - term::print(term::format::italic("Nothing to show.")); - } else { - for (id, patch) in &mut other { - term::blank(); - - print(profile.id(), id, patch, &workdir, repository)?; - } + for (id, patch) in &mut own { + print(&me, id, patch, &workdir, repository)?; + } + for (id, patch) in &mut other { + print(profile.id(), id, patch, &workdir, repository)?; } - term::blank(); Ok(()) } @@ -75,31 +60,43 @@ fn print( let target_head = common::patch_merge_target_oid(patch.target(), repository)?; let you = patch.author().id().as_key() == whoami; - let prefix = "└─ "; - let mut author_info = vec![format!( - "{}* opened by {}", - prefix, - term::format::tertiary(patch.author().id()), - )]; + let mut author_info = term::Line::spaced([ + term::format::positive("●").into(), + term::format::default("opened by").into(), + term::format::tertiary(patch.author().id()).into(), + ]); if you { - author_info.push(term::format::secondary("(you)").to_string()); + author_info.push(term::Label::space()); + author_info.push(term::format::primary("(you)")); } - author_info.push(term::format::dim(term::format::timestamp(&patch.timestamp())).to_string()); + author_info.push(term::Label::space()); + author_info.push(term::format::dim(term::format::timestamp( + &patch.timestamp(), + ))); let (_, revision) = patch .latest() .ok_or_else(|| anyhow!("patch is malformed: no revisions found"))?; - term::info!( - "{} {} {} {} {}", - term::format::bold(patch.title()), - term::format::highlight(term::format::cob(patch_id)), - term::format::dim(format!("R{}", patch.version())), - common::pretty_commit_version(&revision.oid, workdir)?, - common::pretty_sync_status(repository.raw(), *revision.oid, target_head)?, - ); - term::info!("{}", author_info.join(" ")); - term::info!("{prefix}* patch id {}", term::format::highlight(patch_id)); + let mut widget = term::VStack::default() + .child( + term::Line::spaced([ + term::format::bold(patch.title()).into(), + term::format::highlight(patch_id).into(), + term::format::dim(format!("R{}", patch.version())).into(), + ]) + .space() + .extend(common::pretty_commit_version(&revision.oid, workdir)?) + .space() + .extend(common::pretty_sync_status( + repository.raw(), + *revision.oid, + target_head, + )?), + ) + .divider() + .child(author_info) + .border(Some(term::colors::FAINT)); let mut timeline = Vec::new(); for merge in revision.merges.iter() { @@ -107,20 +104,22 @@ fn print( let mut badges = Vec::new(); if peer.delegate { - badges.push(term::format::secondary("(delegate)").to_string()); + badges.push(term::format::secondary("(delegate)").into()); } if peer.id == *whoami { - badges.push(term::format::secondary("(you)").to_string()); + badges.push(term::format::primary("(you)").into()); } timeline.push(( merge.timestamp, - format!( - "{}{} by {} {}", - " ".repeat(prefix.width()), - term::format::secondary(term::format::dim("✓ merged")), - term::format::tertiary(peer.id), - badges.join(" "), + term::Line::spaced( + [ + term::format::secondary(term::format::dim("✓ merged")).into(), + term::format::default("by").into(), + term::format::tertiary(peer.id).into(), + ] + .into_iter() + .chain(badges), ), )); } @@ -134,32 +133,33 @@ fn print( let mut badges = Vec::new(); if peer.delegate { - badges.push(term::format::secondary("(delegate)").to_string()); + badges.push(term::format::secondary("(delegate)").into()); } if peer.id == *whoami { - badges.push(term::format::secondary("(you)").to_string()); + badges.push(term::format::primary("(you)").into()); } timeline.push(( review.timestamp(), - format!( - "{}{} by {} {}", - " ".repeat(prefix.width()), - verdict, - term::format::tertiary(reviewer), - badges.join(" "), + term::Line::spaced( + [ + verdict.into(), + term::format::default("by").into(), + term::format::tertiary(reviewer).into(), + ] + .into_iter() + .chain(badges), ), )); } timeline.sort_by_key(|(t, _)| *t); - for (time, event) in timeline.iter().rev() { - term::info!( - "{} {}", - event, - term::format::dim(term::format::timestamp(time)) - ); + for (time, mut line) in timeline.into_iter().rev() { + line.push(term::Label::space()); + line.push(term::format::dim(term::format::timestamp(&time))); + widget.push(line); } + widget.print(); Ok(()) } diff --git a/radicle-cli/src/commands/patch/show.rs b/radicle-cli/src/commands/patch/show.rs index 13cf2e19..6c078dff 100644 --- a/radicle-cli/src/commands/patch/show.rs +++ b/radicle-cli/src/commands/patch/show.rs @@ -68,7 +68,7 @@ pub fn run( let description = patch.description().trim(); let meta = VStack::default() - .border(Some(term::ansi::Color::Blue)) + .border(Some(term::colors::FAINT)) .child(attrs) .blank() .children(if !description.is_empty() { diff --git a/radicle-cli/src/commands/review.rs b/radicle-cli/src/commands/review.rs index 3432d1f4..97499ede 100644 --- a/radicle-cli/src/commands/review.rs +++ b/radicle-cli/src/commands/review.rs @@ -28,6 +28,7 @@ Options -r, --revision Revision number to review, defaults to the latest --[no-]sync Sync review to seed (default: sync) -m, --message [] Provide a comment with the review (default: prompt) + --no-confirm Don't ask for confirmation --no-message Don't provide a comment with the review --help Print help "#, @@ -50,6 +51,7 @@ pub struct Options { pub message: Message, pub sync: bool, pub verbose: bool, + pub confirm: bool, pub verdict: Option, } @@ -63,6 +65,7 @@ impl Args for Options { let mut message = Message::default(); let mut sync = true; let mut verbose = false; + let mut confirm = true; let mut verdict = None; while let Some(arg) = parser.next()? { @@ -90,6 +93,9 @@ impl Args for Options { message.append(&txt); } } + Long("no-confirm") => { + confirm = false; + } Long("no-message") => { message = Message::Blank; } @@ -121,6 +127,7 @@ impl Args for Options { id: id.ok_or_else(|| anyhow!("a patch id to review must be provided"))?, message, sync, + confirm, revision, verbose, verdict, @@ -158,13 +165,15 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { Some(Verdict::Reject) => term::format::negative("Reject"), None => term::format::dim("Review"), }; - if !term::confirm(format!( - "{} {} {} by {}?", - verdict_pretty, - patch_id_pretty, - term::format::dim(format!("R{revision_ix}")), - term::format::tertiary(patch.author().id()) - )) { + if options.confirm + && !term::confirm(format!( + "{} {} {} by {}?", + verdict_pretty, + patch_id_pretty, + term::format::dim(format!("R{revision_ix}")), + term::format::tertiary(patch.author().id()) + )) + { anyhow::bail!("Patch review aborted"); } diff --git a/radicle-term/src/colors.rs b/radicle-term/src/colors.rs new file mode 100644 index 00000000..2b93920f --- /dev/null +++ b/radicle-term/src/colors.rs @@ -0,0 +1,4 @@ +use crate::ansi::Color; + +/// The faintest color; useful for borders and such. +pub const FAINT: Color = Color::Fixed(236); diff --git a/radicle-term/src/element.rs b/radicle-term/src/element.rs index 88cea2b7..717d9d5c 100644 --- a/radicle-term/src/element.rs +++ b/radicle-term/src/element.rs @@ -71,6 +71,16 @@ impl Line { } } + pub fn spaced(items: impl IntoIterator) -> Self { + let mut line = Self::default(); + for item in items.into_iter() { + line.push(item); + line.push(Label::space()); + } + line.items.pop(); + line + } + /// Add an item to this line. pub fn item(mut self, item: impl Into