cli: Use new widgets for `patch list`
This commit is contained in:
parent
ab74e54949
commit
db8ec5735f
|
|
@ -46,17 +46,13 @@ It will now be listed as one of the project's open patches.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rad patch
|
$ rad patch
|
||||||
|
╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
❲YOU PROPOSED❳
|
│ Define power requirements 191a14e520f2eeff7c0e3ee0a5523c5217eecb89 R0 3e674d1 (flux-capacitor-power) ahead 1, behind 0 │
|
||||||
|
├────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
|
||||||
Define power requirements 191a14e520f R0 3e674d1 (flux-capacitor-power) ahead 1, behind 0
|
│ ● opened by did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) 3 months ago │
|
||||||
└─ * opened by did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) [..]
|
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
└─ * patch id 191a14e520f2eeff7c0e3ee0a5523c5217eecb89
|
```
|
||||||
|
```
|
||||||
❲OTHERS PROPOSED❳
|
|
||||||
|
|
||||||
Nothing to show.
|
|
||||||
|
|
||||||
$ rad patch show 191a14e520f2eeff7c0e3ee0a5523c5217eecb89
|
$ rad patch show 191a14e520f2eeff7c0e3ee0a5523c5217eecb89
|
||||||
╭────────────────────────────────────────────────────────────────────╮
|
╭────────────────────────────────────────────────────────────────────╮
|
||||||
│ Title Define power requirements │
|
│ Title Define power requirements │
|
||||||
|
|
@ -118,3 +114,22 @@ Now, let's checkout the patch that we just created:
|
||||||
$ rad patch checkout 191a14e520f2eeff7c0e3ee0a5523c5217eecb89
|
$ rad patch checkout 191a14e520f2eeff7c0e3ee0a5523c5217eecb89
|
||||||
✓ Switched to branch patch/191a14e520f
|
✓ 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 │
|
||||||
|
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -46,17 +46,11 @@ It will now be listed as one of the project's open patches.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rad patch
|
$ rad patch
|
||||||
|
╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
❲YOU PROPOSED❳
|
│ Define power requirements a07ef7743a32a2e902672ea3526d1db6ee08108a R0 3e674d1 (flux-capacitor-power) ahead 1, behind 0 │
|
||||||
|
├────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
|
||||||
Define power requirements a07ef7743a3 R0 3e674d1 (flux-capacitor-power) ahead 1, behind 0
|
│ ● opened by did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (you) 3 months ago │
|
||||||
└─ * opened by did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (you) [..]
|
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
└─ * patch id a07ef7743a32a2e902672ea3526d1db6ee08108a
|
|
||||||
|
|
||||||
❲OTHERS PROPOSED❳
|
|
||||||
|
|
||||||
Nothing to show.
|
|
||||||
|
|
||||||
$ rad patch show a07ef7743a32a2e902672ea3526d1db6ee08108a
|
$ rad patch show a07ef7743a32a2e902672ea3526d1db6ee08108a
|
||||||
╭────────────────────────────────────────────────────────────────────╮
|
╭────────────────────────────────────────────────────────────────────╮
|
||||||
│ Title Define power requirements │
|
│ Title Define power requirements │
|
||||||
|
|
|
||||||
|
|
@ -27,17 +27,11 @@ $ git branch -r
|
||||||
bob/master
|
bob/master
|
||||||
rad/master
|
rad/master
|
||||||
$ rad patch
|
$ rad patch
|
||||||
|
╭─────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
❲YOU PROPOSED❳
|
│ Define power requirements a07ef7743a32a2e902672ea3526d1db6ee08108a R1 27857ec ahead 2, behind 0 │
|
||||||
|
├─────────────────────────────────────────────────────────────────────────────────────────────────┤
|
||||||
Nothing to show.
|
│ ● opened by did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk 3 months ago │
|
||||||
|
╰─────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
❲OTHERS PROPOSED❳
|
|
||||||
|
|
||||||
Define power requirements a07ef7743a3 R1 27857ec ahead 2, behind 0
|
|
||||||
└─ * opened by did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk [..]
|
|
||||||
└─ * patch id a07ef7743a32a2e902672ea3526d1db6ee08108a
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Wait! There's a mistake. The REQUIREMENTS should be a markdown file. Let's
|
Wait! There's a mistake. The REQUIREMENTS should be a markdown file. Let's
|
||||||
|
|
|
||||||
|
|
@ -80,16 +80,20 @@ pub fn pretty_sync_status(
|
||||||
repo: &git::raw::Repository,
|
repo: &git::raw::Repository,
|
||||||
revision_oid: Oid,
|
revision_oid: Oid,
|
||||||
head_oid: Oid,
|
head_oid: Oid,
|
||||||
) -> anyhow::Result<String> {
|
) -> anyhow::Result<term::Line> {
|
||||||
let (a, b) = repo.graph_ahead_behind(revision_oid, head_oid)?;
|
let (a, b) = repo.graph_ahead_behind(revision_oid, head_oid)?;
|
||||||
if a == 0 && b == 0 {
|
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 ahead = term::format::positive(a);
|
||||||
let behind = term::format::negative(b);
|
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.
|
/// Make a human friendly string for commit version information.
|
||||||
|
|
@ -98,8 +102,9 @@ pub fn pretty_sync_status(
|
||||||
pub fn pretty_commit_version(
|
pub fn pretty_commit_version(
|
||||||
revision_oid: &Oid,
|
revision_oid: &Oid,
|
||||||
repo: &Option<git::raw::Repository>,
|
repo: &Option<git::raw::Repository>,
|
||||||
) -> anyhow::Result<String> {
|
) -> anyhow::Result<term::Line> {
|
||||||
let mut oid = term::format::secondary(term::format::oid(*revision_oid)).to_string();
|
let oid = term::format::secondary(term::format::oid(*revision_oid));
|
||||||
|
let mut line = term::Line::new(oid);
|
||||||
let mut branches: Vec<String> = vec![];
|
let mut branches: Vec<String> = vec![];
|
||||||
|
|
||||||
if let Some(repo) = repo {
|
if let Some(repo) = repo {
|
||||||
|
|
@ -115,14 +120,11 @@ pub fn pretty_commit_version(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if !branches.is_empty() {
|
if !branches.is_empty() {
|
||||||
oid = format!(
|
line.push(term::Label::space());
|
||||||
"{} {}",
|
line.push(term::format::yellow(format!("({})", branches.join(", "))));
|
||||||
oid,
|
|
||||||
term::format::yellow(format!("({})", branches.join(", "))),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(oid)
|
Ok(line)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use radicle::profile::Profile;
|
||||||
use radicle::storage::git::Repository;
|
use radicle::storage::git::Repository;
|
||||||
|
|
||||||
use crate::terminal as term;
|
use crate::terminal as term;
|
||||||
use term::cell::Cell as _;
|
use term::Element as _;
|
||||||
|
|
||||||
use super::common;
|
use super::common;
|
||||||
|
|
||||||
|
|
@ -33,33 +33,18 @@ pub fn run(
|
||||||
other.push((id, patch));
|
other.push((id, patch));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
term::blank();
|
|
||||||
term::print(term::format::badge_secondary("YOU PROPOSED"));
|
|
||||||
|
|
||||||
if own.is_empty() {
|
if own.is_empty() && other.is_empty() {
|
||||||
term::blank();
|
|
||||||
term::print(term::format::italic("Nothing to show."));
|
term::print(term::format::italic("Nothing to show."));
|
||||||
} else {
|
return Ok(());
|
||||||
for (id, patch) in &mut own {
|
|
||||||
term::blank();
|
|
||||||
|
|
||||||
print(&me, id, patch, &workdir, repository)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
term::blank();
|
|
||||||
term::print(term::format::badge_secondary("OTHERS PROPOSED"));
|
|
||||||
|
|
||||||
if other.is_empty() {
|
for (id, patch) in &mut own {
|
||||||
term::blank();
|
print(&me, id, patch, &workdir, repository)?;
|
||||||
term::print(term::format::italic("Nothing to show."));
|
}
|
||||||
} else {
|
for (id, patch) in &mut other {
|
||||||
for (id, patch) in &mut other {
|
print(profile.id(), id, patch, &workdir, repository)?;
|
||||||
term::blank();
|
|
||||||
|
|
||||||
print(profile.id(), id, patch, &workdir, repository)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
term::blank();
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -75,31 +60,43 @@ fn print(
|
||||||
let target_head = common::patch_merge_target_oid(patch.target(), repository)?;
|
let target_head = common::patch_merge_target_oid(patch.target(), repository)?;
|
||||||
|
|
||||||
let you = patch.author().id().as_key() == whoami;
|
let you = patch.author().id().as_key() == whoami;
|
||||||
let prefix = "└─ ";
|
let mut author_info = term::Line::spaced([
|
||||||
let mut author_info = vec![format!(
|
term::format::positive("●").into(),
|
||||||
"{}* opened by {}",
|
term::format::default("opened by").into(),
|
||||||
prefix,
|
term::format::tertiary(patch.author().id()).into(),
|
||||||
term::format::tertiary(patch.author().id()),
|
]);
|
||||||
)];
|
|
||||||
|
|
||||||
if you {
|
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
|
let (_, revision) = patch
|
||||||
.latest()
|
.latest()
|
||||||
.ok_or_else(|| anyhow!("patch is malformed: no revisions found"))?;
|
.ok_or_else(|| anyhow!("patch is malformed: no revisions found"))?;
|
||||||
term::info!(
|
let mut widget = term::VStack::default()
|
||||||
"{} {} {} {} {}",
|
.child(
|
||||||
term::format::bold(patch.title()),
|
term::Line::spaced([
|
||||||
term::format::highlight(term::format::cob(patch_id)),
|
term::format::bold(patch.title()).into(),
|
||||||
term::format::dim(format!("R{}", patch.version())),
|
term::format::highlight(patch_id).into(),
|
||||||
common::pretty_commit_version(&revision.oid, workdir)?,
|
term::format::dim(format!("R{}", patch.version())).into(),
|
||||||
common::pretty_sync_status(repository.raw(), *revision.oid, target_head)?,
|
])
|
||||||
);
|
.space()
|
||||||
term::info!("{}", author_info.join(" "));
|
.extend(common::pretty_commit_version(&revision.oid, workdir)?)
|
||||||
term::info!("{prefix}* patch id {}", term::format::highlight(patch_id));
|
.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();
|
let mut timeline = Vec::new();
|
||||||
for merge in revision.merges.iter() {
|
for merge in revision.merges.iter() {
|
||||||
|
|
@ -107,20 +104,22 @@ fn print(
|
||||||
let mut badges = Vec::new();
|
let mut badges = Vec::new();
|
||||||
|
|
||||||
if peer.delegate {
|
if peer.delegate {
|
||||||
badges.push(term::format::secondary("(delegate)").to_string());
|
badges.push(term::format::secondary("(delegate)").into());
|
||||||
}
|
}
|
||||||
if peer.id == *whoami {
|
if peer.id == *whoami {
|
||||||
badges.push(term::format::secondary("(you)").to_string());
|
badges.push(term::format::primary("(you)").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
timeline.push((
|
timeline.push((
|
||||||
merge.timestamp,
|
merge.timestamp,
|
||||||
format!(
|
term::Line::spaced(
|
||||||
"{}{} by {} {}",
|
[
|
||||||
" ".repeat(prefix.width()),
|
term::format::secondary(term::format::dim("✓ merged")).into(),
|
||||||
term::format::secondary(term::format::dim("✓ merged")),
|
term::format::default("by").into(),
|
||||||
term::format::tertiary(peer.id),
|
term::format::tertiary(peer.id).into(),
|
||||||
badges.join(" "),
|
]
|
||||||
|
.into_iter()
|
||||||
|
.chain(badges),
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
@ -134,32 +133,33 @@ fn print(
|
||||||
let mut badges = Vec::new();
|
let mut badges = Vec::new();
|
||||||
|
|
||||||
if peer.delegate {
|
if peer.delegate {
|
||||||
badges.push(term::format::secondary("(delegate)").to_string());
|
badges.push(term::format::secondary("(delegate)").into());
|
||||||
}
|
}
|
||||||
if peer.id == *whoami {
|
if peer.id == *whoami {
|
||||||
badges.push(term::format::secondary("(you)").to_string());
|
badges.push(term::format::primary("(you)").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
timeline.push((
|
timeline.push((
|
||||||
review.timestamp(),
|
review.timestamp(),
|
||||||
format!(
|
term::Line::spaced(
|
||||||
"{}{} by {} {}",
|
[
|
||||||
" ".repeat(prefix.width()),
|
verdict.into(),
|
||||||
verdict,
|
term::format::default("by").into(),
|
||||||
term::format::tertiary(reviewer),
|
term::format::tertiary(reviewer).into(),
|
||||||
badges.join(" "),
|
]
|
||||||
|
.into_iter()
|
||||||
|
.chain(badges),
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
timeline.sort_by_key(|(t, _)| *t);
|
timeline.sort_by_key(|(t, _)| *t);
|
||||||
|
|
||||||
for (time, event) in timeline.iter().rev() {
|
for (time, mut line) in timeline.into_iter().rev() {
|
||||||
term::info!(
|
line.push(term::Label::space());
|
||||||
"{} {}",
|
line.push(term::format::dim(term::format::timestamp(&time)));
|
||||||
event,
|
widget.push(line);
|
||||||
term::format::dim(term::format::timestamp(time))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
widget.print();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ pub fn run(
|
||||||
|
|
||||||
let description = patch.description().trim();
|
let description = patch.description().trim();
|
||||||
let meta = VStack::default()
|
let meta = VStack::default()
|
||||||
.border(Some(term::ansi::Color::Blue))
|
.border(Some(term::colors::FAINT))
|
||||||
.child(attrs)
|
.child(attrs)
|
||||||
.blank()
|
.blank()
|
||||||
.children(if !description.is_empty() {
|
.children(if !description.is_empty() {
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ Options
|
||||||
-r, --revision <number> Revision number to review, defaults to the latest
|
-r, --revision <number> Revision number to review, defaults to the latest
|
||||||
--[no-]sync Sync review to seed (default: sync)
|
--[no-]sync Sync review to seed (default: sync)
|
||||||
-m, --message [<string>] Provide a comment with the review (default: prompt)
|
-m, --message [<string>] 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
|
--no-message Don't provide a comment with the review
|
||||||
--help Print help
|
--help Print help
|
||||||
"#,
|
"#,
|
||||||
|
|
@ -50,6 +51,7 @@ pub struct Options {
|
||||||
pub message: Message,
|
pub message: Message,
|
||||||
pub sync: bool,
|
pub sync: bool,
|
||||||
pub verbose: bool,
|
pub verbose: bool,
|
||||||
|
pub confirm: bool,
|
||||||
pub verdict: Option<Verdict>,
|
pub verdict: Option<Verdict>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,6 +65,7 @@ impl Args for Options {
|
||||||
let mut message = Message::default();
|
let mut message = Message::default();
|
||||||
let mut sync = true;
|
let mut sync = true;
|
||||||
let mut verbose = false;
|
let mut verbose = false;
|
||||||
|
let mut confirm = true;
|
||||||
let mut verdict = None;
|
let mut verdict = None;
|
||||||
|
|
||||||
while let Some(arg) = parser.next()? {
|
while let Some(arg) = parser.next()? {
|
||||||
|
|
@ -90,6 +93,9 @@ impl Args for Options {
|
||||||
message.append(&txt);
|
message.append(&txt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Long("no-confirm") => {
|
||||||
|
confirm = false;
|
||||||
|
}
|
||||||
Long("no-message") => {
|
Long("no-message") => {
|
||||||
message = Message::Blank;
|
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"))?,
|
id: id.ok_or_else(|| anyhow!("a patch id to review must be provided"))?,
|
||||||
message,
|
message,
|
||||||
sync,
|
sync,
|
||||||
|
confirm,
|
||||||
revision,
|
revision,
|
||||||
verbose,
|
verbose,
|
||||||
verdict,
|
verdict,
|
||||||
|
|
@ -158,13 +165,15 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
Some(Verdict::Reject) => term::format::negative("Reject"),
|
Some(Verdict::Reject) => term::format::negative("Reject"),
|
||||||
None => term::format::dim("Review"),
|
None => term::format::dim("Review"),
|
||||||
};
|
};
|
||||||
if !term::confirm(format!(
|
if options.confirm
|
||||||
"{} {} {} by {}?",
|
&& !term::confirm(format!(
|
||||||
verdict_pretty,
|
"{} {} {} by {}?",
|
||||||
patch_id_pretty,
|
verdict_pretty,
|
||||||
term::format::dim(format!("R{revision_ix}")),
|
patch_id_pretty,
|
||||||
term::format::tertiary(patch.author().id())
|
term::format::dim(format!("R{revision_ix}")),
|
||||||
)) {
|
term::format::tertiary(patch.author().id())
|
||||||
|
))
|
||||||
|
{
|
||||||
anyhow::bail!("Patch review aborted");
|
anyhow::bail!("Patch review aborted");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
use crate::ansi::Color;
|
||||||
|
|
||||||
|
/// The faintest color; useful for borders and such.
|
||||||
|
pub const FAINT: Color = Color::Fixed(236);
|
||||||
|
|
@ -71,6 +71,16 @@ impl Line {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn spaced(items: impl IntoIterator<Item = Label>) -> 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.
|
/// Add an item to this line.
|
||||||
pub fn item(mut self, item: impl Into<Label>) -> Self {
|
pub fn item(mut self, item: impl Into<Label>) -> Self {
|
||||||
self.push(item);
|
self.push(item);
|
||||||
|
|
@ -90,10 +100,12 @@ impl Line {
|
||||||
|
|
||||||
/// Pad this line to occupy the given width.
|
/// Pad this line to occupy the given width.
|
||||||
pub fn pad(&mut self, width: usize) {
|
pub fn pad(&mut self, width: usize) {
|
||||||
let w = Element::columns(self);
|
let w = self.columns();
|
||||||
let pad = width - w;
|
|
||||||
|
|
||||||
self.items.push(Label::new(" ".repeat(pad).as_str()));
|
if width > w {
|
||||||
|
let pad = width - w;
|
||||||
|
self.items.push(Label::new(" ".repeat(pad).as_str()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Truncate this line to the given width.
|
/// Truncate this line to the given width.
|
||||||
|
|
@ -108,6 +120,11 @@ impl Line {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn space(mut self) -> Self {
|
||||||
|
self.items.push(Label::space());
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoIterator for Line {
|
impl IntoIterator for Line {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,10 @@ pub fn positive<D: std::fmt::Display>(msg: D) -> Paint<D> {
|
||||||
Paint::green(msg).bold()
|
Paint::green(msg).bold()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn primary<D: std::fmt::Display>(msg: D) -> Paint<D> {
|
||||||
|
Paint::magenta(msg)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn secondary<D: std::fmt::Display>(msg: D) -> Paint<D> {
|
pub fn secondary<D: std::fmt::Display>(msg: D) -> Paint<D> {
|
||||||
Paint::blue(msg).bold()
|
Paint::blue(msg).bold()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,14 @@ pub struct HStack<'a> {
|
||||||
impl<'a> HStack<'a> {
|
impl<'a> HStack<'a> {
|
||||||
/// Add an element to the stack.
|
/// Add an element to the stack.
|
||||||
pub fn child(mut self, child: impl Element + 'a) -> Self {
|
pub fn child(mut self, child: impl Element + 'a) -> Self {
|
||||||
self.width += child.columns();
|
self.push(child);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push(&mut self, child: impl Element + 'a) {
|
||||||
|
self.width += child.columns() + 1;
|
||||||
self.height = self.height.max(child.rows());
|
self.height = self.height.max(child.rows());
|
||||||
self.elems.push(Box::new(child));
|
self.elems.push(Box::new(child));
|
||||||
self
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,11 @@ impl Label {
|
||||||
pub fn blank() -> Self {
|
pub fn blank() -> Self {
|
||||||
Self(Paint::default())
|
Self(Paint::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a single space label
|
||||||
|
pub fn space() -> Self {
|
||||||
|
Self(Paint::new(" ".to_owned()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element for Label {
|
impl Element for Label {
|
||||||
|
|
@ -51,19 +56,10 @@ impl Cell for Label {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Paint<String>> for Label {
|
impl<D: fmt::Display> From<Paint<D>> for Label {
|
||||||
fn from(paint: Paint<String>) -> Self {
|
fn from(paint: Paint<D>) -> Self {
|
||||||
Self(Paint {
|
Self(Paint {
|
||||||
item: paint.item.replace('\n', " "),
|
item: paint.item.to_string().replace('\n', " "),
|
||||||
style: paint.style,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Paint<&str>> for Label {
|
|
||||||
fn from(paint: Paint<&str>) -> Self {
|
|
||||||
Self(Paint {
|
|
||||||
item: paint.item.replace('\n', " "),
|
|
||||||
style: paint.style,
|
style: paint.style,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
pub mod ansi;
|
pub mod ansi;
|
||||||
pub mod cell;
|
pub mod cell;
|
||||||
|
pub mod colors;
|
||||||
pub mod command;
|
pub mod command;
|
||||||
pub mod editor;
|
pub mod editor;
|
||||||
pub mod element;
|
pub mod element;
|
||||||
|
|
|
||||||
|
|
@ -6,21 +6,26 @@ pub struct VStackOptions {
|
||||||
border: Option<Color>,
|
border: Option<Color>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A vertical stack row.
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
enum Row<'a> {
|
||||||
|
Element(Box<dyn Element + 'a>),
|
||||||
|
#[default]
|
||||||
|
Dividier,
|
||||||
|
}
|
||||||
/// Vertical stack of [`Element`] objects that implements [`Element`].
|
/// Vertical stack of [`Element`] objects that implements [`Element`].
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct VStack<'a> {
|
pub struct VStack<'a> {
|
||||||
elems: Vec<Box<dyn Element + 'a>>,
|
rows: Vec<Row<'a>>,
|
||||||
opts: VStackOptions,
|
opts: VStackOptions,
|
||||||
width: usize,
|
width: usize,
|
||||||
height: usize,
|
height: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> VStack<'a> {
|
impl<'a> VStack<'a> {
|
||||||
/// Add an element to the stack.
|
/// Add an element to the stack and return the stack.
|
||||||
pub fn child(mut self, child: impl Element + 'a) -> Self {
|
pub fn child(mut self, child: impl Element + 'a) -> Self {
|
||||||
self.width = self.width.max(child.columns());
|
self.push(child);
|
||||||
self.height += child.rows();
|
|
||||||
self.elems.push(Box::new(child));
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -29,6 +34,13 @@ impl<'a> VStack<'a> {
|
||||||
self.child(Label::blank())
|
self.child(Label::blank())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a horizontal divider.
|
||||||
|
pub fn divider(mut self) -> Self {
|
||||||
|
self.rows.push(Row::Dividier);
|
||||||
|
self.height += 1;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Add multiple elements to the stack.
|
/// Add multiple elements to the stack.
|
||||||
pub fn children<E: Element + 'a>(self, children: impl IntoIterator<Item = E>) -> Self {
|
pub fn children<E: Element + 'a>(self, children: impl IntoIterator<Item = E>) -> Self {
|
||||||
let mut vstack = self;
|
let mut vstack = self;
|
||||||
|
|
@ -44,6 +56,13 @@ impl<'a> VStack<'a> {
|
||||||
self.opts.border = color;
|
self.opts.border = color;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add an element to the stack.
|
||||||
|
pub fn push(&mut self, child: impl Element + 'a) {
|
||||||
|
self.width = self.width.max(child.columns());
|
||||||
|
self.height += child.rows();
|
||||||
|
self.rows.push(Row::Element(Box::new(child)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Element for VStack<'a> {
|
impl<'a> Element for VStack<'a> {
|
||||||
|
|
@ -67,18 +86,34 @@ impl<'a> Element for VStack<'a> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for elem in &self.elems {
|
for row in &self.rows {
|
||||||
for mut line in elem.render() {
|
match row {
|
||||||
if let Some(color) = self.opts.border {
|
Row::Element(elem) => {
|
||||||
line.pad(self.width);
|
for mut line in elem.render() {
|
||||||
lines.push(
|
if let Some(color) = self.opts.border {
|
||||||
Line::default()
|
line.pad(self.width);
|
||||||
.item(Paint::new("│ ").fg(color))
|
lines.push(
|
||||||
.extend(line)
|
Line::default()
|
||||||
.item(Paint::new(" │").fg(color)),
|
.item(Paint::new("│ ").fg(color))
|
||||||
);
|
.extend(line)
|
||||||
} else {
|
.item(Paint::new(" │").fg(color)),
|
||||||
lines.push(line);
|
);
|
||||||
|
} else {
|
||||||
|
lines.push(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row::Dividier => {
|
||||||
|
if let Some(color) = self.opts.border {
|
||||||
|
lines.push(
|
||||||
|
Line::default()
|
||||||
|
.item(Paint::new("├").fg(color))
|
||||||
|
.item(Paint::new("─".repeat(self.width + 2)).fg(color))
|
||||||
|
.item(Paint::new("┤").fg(color)),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
lines.push(Line::default());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue