Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
term, cli, remote-helper: Status Symbols and improved sync output
Merged lorenz opened 8 months ago

A new table style that is more narrow, and follows the recent change to rad node status. Deduplicate and clean up symbols we use to indicate status along the way.

12 files changed +126 -96 1cd3ad07 c8b6a13d
modified crates/radicle-cli/examples/rad-merge-via-push.md
@@ -79,8 +79,8 @@ $ rad patch --merged
╭─────────────────────────────────────────────────────────────────────────────╮
│ ●  ID       Title          Author         Reviews  Head     +   -   Updated │
├─────────────────────────────────────────────────────────────────────────────┤
-
│ ✔  [ ... ]  Second change  alice   (you)  -        daf349f  +0  -0  now     │
-
│ ✔  [ ... ]  First change   alice   (you)  -        20aa5dd  +0  -0  now     │
+
│ ✓  [ ... ]  Second change  alice   (you)  -        daf349f  +0  -0  now     │
+
│ ✓  [ ... ]  First change   alice   (you)  -        20aa5dd  +0  -0  now     │
╰─────────────────────────────────────────────────────────────────────────────╯
$ rad patch show 696ec5508494692899337afe6713fe1796d0315c
╭────────────────────────────────────────────────────────────────╮
@@ -158,6 +158,6 @@ $ rad patch --all
│ ●  ID       Title          Author         Reviews  Head     +   -   Updated │
├─────────────────────────────────────────────────────────────────────────────┤
│ ●  356f738  Second change  alice   (you)  -        daf349f  +0  -0  now     │
-
│ ✔  696ec55  First change   alice   (you)  -        20aa5dd  +0  -0  now     │
+
│ ✓  696ec55  First change   alice   (you)  -        20aa5dd  +0  -0  now     │
╰─────────────────────────────────────────────────────────────────────────────╯
```
modified crates/radicle-cli/examples/rad-patch.md
@@ -206,7 +206,7 @@ $ rad patch list
╭─────────────────────────────────────────────────────────────────────────────────────────╮
│ ●  ID       Title                      Author         Reviews  Head     +   -   Updated │
├─────────────────────────────────────────────────────────────────────────────────────────┤
-
│ ●  aa45913  Define power requirements  alice   (you)  ✔        27857ec  +0  -0  now     │
+
│ ●  aa45913  Define power requirements  alice   (you)  ✓        27857ec  +0  -0  now     │
╰─────────────────────────────────────────────────────────────────────────────────────────╯
```

modified crates/radicle-cli/examples/rad-sync.md
@@ -12,13 +12,13 @@ change has not yet been announced.

```
$ rad sync status --sort-by alias
-
╭──────────────────────────────────────────────────────────────────────────────────────────────╮
-
│ ●   Node                      Address                      Status        Tip       Timestamp │
-
├──────────────────────────────────────────────────────────────────────────────────────────────┤
-
│ ●   alice   (you)             alice.radicle.example:8776   unannounced   056b1db   [  ...  ] │
-
│ ●   bob     z6Mkt67…v4N1tRk   bob.radicle.example:8776     out-of-sync   99c5497   [  ...  ] │
-
│ ●   eve     z6Mkux1…nVhib7Z   eve.radicle.example:8776     out-of-sync   99c5497   [  ...  ] │
-
╰──────────────────────────────────────────────────────────────────────────────────────────────╯
+
╭───────────────────────────────────────────────────╮
+
│ Node ID           Alias   ?   SigRefs   Timestamp │
+
├───────────────────────────────────────────────────┤
+
│ (you)             alice   !   056b1db   [..]      │
+
│ z6Mkt67…v4N1tRk   bob     ✗   99c5497   [..]      │
+
│ z6Mkux1…nVhib7Z   eve     ✗   99c5497   [..]      │
+
╰───────────────────────────────────────────────────╯
```

Now let's run `rad sync`. This will announce the issue refs to the network and
@@ -34,13 +34,13 @@ Now, when we run `rad sync status` again, we can see that `bob` and

