From e59e196da8b8fe5df1fdcf3187e30ccf2990db3c Mon Sep 17 00:00:00 2001 From: cloudhead Date: Wed, 30 Aug 2023 17:20:34 +0200 Subject: [PATCH] cli: Show repo summary on `rad clone` After cloning, we show a cool little summary with some repo stats. We had to make some adjustments to the way visual width is calculated due to a bug surfaced around emoji width calculation. Unfortunately there isn't a perfect solution that works across terminals yet, so the "variation selector 16" code-point is filtered out for now. --- radicle-cli/examples/rad-clone-all.md | 6 +++ radicle-cli/examples/rad-clone-connect.md | 6 +++ radicle-cli/examples/rad-clone.md | 6 +++ radicle-cli/examples/rad-patch-pull-update.md | 6 +++ radicle-cli/src/commands/clone.rs | 41 ++++++++++++++++--- radicle-term/src/cell.rs | 7 ++-- radicle-term/src/element.rs | 8 ++++ radicle-term/src/label.rs | 16 ++++++-- radicle-term/src/lib.rs | 2 +- 9 files changed, 84 insertions(+), 14 deletions(-) diff --git a/radicle-cli/examples/rad-clone-all.md b/radicle-cli/examples/rad-clone-all.md index d1846840..6db70dc0 100644 --- a/radicle-cli/examples/rad-clone-all.md +++ b/radicle-cli/examples/rad-clone-all.md @@ -10,6 +10,12 @@ $ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --scope all ✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added ✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi ✓ Repository successfully cloned under [..]/heartwood/ +╭────────────────────────────────────╮ +│ heartwood │ +│ Radicle Heartwood Protocol & Stack │ +│ 0 issues · 0 patches │ +╰────────────────────────────────────╯ +Run `cd ./heartwood` to go to the project directory. ``` We can now have a look at the new working copy that was created from the cloned diff --git a/radicle-cli/examples/rad-clone-connect.md b/radicle-cli/examples/rad-clone-connect.md index af23c657..8433a51f 100644 --- a/radicle-cli/examples/rad-clone-connect.md +++ b/radicle-cli/examples/rad-clone-connect.md @@ -13,4 +13,10 @@ $ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji ✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added ✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi ✓ Repository successfully cloned under [..]/heartwood/ +╭────────────────────────────────────╮ +│ heartwood │ +│ Radicle Heartwood Protocol & Stack │ +│ 0 issues · 0 patches │ +╰────────────────────────────────────╯ +Run `cd ./heartwood` to go to the project directory. ``` diff --git a/radicle-cli/examples/rad-clone.md b/radicle-cli/examples/rad-clone.md index 8632875f..a601d5d7 100644 --- a/radicle-cli/examples/rad-clone.md +++ b/radicle-cli/examples/rad-clone.md @@ -10,6 +10,12 @@ $ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --scope trusted ✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added ✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi ✓ Repository successfully cloned under [..]/heartwood/ +╭────────────────────────────────────╮ +│ heartwood │ +│ Radicle Heartwood Protocol & Stack │ +│ 0 issues · 0 patches │ +╰────────────────────────────────────╯ +Run `cd ./heartwood` to go to the project directory. ``` We can now have a look at the new working copy that was created from the cloned diff --git a/radicle-cli/examples/rad-patch-pull-update.md b/radicle-cli/examples/rad-patch-pull-update.md index 5c9b062f..58855856 100644 --- a/radicle-cli/examples/rad-patch-pull-update.md +++ b/radicle-cli/examples/rad-patch-pull-update.md @@ -24,6 +24,12 @@ $ rad clone rad:zhbMU4DUXrzB8xT6qAJh6yZ7bFMK ✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added ✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi ✓ Repository successfully cloned under [..]/heartwood/ +╭────────────────────────────────────╮ +│ heartwood │ +│ radicle heartwood protocol & stack │ +│ 0 issues · 0 patches │ +╰────────────────────────────────────╯ +Run `cd ./heartwood` to go to the project directory. ``` We wait for Alice to sync our fork. diff --git a/radicle-cli/src/commands/clone.rs b/radicle-cli/src/commands/clone.rs index 633ba94a..87c516ae 100644 --- a/radicle-cli/src/commands/clone.rs +++ b/radicle-cli/src/commands/clone.rs @@ -7,6 +7,7 @@ use std::time; use anyhow::anyhow; use thiserror::Error; +use radicle::cob; use radicle::git::raw; use radicle::identity::doc::{DocError, Id}; use radicle::identity::{doc, IdentityError}; @@ -23,6 +24,7 @@ use crate::commands::rad_sync as sync; use crate::project; use crate::terminal as term; use crate::terminal::args::{Args, Error, Help}; +use crate::terminal::Element as _; pub const HELP: Help = Help { name: "clone", @@ -113,7 +115,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { ); } - let (working, doc, proj) = clone( + let (working, repo, doc, proj) = clone( options.id, options.announce, options.scope, @@ -145,7 +147,28 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { term::success!( "Repository successfully cloned under {}", - term::format::highlight(Path::new(".").join(path).display()) + term::format::dim(Path::new(".").join(path).display()) + ); + + let mut info: term::Table<1, term::Line> = term::Table::new(term::TableOptions::bordered()); + info.push([term::format::bold(proj.name()).into()]); + info.push([term::format::italic(proj.description()).into()]); + + let issues = cob::issue::Issues::open(&repo)?.counts()?; + let patches = cob::patch::Patches::open(&repo)?.counts()?; + + info.push([term::Line::spaced([ + term::format::tertiary(issues.open).into(), + term::format::default("issues").into(), + term::format::dim("·").into(), + term::format::tertiary(patches.open).into(), + term::format::default("patches").into(), + ])]); + info.print(); + + term::info!( + "Run {} to go to the project directory.", + term::format::command(format!("cd ./{}", proj.name())), ); Ok(()) @@ -180,7 +203,15 @@ pub fn clone( node: &mut Node, signer: &G, storage: &Storage, -) -> Result<(raw::Repository, Doc, Project), CloneError> { +) -> Result< + ( + raw::Repository, + storage::git::Repository, + Doc, + Project, + ), + CloneError, +> { let me = *signer.public_key(); // Track. @@ -244,9 +275,9 @@ pub fn clone( "Creating checkout in ./{}..", term::format::tertiary(path.display()) )); - let repo = rad::checkout(id, &me, path, &storage)?; + let working = rad::checkout(id, &me, path, &storage)?; spinner.finish(); - Ok((repo, doc, proj)) + Ok((working, repository, doc, proj)) } diff --git a/radicle-term/src/cell.rs b/radicle-term/src/cell.rs index eb646e71..fb8e8c18 100644 --- a/radicle-term/src/cell.rs +++ b/radicle-term/src/cell.rs @@ -2,6 +2,7 @@ use std::fmt::Display; use super::{Element, Line, Paint}; +use unicode_segmentation::UnicodeSegmentation as _; use unicode_width::UnicodeWidthStr; /// Text that can be displayed on the terminal, measured, truncated and padded. @@ -26,7 +27,7 @@ impl Cell for Paint { type Padded = Self; fn width(&self) -> usize { - UnicodeWidthStr::width(self.content()) + Cell::width(self.content()) } fn truncate(&self, width: usize, delim: &str) -> Self { @@ -110,12 +111,10 @@ impl Cell for str { type Padded = String; fn width(&self) -> usize { - UnicodeWidthStr::width(self) + self.graphemes(true).map(UnicodeWidthStr::width).sum() } fn truncate(&self, width: usize, delim: &str) -> String { - use unicode_segmentation::UnicodeSegmentation as _; - if width < Cell::width(self) { let d = Cell::width(delim); if width < d { diff --git a/radicle-term/src/element.rs b/radicle-term/src/element.rs index cc233bb6..0209a835 100644 --- a/radicle-term/src/element.rs +++ b/radicle-term/src/element.rs @@ -237,4 +237,12 @@ mod test { actual = actual.truncate(0, "…"); assert_eq!(actual.to_string(), ""); } + + #[test] + fn test_width() { + let line = Line::new("Radicle Heartwood Protocol & Stack ❤️ 🪵"); + assert_eq!(line.width(), 39, "{line}"); + let line = Line::new("❤\u{fe0f}"); + assert_eq!(line.width(), 1, "{line}"); + } } diff --git a/radicle-term/src/label.rs b/radicle-term/src/label.rs index b114fb92..b072428e 100644 --- a/radicle-term/src/label.rs +++ b/radicle-term/src/label.rs @@ -9,7 +9,7 @@ pub struct Label(Paint); impl Label { /// Create a new label. pub fn new(s: &str) -> Self { - Self(Paint::new(s.replace('\n', " "))) + Self(Paint::new(cleanup(s))) } /// Create a blank label. @@ -64,7 +64,7 @@ impl Cell for Label { impl From> for Label { fn from(paint: Paint) -> Self { Self(Paint { - item: paint.item.to_string().replace('\n', " "), + item: cleanup(paint.item.to_string().as_str()), style: paint.style, }) } @@ -72,13 +72,13 @@ impl From> for Label { impl From for Label { fn from(value: String) -> Self { - Label::from(value.as_str()) + Self::new(value.as_str()) } } impl From<&str> for Label { fn from(value: &str) -> Self { - Self(Paint::new(value.replace('\n', " "))) + Self::new(value) } } @@ -86,3 +86,11 @@ impl From<&str> for Label { pub fn label(s: impl Into>) -> Label { Label(s.into()) } + +/// Cleanup the input string for display as a label. +fn cleanup(input: &str) -> String { + input + .chars() + .filter(|c| *c != '\u{fe0f}' && *c != '\n' && *c != '\r') + .collect() +} diff --git a/radicle-term/src/lib.rs b/radicle-term/src/lib.rs index 2e50a2fb..97b6e5bc 100644 --- a/radicle-term/src/lib.rs +++ b/radicle-term/src/lib.rs @@ -22,7 +22,7 @@ pub use inquire::ui::Styled; pub use io::*; pub use label::{label, Label}; pub use spinner::{spinner, spinner_to, Spinner}; -pub use table::Table; +pub use table::{Table, TableOptions}; pub use textarea::{textarea, TextArea}; pub use vstack::{VStack, VStackOptions};