cli: Display patches like issues, in `rad patch`

We also make sure not to bail if one of the patches fails to load.
This commit is contained in:
Alexis Sellier 2023-04-25 13:10:50 +02:00
parent b809bd3d73
commit 3b20d45d80
No known key found for this signature in database
11 changed files with 237 additions and 139 deletions

View File

@ -41,19 +41,22 @@ It will now be listed as one of the project's open patches.
``` ```
$ rad patch $ rad patch
╭─────────────────────────────────────────────────────────────────────────────────────────╮ ╭──────────────────────────────────────────────────────────────────────────────────────────────
Define power requirements 191a14e R0 3e674d1 (flux-capacitor-power) ahead 1, behind 0 ● ID Title Author Head + - Opened
├─────────────────────────────────────────────────────────────────────────────────────────┤ ├──────────────────────────────────────────────────────────────────────────────────────────────
│ ● opened by did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) [ ... ] │ ● 191a14e Define power requirements z6MknSL…StBU8Vi (you) 3e674d1 +0 -0 4 months ago
╰─────────────────────────────────────────────────────────────────────────────────────────╯ ╰──────────────────────────────────────────────────────────────────────────────────────────────
``` ```
``` ```
$ rad patch show 191a14e520f2eeff7c0e3ee0a5523c5217eecb89 -p $ rad patch show 191a14e520f2eeff7c0e3ee0a5523c5217eecb89 -p
╭─────────────────────────────────────────────────────────────────────────────────────────╮ ╭─────────────────────────────────────────────────────────────────────────────────────────╮
│ Title Define power requirements │ │ Title Define power requirements │
│ Patch 191a14e520f2eeff7c0e3ee0a5523c5217eecb89 │ │ Patch 191a14e520f2eeff7c0e3ee0a5523c5217eecb89 │
│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │ │ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │
│ Status open │ │ Head 3e674d1a1df90807e934f9ae5da2591dd6848a33 │
│ Branches flux-capacitor-power │
│ Commits ahead 1, behind 0 │
│ Status open │
│ │ │ │
│ See details. │ │ See details. │
├─────────────────────────────────────────────────────────────────────────────────────────┤ ├─────────────────────────────────────────────────────────────────────────────────────────┤
@ -120,12 +123,20 @@ $ rad review 191a14e520f2eeff7c0e3ee0a5523c5217eecb89 --accept --no-message --no
Showing the patch list now will reveal the favorable verdict: Showing the patch list now will reveal the favorable verdict:
``` ```
$ rad patch $ rad patch show 191a14e
╭──────────────────────────────────────────────────────────────────────────────────────────────────────╮ ╭─────────────────────────────────────────────────────────────────────────────────────────╮
│ Define power requirements 191a14e R1 27857ec (flux-capacitor-power, patch/191a14e) ahead 2, behind 0 │ │ Title Define power requirements │
├──────────────────────────────────────────────────────────────────────────────────────────────────────┤ │ Patch 191a14e520f2eeff7c0e3ee0a5523c5217eecb89 │
│ ● opened by did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) [ ... ]│ │ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │
│ ↑ updated to b8f7bfbbb3c6a207b349e9f45bf535c706805871 (27857ec) [ ... ]│ │ Head 27857ec9eb04c69cacab516e8bf4b5fd36090f66 │
│ ✓ accepted by z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) [ ... ]│ │ Branches flux-capacitor-power, patch/191a14e │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ Commits ahead 2, behind 0 │
│ Status open │
│ │
│ See details. │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ ● opened by did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) [ ... ]│
│ ↑ updated to b8f7bfbbb3c6a207b349e9f45bf535c706805871 (27857ec) [ ... ]│
│ ✓ accepted by z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) [ ... ]│
╰─────────────────────────────────────────────────────────────────────────────────────────╯
``` ```

View File

@ -41,17 +41,20 @@ It will now be listed as one of the project's open patches.
``` ```
$ rad patch $ rad patch
╭─────────────────────────────────────────────────────────────────────────────────────────╮ ╭──────────────────────────────────────────────────────────────────────────────────────────────
Define power requirements a07ef77 R0 3e674d1 (flux-capacitor-power) ahead 1, behind 0 ● ID Title Author Head + - Opened
├─────────────────────────────────────────────────────────────────────────────────────────┤ ├──────────────────────────────────────────────────────────────────────────────────────────────
│ ● opened by did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (you) [ ... ] │ ● a07ef77 Define power requirements z6Mkt67…v4N1tRk (you) 3e674d1 +0 -0 4 months ago
╰─────────────────────────────────────────────────────────────────────────────────────────╯ ╰──────────────────────────────────────────────────────────────────────────────────────────────
$ rad patch show a07ef7743a32a2e902672ea3526d1db6ee08108a $ rad patch show a07ef7743a32a2e902672ea3526d1db6ee08108a
╭─────────────────────────────────────────────────────────────────────────────────────────╮ ╭─────────────────────────────────────────────────────────────────────────────────────────╮
│ Title Define power requirements │ │ Title Define power requirements │
│ Patch a07ef7743a32a2e902672ea3526d1db6ee08108a │ │ Patch a07ef7743a32a2e902672ea3526d1db6ee08108a │
│ Author did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk │ │ Author did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk │
│ Status open │ │ Head 3e674d1a1df90807e934f9ae5da2591dd6848a33 │
│ Branches flux-capacitor-power │
│ Commits ahead 1, behind 0 │
│ Status open │
│ │ │ │
│ See details. │ │ See details. │
├─────────────────────────────────────────────────────────────────────────────────────────┤ ├─────────────────────────────────────────────────────────────────────────────────────────┤

View File

@ -26,12 +26,19 @@ $ git branch -r
bob/flux-capacitor-power bob/flux-capacitor-power
bob/master bob/master
rad/master rad/master
$ rad patch $ rad patch show a07ef77
╭───────────────────────────────────────────────────────────────────────────────────╮ ╭───────────────────────────────────────────────────────────────────────────────────╮
│ Define power requirements a07ef77 R1 27857ec ahead 2, behind 0 │ │ Title Define power requirements │
│ Patch a07ef7743a32a2e902672ea3526d1db6ee08108a │
│ Author did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk │
│ Head 27857ec9eb04c69cacab516e8bf4b5fd36090f66 │
│ Commits ahead 2, behind 0 │
│ Status open │
│ │
│ See details. │
├───────────────────────────────────────────────────────────────────────────────────┤ ├───────────────────────────────────────────────────────────────────────────────────┤
│ ● opened by did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk [ ... ]│ │ ● opened by did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk [ ... ]
│ ↑ updated to 11483929d8714a92992229f65433e06288f3b760 (27857ec) [ ... ]│ │ ↑ updated to 11483929d8714a92992229f65433e06288f3b760 (27857ec) [ ... ]
╰───────────────────────────────────────────────────────────────────────────────────╯ ╰───────────────────────────────────────────────────────────────────────────────────╯
``` ```
@ -76,15 +83,23 @@ Fast-forward
The patch is now merged and closed :). The patch is now merged and closed :).
``` ```
$ rad patch --merged $ rad patch show a07ef77
╭───────────────────────────────────────────────────────────────────────────────────────────────╮ ╭─────────────────────────────────────────────────────────────────────────────────────────╮
│ Define power requirements a07ef77 R2 f6484e0 (flux-capacitor-power, master) ahead 3, behind 0 │ │ Title Define power requirements │
├───────────────────────────────────────────────────────────────────────────────────────────────┤ │ Patch a07ef7743a32a2e902672ea3526d1db6ee08108a │
│ ● opened by did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk [ ... ]│ │ Author did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk │
│ ↑ updated to 11483929d8714a92992229f65433e06288f3b760 (27857ec) [ ... ]│ │ Head f6484e0f43e48a8983b9b39bf9bd4cd889f1d520 │
│ ↑ updated to 0795d619232479e910f95bb9c873ee1ec305c43c (f6484e0) [ ... ]│ │ Branches flux-capacitor-power, master │
│ ✓ merged by did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) [ ... ]│ │ Commits ahead 3, behind 0 │
╰───────────────────────────────────────────────────────────────────────────────────────────────╯ │ Status merged │
│ │
│ See details. │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ ● opened by did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk [ ... ] │
│ ↑ updated to 11483929d8714a92992229f65433e06288f3b760 (27857ec) [ ... ] │
│ ↑ updated to 0795d619232479e910f95bb9c873ee1ec305c43c (f6484e0) [ ... ] │
│ ✓ merged by did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) [ ... ] │
╰─────────────────────────────────────────────────────────────────────────────────────────╯
``` ```
To publish our new state to the network, we simply push: To publish our new state to the network, we simply push:

View File

@ -270,7 +270,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
create::run(&repository, &profile, &workdir, message.clone(), options)?; create::run(&repository, &profile, &workdir, message.clone(), options)?;
} }
Operation::List { filter } => { Operation::List { filter } => {
list::run(&repository, &profile, Some(workdir), filter)?; list::run(&repository, &profile, filter)?;
} }
Operation::Show { patch_id, diff } => { Operation::Show { patch_id, diff } => {
let patch_id = patch_id.resolve(&repository.backend)?; let patch_id = patch_id.resolve(&repository.backend)?;

View File

@ -68,8 +68,23 @@ pub fn patch_merge_target_oid(target: MergeTarget, repository: &Repository) -> a
} }
} }
/// Get the diff stats between two commits.
pub fn diff_stats(
repo: &git::raw::Repository,
old: &Oid,
new: &Oid,
) -> Result<git::raw::DiffStats, git::raw::Error> {
let old = repo.find_commit(*old)?;
let new = repo.find_commit(*new)?;
let old_tree = old.tree()?;
let new_tree = new.tree()?;
let diff = repo.diff_tree_to_tree(Some(&old_tree), Some(&new_tree), None)?;
diff.stats()
}
/// Create a human friendly message about git's sync status. /// Create a human friendly message about git's sync status.
pub fn pretty_sync_status( pub fn ahead_behind(
repo: &git::raw::Repository, repo: &git::raw::Repository,
revision_oid: Oid, revision_oid: Oid,
head_oid: Oid, head_oid: Oid,
@ -89,35 +104,21 @@ pub fn pretty_sync_status(
.item(behind)) .item(behind))
} }
/// Make a human friendly string for commit version information. /// Get the branches that point to a commit.
/// pub fn branches(target: &Oid, repo: &git::raw::Repository) -> anyhow::Result<Vec<String>> {
/// For example '<oid> (branch1[, branch2])'.
pub fn pretty_commit_version(
revision_oid: &Oid,
repo: Option<&git::raw::Repository>,
) -> anyhow::Result<term::Line> {
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 { for r in repo.references()?.flatten() {
for r in repo.references()?.flatten() { if !r.is_branch() {
if !r.is_branch() { continue;
continue;
}
if let (Some(oid), Some(name)) = (&r.target(), &r.shorthand()) {
if oid == revision_oid {
branches.push(name.to_string());
};
};
} }
}; if let (Some(oid), Some(name)) = (&r.target(), &r.shorthand()) {
if !branches.is_empty() { if oid == target {
line.push(term::Label::space()); branches.push(name.to_string());
line.push(term::format::yellow(format!("({})", branches.join(", ")))); };
};
} }
Ok(branches)
Ok(line)
} }
#[inline] #[inline]

View File