```
$ rad sync status --sort-by alias
-
╭─────────────────────────────────────────────────────────────────────────────────────────╮
-
│ ●   Node                      Address                      Status   Tip       Timestamp │
-
├─────────────────────────────────────────────────────────────────────────────────────────┤
-
│ ●   alice   (you)             alice.radicle.example:8776            056b1db   [  ...  ] │
-
│ ●   bob     z6Mkt67…v4N1tRk   bob.radicle.example:8776     synced   056b1db   [  ...  ] │
-
│ ●   eve     z6Mkux1…nVhib7Z   eve.radicle.example:8776     synced   056b1db   [  ...  ] │
-
╰─────────────────────────────────────────────────────────────────────────────────────────╯
+
╭───────────────────────────────────────────────────╮
+
│ Node ID           Alias   ?   SigRefs   Timestamp │
+
├───────────────────────────────────────────────────┤
+
│ (you)             alice   ✓   056b1db   [..]      │
+
│ z6Mkt67…v4N1tRk   bob     ✓   056b1db   [..]      │
+
│ z6Mkux1…nVhib7Z   eve     ✓   056b1db   [..]      │
+
╰───────────────────────────────────────────────────╯
```

If we try to sync again after the nodes have synced, we will already
modified crates/radicle-cli/src/commands/id.rs
@@ -588,7 +588,7 @@ fn print_meta(revision: &Revision, previous: &Doc, profile: &Profile) -> anyhow:
    for id in accepted {
        let author = term::format::Author::new(&id, profile);
        signatures.push([
-
            term::format::positive("✓").into(),
+
            term::PREFIX_SUCCESS.into(),
            id.to_string().into(),
            author.alias().unwrap_or_default(),
            author.you().unwrap_or_default(),
@@ -597,7 +597,7 @@ fn print_meta(revision: &Revision, previous: &Doc, profile: &Profile) -> anyhow:
    for id in rejected {
        let author = term::format::Author::new(&id, profile);
        signatures.push([
-
            term::format::negative("✗").into(),
+
            term::PREFIX_ERROR.into(),
            id.to_string().into(),
            author.alias().unwrap_or_default(),
            author.you().unwrap_or_default(),
modified crates/radicle-cli/src/commands/node/control.rs
@@ -412,15 +412,15 @@ fn state_initial() -> term::Paint<String> {
}

fn state_attempted() -> term::Paint<String> {
-
    term::format::yellow("!".to_string())
+
    term::PREFIX_WARNING.into()
}

fn state_connected() -> term::Paint<String> {
-
    term::format::positive("✓".to_string())
+
    term::PREFIX_SUCCESS.into()
}

fn state_disconnected() -> term::Paint<String> {
-
    term::format::negative("✗".to_string())
+
    term::PREFIX_ERROR.into()
}

fn link_direction_label() -> term::Paint<String> {
modified crates/radicle-cli/src/commands/patch/list.rs
@@ -142,7 +142,7 @@ pub fn row(
            patch::State::Open { .. } => term::format::positive("●").into(),
            patch::State::Archived => term::format::yellow("●").into(),
            patch::State::Draft => term::format::dim("●").into(),
-
            patch::State::Merged { .. } => term::format::primary("✔").into(),
+
            patch::State::Merged { .. } => term::PREFIX_SUCCESS.into(),
        },
        term::format::tertiary(term::format::cob(id)).into(),
        term::format::default(patch.title().to_owned()).into(),
modified crates/radicle-cli/src/commands/sync.rs
@@ -12,6 +12,7 @@ use radicle::node;
use radicle::node::address::Store;
use radicle::node::sync;
use radicle::node::sync::fetch::SuccessfulOutcome;
+
use radicle::node::SyncedAt;
use radicle::node::{AliasStore, Handle as _, Node, Seed, SyncStatus};
use radicle::prelude::{NodeId, Profile, RepoId};
use radicle::storage::ReadRepository;
@@ -340,71 +341,63 @@ fn sync_status(
    profile: &Profile,
    options: &Options,
) -> anyhow::Result<()> {
-
    let mut table = Table::<7, term::Label>::new(TableOptions::bordered());
+
    const SYMBOL_STATE: &str = "?";
+
    const SYMBOL_STATE_UNKNOWN: &str = "•";
+

+
    let mut table = Table::<5, term::Label>::new(TableOptions::bordered());
    let mut seeds: Vec<_> = node.seeds(rid)?.into();
    let local_nid = node.nid()?;
    let aliases = profile.aliases();

    table.header([
-
        term::format::dim(String::from("●")).into(),
-
        term::format::bold(String::from("Node")).into(),
-
        term::Label::blank(),
-
        term::format::bold(String::from("Address")).into(),
-
        term::format::bold(String::from("Status")).into(),
-
        term::format::bold(String::from("Tip")).into(),
-
        term::format::bold(String::from("Timestamp")).into(),
+
        term::format::bold("Node ID").into(),
+
        term::format::bold("Alias").into(),
+
        term::format::bold(SYMBOL_STATE).into(),
+
        term::format::bold("SigRefs").into(),
+
        term::format::bold("Timestamp").into(),
    ]);
    table.divider();

    sort_seeds_by(local_nid, &mut seeds, &aliases, &options.sort_by);

    for seed in seeds {
-
        let (icon, status, head, time) = match seed.sync {
-
            Some(SyncStatus::Synced { at }) => (
-
                term::format::positive("●"),
-
                term::format::positive(if seed.nid != local_nid { "synced" } else { "" }),
-
                term::format::oid(at.oid),
-
                term::format::timestamp(at.timestamp),
+
        let (status, head, time) = match seed.sync {
+
            Some(SyncStatus::Synced {
+
                at: SyncedAt { oid, timestamp },
+
            }) => (
+
                term::PREFIX_SUCCESS,
+
                term::format::oid(oid),
+
                term::format::timestamp(timestamp),
            ),
-
            Some(SyncStatus::OutOfSync { remote, local, .. }) => (
-
                if seed.nid != local_nid {
-
                    term::format::negative("●")
-
                } else {
-
                    term::format::yellow("●")
-
                },
-
                if seed.nid != local_nid {
-
                    term::format::negative("out-of-sync")
-
                } else {
-
                    term::format::yellow("unannounced")
-
                },
-
                term::format::oid(if seed.nid != local_nid {
-
                    remote.oid
-
                } else {
-
                    local.oid
-
                }),
-
                term::format::timestamp(remote.timestamp),
+
            Some(SyncStatus::OutOfSync {
+
                remote: SyncedAt { timestamp, .. },
+
                local,
+
                ..
+
            }) if seed.nid == local_nid => (
+
                term::PREFIX_WARNING,
+
                term::format::oid(local.oid),
+
                term::format::timestamp(timestamp),
+
            ),
+
            Some(SyncStatus::OutOfSync {
+
                remote: SyncedAt { oid, timestamp },
+
                ..
+
            }) => (
+
                term::PREFIX_ERROR,
+
                term::format::oid(oid),
+
                term::format::timestamp(timestamp),
            ),
            None if options.verbose => (
-
                term::format::dim("●"),
-
                term::format::dim("unknown"),
+
                term::format::dim(SYMBOL_STATE_UNKNOWN),
                term::paint(String::new()),
                term::paint(String::new()),
            ),
            None => continue,
        };
-
        let addr = seed
-
            .addrs
-
            .first()
-
            .map(|a| a.addr.to_string())
-
            .unwrap_or_default()
-
            .into();
        let (alias, nid) = Author::new(&seed.nid, profile).labels();

        table.push([
-
            icon.into(),
-
            alias,
            nid,
-
            addr,
+
            alias,
            status.into(),
            term::format::secondary(head).into(),
            time.dim().italic().into(),
@@ -412,6 +405,38 @@ fn sync_status(
    }
    table.print();

+
    if profile.hints() {
+
        const COLUMN_WIDTH: usize = 16;
+
        let status = format!(
+
            "\n{:>4} … {}\n       {}   {}\n       {}   {}",
+
            term::Paint::from(SYMBOL_STATE.to_string()).fg(radicle_term::Color::White),
+
            term::format::dim("Status:"),
+
            format_args!(
+
                "{} {:width$}",
+
                term::PREFIX_SUCCESS,
+
                term::format::dim("… in sync"),
+
                width = COLUMN_WIDTH,
+
            ),
+
            format_args!(
+
                "{} {}",
+
                term::PREFIX_ERROR,
+
                term::format::dim("… out of sync")
+
            ),
+
            format_args!(
+
                "{} {:width$}",
+
                term::PREFIX_WARNING,
+
                term::format::dim("… not announced"),
+
                width = COLUMN_WIDTH,
+
            ),
+
            format_args!(
+
                "{} {}",
+
                term::format::dim(SYMBOL_STATE_UNKNOWN),
+
                term::format::dim("… unknown")
+
            ),
+
        );
+
        term::hint(status);
+
    }
+

    Ok(())
}

modified crates/radicle-cli/src/terminal/format.rs
@@ -341,8 +341,8 @@ pub mod patch {

    pub fn verdict(v: Option<Verdict>) -> term::Paint<String> {
        match v {
-
            Some(Verdict::Accept) => term::format::positive("✔".to_string()),
-
            Some(Verdict::Reject) => term::format::negative("✗".to_string()),
+
            Some(Verdict::Accept) => term::PREFIX_SUCCESS.into(),
+
            Some(Verdict::Reject) => term::PREFIX_ERROR.into(),
            None => term::format::dim("-".to_string()),
        }
    }
modified crates/radicle-cli/src/terminal/patch/timeline.rs
@@ -290,8 +290,8 @@ impl Update<'_> {
            Update::Reviewed { review } => {
                let verdict = review.verdict();
                let verdict_symbol = match verdict {
-
                    Some(Verdict::Accept) => term::format::positive("✓"),
-
                    Some(Verdict::Reject) => term::format::negative("✗"),
+
                    Some(Verdict::Accept) => term::PREFIX_SUCCESS,
+
                    Some(Verdict::Reject) => term::PREFIX_ERROR,
                    None => term::format::dim("⋄"),
                };
                let verdict_verb = match verdict {
@@ -310,7 +310,7 @@ impl Update<'_> {
            Update::Merged { author, merge } => {
                let (alias, nid) = author.labels();
                term::Line::spaced([
-
                    term::format::primary("✓").bold().into(),
+
                    term::PREFIX_SUCCESS.bold().into(),
                    term::format::default("merged by").into(),
                    alias,
                    nid,
modified crates/radicle-remote-helper/src/push.rs
@@ -390,7 +390,7 @@ pub fn run(
            let print_update = || {
                eprintln!(
                    "{} Canonical reference {} updated to target {kind} {}",
-
                    term::format::positive("✓"),
+
                    term::PREFIX_SUCCESS,
                    term::format::secondary(refname),
                    term::format::secondary(oid),
                )
@@ -530,7 +530,7 @@ where

            eprintln!(
                "{} Patch {} {action}",
-
                term::format::positive("✓"),
+
                term::PREFIX_SUCCESS,
                term::format::tertiary(patch),
            );

@@ -630,7 +630,7 @@ where

    eprintln!(
        "{} Patch {} updated to revision {}",
-
        term::format::positive("✓"),
+
        term::PREFIX_SUCCESS,
        term::format::tertiary(term::format::cob(&patch_id)),
        term::format::dim(revision.id())
    );
@@ -745,13 +745,13 @@ where
                    Ok(()) => {
                        eprintln!(
                            "{} Patch {} reverted at revision {}",
-
                            term::format::yellow("!"),
+
                            term::PREFIX_WARNING,
                            term::format::tertiary(&id),
                            term::format::dim(term::format::oid(*revision_id)),
                        );
                    }
                    Err(e) => {
-
                        eprintln!("{} Error reverting patch {id}: {e}", term::ERROR_PREFIX);
+
                        eprintln!("{} Error reverting patch {id}: {e}", term::PREFIX_ERROR);
                    }
                }
                break;
@@ -833,13 +833,13 @@ where
    if revision == latest {
        eprintln!(
            "{} Patch {} merged",
-
            term::format::positive("✓"),
+
            term::PREFIX_SUCCESS,
            term::format::tertiary(merged.patch)
        );
    } else {
        eprintln!(
            "{} Patch {} merged at revision {}",
-
            term::format::positive("✓"),
+
            term::PREFIX_SUCCESS,
            term::format::tertiary(merged.patch),
            term::format::dim(term::format::oid(revision)),
        );
modified crates/radicle-term/src/io.rs
@@ -17,9 +17,14 @@ use crate::{style, Paint, Size};
pub use inquire;
pub use inquire::Select;

-
pub const ERROR_PREFIX: Paint<&str> = Paint::red("✗");
-
pub const ERROR_HINT_PREFIX: Paint<&str> = Paint::yellow("✗ Hint:");
-
pub const WARNING_PREFIX: Paint<&str> = Paint::yellow("!");
+
pub(crate) const SYMBOL_ERROR: &str = "✗";
+
pub(crate) const SYMBOL_SUCCESS: &str = "✓";
+
pub(crate) const SYMBOL_WARNING: &str = "!";
+

+
pub const PREFIX_ERROR: Paint<&str> = Paint::red(SYMBOL_ERROR);
+
pub const PREFIX_SUCCESS: Paint<&str> = Paint::green(SYMBOL_SUCCESS);
+
pub const PREFIX_WARNING: Paint<&str> = Paint::yellow(SYMBOL_WARNING);
+

pub const TAB: &str = "    ";

/// Passphrase input.
@@ -29,15 +34,15 @@ pub type Passphrase = Zeroizing<String>;
pub static CONFIG: LazyLock<RenderConfig> = LazyLock::new(|| RenderConfig {
    prompt: StyleSheet::new().with_fg(Color::LightCyan),
    prompt_prefix: Styled::new("?").with_fg(Color::LightBlue),
-
    answered_prompt_prefix: Styled::new("✓").with_fg(Color::LightGreen),
+
    answered_prompt_prefix: Styled::new(SYMBOL_SUCCESS).with_fg(Color::LightGreen),
    answer: StyleSheet::new(),
-
    highlighted_option_prefix: Styled::new("✓").with_fg(Color::LightYellow),
+
    highlighted_option_prefix: Styled::new(SYMBOL_SUCCESS).with_fg(Color::LightYellow),
    selected_option: Some(StyleSheet::new().with_fg(Color::LightYellow)),
    option: StyleSheet::new(),
    help_message: StyleSheet::new().with_fg(Color::DarkGrey),
    default_value: StyleSheet::new().with_fg(Color::LightBlue),
    error_message: ErrorMessageRenderConfig::default_colored()
-
        .with_prefix(Styled::new("✗").with_fg(Color::LightRed)),
+
        .with_prefix(Styled::new(SYMBOL_ERROR).with_fg(Color::LightRed)),
    ..RenderConfig::default_colored()
});

@@ -87,7 +92,7 @@ pub use success;
pub use tip;

pub fn success_args<W: io::Write>(w: &mut W, args: fmt::Arguments) {
-
    writeln!(w, "{} {args}", Paint::green("✓")).ok();
+
    writeln!(w, "{PREFIX_SUCCESS} {args}").ok();
}

pub fn tip_args(args: fmt::Arguments) {
@@ -99,7 +104,7 @@ pub fn tip_args(args: fmt::Arguments) {
}

pub fn notice_args<W: io::Write>(w: &mut W, args: fmt::Arguments) {
-
    writeln!(w, "{} {args}", Paint::new("!").dim()).ok();
+
    writeln!(w, "{} {args}", Paint::new(SYMBOL_WARNING).dim()).ok();
}

pub fn columns() -> Option<usize> {
@@ -167,7 +172,7 @@ pub fn manual(name: &str) -> io::Result<process::ExitStatus> {
pub fn usage(name: &str, usage: &str) {
    println!(
        "{} {}\n{}",
-
        ERROR_PREFIX,
+
        PREFIX_ERROR,
        Paint::red(format!("Error: rad-{name}: invalid usage")),
        Paint::red(prefixed(TAB, usage)).dim()
    );
@@ -188,17 +193,17 @@ pub fn subcommand(msg: impl fmt::Display) {
pub fn warning(warning: impl fmt::Display) {
    println!(
        "{} {} {warning}",
-
        WARNING_PREFIX,
+
        PREFIX_WARNING,
        Paint::yellow("Warning:").bold(),
    );
}

pub fn error(error: impl fmt::Display) {
-
    println!("{ERROR_PREFIX} {} {error}", Paint::red("Error:"));
+
    println!("{PREFIX_ERROR} {} {error}", Paint::red("Error:"));
}

pub fn hint(hint: impl fmt::Display) {
-
    println!("{ERROR_HINT_PREFIX} {}", format::hint(hint));
+
    println!("{}", format::hint(format!("{SYMBOL_ERROR} Hint: {hint}")));
}

pub fn ask<D: fmt::Display>(prompt: D, default: bool) -> bool {
modified crates/radicle-term/src/spinner.rs
@@ -3,7 +3,7 @@ use std::mem::ManuallyDrop;
use std::sync::{Arc, Mutex};
use std::{fmt, io, thread, time};

-
use crate::io::{ERROR_PREFIX, WARNING_PREFIX};
+
use crate::io::{PREFIX_ERROR, PREFIX_WARNING};
use crate::Paint;

/// How much time to wait between spinner animation updates.
@@ -161,7 +161,7 @@ pub fn spinner_to(
                                write!(animation, "\r{CLEAR_UNTIL_NEWLINE}").ok();
                                writeln!(
                                    completion,
-
                                    "{ERROR_PREFIX} {} {}",
+
                                    "{PREFIX_ERROR} {} {}",
                                    &progress.message,
                                    Paint::red("<canceled>")
                                )
@@ -190,7 +190,7 @@ pub fn spinner_to(
                            message,
                        } => {
                            write!(animation, "\r{CLEAR_UNTIL_NEWLINE}").ok();
-
                            writeln!(completion, "{} {message}", Paint::green("✓")).ok();
+
                            writeln!(completion, "{} {message}", super::PREFIX_SUCCESS).ok();
                            break;
                        }
                        Progress {
@@ -200,7 +200,7 @@ pub fn spinner_to(
                            write!(animation, "\r{CLEAR_UNTIL_NEWLINE}").ok();
                            writeln!(
                                completion,
-
                                "{ERROR_PREFIX} {message} {}",
+
                                "{PREFIX_ERROR} {message} {}",
                                Paint::red("<canceled>")
                            )
                            .ok();
@@ -211,7 +211,7 @@ pub fn spinner_to(
                            message,
                        } => {
                            write!(animation, "\r{CLEAR_UNTIL_NEWLINE}").ok();
-
                            writeln!(completion, "{WARNING_PREFIX} {message}").ok();
+
                            writeln!(completion, "{PREFIX_WARNING} {message}").ok();
                            break;
                        }
                        Progress {
@@ -219,7 +219,7 @@ pub fn spinner_to(
                            message,
                        } => {
                            write!(animation, "\r{CLEAR_UNTIL_NEWLINE}").ok();
-
                            writeln!(completion, "{ERROR_PREFIX} {message}").ok();
+
                            writeln!(completion, "{PREFIX_ERROR} {message}").ok();
                            break;
                        }
                    }