@ -1,13 +1,13 @@
use anyhow::anyhow; use anyhow::anyhow;
use radicle::cob::patch; use radicle::cob::patch;
use radicle::cob::patch::{Patch, PatchId, Patches, Revision, Verdict}; use radicle::cob::patch::{Patch, PatchId, Patches, Verdict};
use radicle::git;
use radicle::prelude::*; use radicle::prelude::*;
use radicle::profile::Profile; 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::table::{Table, TableOptions};
use term::Element as _; use term::Element as _;
use super::common; use super::common;
@ -16,7 +16,6 @@ use super::common;
pub fn run( pub fn run(
repository: &Repository, repository: &Repository,
profile: &Profile, profile: &Profile,
workdir: Option<git::raw::Repository>,
filter: Option<patch::State>, filter: Option<patch::State>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let me = *profile.id(); let me = *profile.id();
@ -48,62 +47,89 @@ pub fn run(
return Ok(()); return Ok(());
} }
let mut table = Table::<9, term::Line>::new(TableOptions {
spacing: 2,
border: Some(term::colors::FAINT),
..TableOptions::default()
});
table.push([
term::format::dim(String::from("")).into(),
term::format::bold(String::from("ID")).into(),
term::format::bold(String::from("Title")).into(),
term::format::bold(String::from("Author")).into(),
term::format::bold(String::new()).into(),
term::format::bold(String::from("Head")).into(),
term::format::bold(String::from("+")).into(),
term::format::bold(String::from("-")).into(),
term::format::bold(String::from("Opened")).into(),
]);
table.divider();
let mut errors = Vec::new();
for (id, patch) in &mut own { for (id, patch) in &mut own {
widget(&me, id, patch, workdir.as_ref(), repository)?.print(); match row(&me, id, patch, repository) {
Ok(r) => table.push(r),
Err(e) => errors.push((patch.title(), id, e.to_string())),
}
} }
for (id, patch) in &mut other { for (id, patch) in &mut other {
widget(profile.id(), id, patch, workdir.as_ref(), repository)?.print(); match row(&me, id, patch, repository) {
Ok(r) => table.push(r),
Err(e) => errors.push((patch.title(), id, e.to_string())),
}
}
table.print();
if !errors.is_empty() {
for (title, id, error) in errors {
term::error(format!(
"{} Patch {title:?} ({id}) failed to load: {error}",
term::format::negative("Error:")
));
}
} }
Ok(()) Ok(())
} }
pub fn header( /// Patch row.
patch_id: &PatchId, pub fn row(
patch: &Patch,
workdir: Option<&git::raw::Repository>,
repository: &Repository,
revision: &Revision,
) -> anyhow::Result<term::Line> {
let target_head = common::patch_merge_target_oid(patch.target(), repository)?;
let header = term::Line::spaced([
term::format::bold(patch.title()).into(),
term::format::highlight(term::format::cob(patch_id)).into(),
term::format::dim(format!("R{}", patch.version())).into(),
])
.space()
.extend(common::pretty_commit_version(&revision.head(), workdir)?)
.space()
.extend(common::pretty_sync_status(
repository.raw(),
revision.head().into(),
target_head,
)?);
Ok(header)
}
/// Patch widget.
pub fn widget<'a>(
whoami: &PublicKey, whoami: &PublicKey,
patch_id: &PatchId, id: &PatchId,
patch: &Patch, patch: &Patch,
workdir: Option<&git::raw::Repository>,
repository: &Repository, repository: &Repository,
) -> anyhow::Result<term::VStack<'a>> { ) -> anyhow::Result<[term::Line; 9]> {
let state = patch.state();
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"))?;
let header = header(patch_id, patch, workdir, repository, revision)?; let stats = common::diff_stats(repository.raw(), revision.base(), &revision.head())?;
let mut widget = term::VStack::default() let author = patch.author().id;
.child(header)
.divider()
.border(Some(term::colors::FAINT));
for line in timeline(whoami, patch_id, patch, repository)? { Ok([
widget.push(line); match state {
} patch::State::Open => term::format::positive("").into(),
Ok(widget) patch::State::Archived { .. } => term::format::yellow("").into(),
patch::State::Draft => term::format::dim("").into(),
patch::State::Merged { .. } => term::format::primary("").into(),
},
term::format::tertiary(term::format::cob(id)).into(),
term::format::default(patch.title().to_owned()).into(),
term::format::did(&author).dim().into(),
if author.as_key() == whoami {
term::format::primary("(you)".to_owned()).into()
} else {
term::format::default(String::new()).into()
},
term::format::secondary(term::format::oid(revision.head())).into(),
term::format::positive(format!("+{}", stats.insertions())).into(),
term::format::negative(format!("-{}", stats.deletions())).into(),
term::format::timestamp(&patch.timestamp())
.dim()
.italic()
.into(),
])
} }
pub fn timeline( pub fn timeline(

View File

@ -5,7 +5,7 @@ use radicle::git;
use radicle::storage::git::Repository; use radicle::storage::git::Repository;
use radicle_term::{ use radicle_term::{
table::{Table, TableOptions}, table::{Table, TableOptions},
textarea, Element, Paint, VStack, textarea, Element, VStack,
}; };
use crate::terminal as term; use crate::terminal as term;
@ -13,12 +13,7 @@ use crate::terminal as term;
use super::common::*; use super::common::*;
use super::*; use super::*;
fn show_patch_diff( fn show_patch_diff(patch: &patch::Patch, storage: &Repository) -> anyhow::Result<()> {
patch: &patch::Patch,
storage: &Repository,
// TODO: Tell user which working copy branches point to the patch.
_workdir: &git::raw::Repository,
) -> anyhow::Result<()> {
let target_head = patch_merge_target_oid(patch.target(), storage)?; let target_head = patch_merge_target_oid(patch.target(), storage)?;
let base_oid = storage.raw().merge_base(target_head, **patch.head())?; let base_oid = storage.raw().merge_base(target_head, **patch.head())?;
let diff = format!("{}..{}", base_oid, patch.head()); let diff = format!("{}..{}", base_oid, patch.head());
@ -46,32 +41,53 @@ pub fn run(
let Some(patch) = patches.get(patch_id)? else { let Some(patch) = patches.get(patch_id)? else {
anyhow::bail!("Patch `{patch_id}` not found"); anyhow::bail!("Patch `{patch_id}` not found");
}; };
let (_, revision) = patch
.latest()
.ok_or_else(|| anyhow!("patch is malformed: no revisions found"))?;
let state = patch.state(); let state = patch.state();
let branches = common::branches(&revision.head(), workdir)?;
let target_head = common::patch_merge_target_oid(patch.target(), stored)?;
let ahead_behind = common::ahead_behind(stored.raw(), revision.head().into(), target_head)?;
let mut attrs = Table::<2, Paint<String>>::new(TableOptions { let mut attrs = Table::<2, term::Line>::new(TableOptions {
spacing: 2, spacing: 2,
..TableOptions::default() ..TableOptions::default()
}); });
attrs.push([ attrs.push([
term::format::tertiary("Title".to_owned()), term::format::tertiary("Title".to_owned()).into(),
term::format::bold(patch.title().to_owned()), term::format::bold(patch.title().to_owned()).into(),
]); ]);
attrs.push([ attrs.push([
term::format::tertiary("Patch".to_owned()), term::format::tertiary("Patch".to_owned()).into(),
term::format::default(patch_id.to_string()), term::format::default(patch_id.to_string()).into(),
]); ]);
attrs.push([ attrs.push([
term::format::tertiary("Author".to_owned()), term::format::tertiary("Author".to_owned()).into(),
term::format::default(patch.author().id().to_string()), term::format::default(patch.author().id().to_string()).into(),
]); ]);
attrs.push([ attrs.push([
term::format::tertiary("Status".to_owned()), term::format::tertiary("Head".to_owned()).into(),
term::format::secondary(revision.head().to_string()).into(),
]);
if !branches.is_empty() {
attrs.push([
term::format::tertiary("Branches".to_owned()).into(),
term::format::yellow(branches.join(", ")).into(),
]);
}
attrs.push([
term::format::tertiary("Commits".to_owned()).into(),
ahead_behind,
]);
attrs.push([
term::format::tertiary("Status".to_owned()).into(),
match state { match state {
patch::State::Open => term::format::positive(state.to_string()), patch::State::Open => term::format::positive(state.to_string()),
patch::State::Draft => term::format::dim(state.to_string()), patch::State::Draft => term::format::dim(state.to_string()),
patch::State::Archived => term::format::yellow(state.to_string()), patch::State::Archived => term::format::yellow(state.to_string()),
patch::State::Merged => term::format::primary(state.to_string()), patch::State::Merged => term::format::primary(state.to_string()),
}, }
.into(),
]); ]);
let description = patch.description().trim(); let description = patch.description().trim();
@ -95,7 +111,7 @@ pub fn run(
if diff { if diff {
term::blank(); term::blank();
show_patch_diff(&patch, stored, workdir)?; show_patch_diff(&patch, stored)?;
term::blank(); term::blank();
} }
Ok(()) Ok(())

View File

@ -1,6 +1,6 @@
use std::fmt::Display; use std::fmt::Display;
use super::Paint; use super::{Element, Line, Paint};
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
@ -14,8 +14,10 @@ pub trait Cell: Display {
/// Cell display width in number of terminal columns. /// Cell display width in number of terminal columns.
fn width(&self) -> usize; fn width(&self) -> usize;
/// Truncate cell if longer than given width. Shows the delimiter if truncated. /// Truncate cell if longer than given width. Shows the delimiter if truncated.
#[must_use]
fn truncate(&self, width: usize, delim: &str) -> Self::Truncated; fn truncate(&self, width: usize, delim: &str) -> Self::Truncated;
/// Pad the cell so that it is the given width, while keeping the content left-aligned. /// Pad the cell so that it is the given width, while keeping the content left-aligned.
#[must_use]
fn pad(&self, width: usize) -> Self::Padded; fn pad(&self, width: usize) -> Self::Padded;
} }
@ -42,6 +44,27 @@ impl Cell for Paint<String> {
} }
} }
impl Cell for Line {
type Truncated = Line;
type Padded = Line;
fn width(&self) -> usize {
<Self as Element>::size(self).cols
}
fn pad(&self, width: usize) -> Self::Padded {
let mut line = self.clone();
Line::pad(&mut line, width);
line
}
fn truncate(&self, width: usize, delim: &str) -> Self::Truncated {
let mut line = self.clone();
Line::truncate(&mut line, width, delim);
line
}
}
impl Cell for Paint<&str> { impl Cell for Paint<&str> {
type Truncated = Paint<String>; type Truncated = Paint<String>;
type Padded = Paint<String>; type Padded = Paint<String>;

View File

@ -2,3 +2,6 @@ use crate::ansi::Color;
/// The faintest color; useful for borders and such. /// The faintest color; useful for borders and such.
pub const FAINT: Color = Color::Fixed(236); pub const FAINT: Color = Color::Fixed(236);
/// Negative color, useful for errors.
pub const NEGATIVE: Color = Color::Red;

View File

@ -157,9 +157,9 @@ impl IntoIterator for Line {
} }
} }
impl From<Label> for Line { impl<T: Into<Label>> From<T> for Line {
fn from(label: Label) -> Self { fn from(value: T) -> Self {
Self { items: vec![label] } Self::new(value)
} }
} }
@ -218,19 +218,19 @@ mod test {
let line = Line::default().item("banana").item("peach").item("apple"); let line = Line::default().item("banana").item("peach").item("apple");
let mut actual = line.clone(); let mut actual = line.clone();
actual.truncate(9, ""); actual = actual.truncate(9, "");
assert_eq!(actual.to_string(), "bananape…"); assert_eq!(actual.to_string(), "bananape…");
let mut actual = line.clone(); let mut actual = line.clone();
actual.truncate(7, ""); actual = actual.truncate(7, "");
assert_eq!(actual.to_string(), "banana…"); assert_eq!(actual.to_string(), "banana…");
let mut actual = line.clone(); let mut actual = line.clone();
actual.truncate(1, ""); actual = actual.truncate(1, "");
assert_eq!(actual.to_string(), ""); assert_eq!(actual.to_string(), "");
let mut actual = line; let mut actual = line;
actual.truncate(0, ""); actual = actual.truncate(0, "");
assert_eq!(actual.to_string(), ""); assert_eq!(actual.to_string(), "");
} }
} }

View File

@ -20,7 +20,7 @@ use std::fmt;
use crate as term; use crate as term;
use crate::cell::Cell; use crate::cell::Cell;
use crate::{Color, Label, Line, Max, Paint, Size}; use crate::{Color, Line, Max, Paint, Size};
pub use crate::Element; pub use crate::Element;
@ -82,7 +82,7 @@ impl<const W: usize, T> Default for Table<W, T> {
impl<const W: usize, T: Cell + fmt::Debug> Element for Table<W, T> impl<const W: usize, T: Cell + fmt::Debug> Element for Table<W, T>
where where
T::Padded: Into<Label>, T::Padded: Into<Line>,
{ {
fn size(&self) -> Size { fn size(&self) -> Size {
Table::size(self) Table::size(self)
@ -123,11 +123,11 @@ where
} else { } else {
self.widths[i] + self.opts.spacing self.widths[i] + self.opts.spacing
}; };
line.push(cell.pad(pad)); line = line.extend(cell.pad(pad).into());
} }
if let Some(width) = width { if let Some(width) = width {
line.truncate(width, ""); line = line.truncate(width, "");
} }
if let Some(color) = border { if let Some(color) = border {
line.push(Paint::new("").fg(color)); line.push(Paint::new("").fg(color));