radicle-cli/issue: Improve comment templating
Writing a comment, only the most recent comment replied to is shown. Editing a comment, current contents are not prepared. Manual copy required. To improve: - Chase all replies to the root and quote them. Gracefully handle if this proces fails, i.e. a comment in the chain cannot be resolved. - If editing, copy the current contents.
This commit is contained in:
parent
af35e6f4d0
commit
78ba263d0a
|
|
@ -11,6 +11,7 @@ use radicle::cob::common::{Label, Reaction};
|
|||
use radicle::cob::issue::{CloseReason, State};
|
||||
use radicle::cob::{issue, thread};
|
||||
use radicle::crypto;
|
||||
use radicle::git::Oid;
|
||||
use radicle::issue::cache::Issues as _;
|
||||
use radicle::node::device::Device;
|
||||
use radicle::node::NodeId;
|
||||
|
|
@ -521,11 +522,18 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
message,
|
||||
reply_to,
|
||||
} => {
|
||||
let reply_to = reply_to
|
||||
.map(|rev| rev.resolve::<radicle::git::Oid>(repo.raw()))
|
||||
.transpose()?;
|
||||
|
||||
let signer = term::signer(&profile)?;
|
||||
let issue_id = id.resolve::<cob::ObjectId>(&repo.backend)?;
|
||||
let mut issue = issues.get_mut(&issue_id)?;
|
||||
let (body, reply_to) = prompt_comment(message, reply_to, &issue, &repo)?;
|
||||
let comment_id = issue.comment(body, reply_to, vec![], &signer)?;
|
||||
|
||||
let (root_comment_id, _) = issue.root();
|
||||
let body = prompt_comment(message, issue.thread(), reply_to, None)?;
|
||||
let comment_id =
|
||||
issue.comment(body, reply_to.unwrap_or(*root_comment_id), vec![], &signer)?;
|
||||
|
||||
if options.quiet {
|
||||
term::print(comment_id);
|
||||
|
|
@ -543,7 +551,18 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
let issue_id = id.resolve::<cob::ObjectId>(&repo.backend)?;
|
||||
let comment_id = comment_id.resolve(&repo.backend)?;
|
||||
let mut issue = issues.get_mut(&issue_id)?;
|
||||
let (body, _) = prompt_comment(message, None, &issue, &repo)?;
|
||||
|
||||
let comment = issue
|
||||
.thread()
|
||||
.comment(&comment_id)
|
||||
.ok_or(anyhow::anyhow!("comment '{comment_id}' not found"))?;
|
||||
|
||||
let body = prompt_comment(
|
||||
message,
|
||||
issue.thread(),
|
||||
comment.reply_to(),
|
||||
Some(comment.body()),
|
||||
)?;
|
||||
issue.edit_comment(comment_id, body, vec![], &signer)?;
|
||||
|
||||
if options.quiet {
|
||||
|
|
@ -892,29 +911,92 @@ where
|
|||
}
|
||||
|
||||
/// Get a comment from the user, by prompting.
|
||||
pub fn prompt_comment<R: WriteRepository + radicle::cob::Store<Namespace = NodeId>>(
|
||||
pub fn prompt_comment(
|
||||
message: Message,
|
||||
reply_to: Option<Rev>,
|
||||
issue: &issue::Issue,
|
||||
repo: &R,
|
||||
) -> anyhow::Result<(String, thread::CommentId)> {
|
||||
let (root, r) = issue.root();
|
||||
let (reply_to, help) = if let Some(rev) = reply_to {
|
||||
let id = rev.resolve::<radicle::git::Oid>(repo.raw())?;
|
||||
let parent = issue
|
||||
.thread()
|
||||
.comment(&id)
|
||||
.ok_or(anyhow::anyhow!("comment '{rev}' not found"))?;
|
||||
thread: &thread::Thread,
|
||||
mut reply_to: Option<Oid>,
|
||||
edit: Option<&str>,
|
||||
) -> anyhow::Result<String> {
|
||||
let (chase, missing) = {
|
||||
let mut chase = Vec::with_capacity(thread.len());
|
||||
let mut missing = None;
|
||||
|
||||
(id, parent.body().trim())
|
||||
} else {
|
||||
(*root, r.body().trim())
|
||||
while let Some(id) = reply_to {
|
||||
if let Some(comment) = thread.comment(&id) {
|
||||
chase.push(comment);
|
||||
reply_to = comment.reply_to();
|
||||
} else {
|
||||
missing = reply_to;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
(chase, missing)
|
||||
};
|
||||
let help = format!("\n{}\n", term::format::html::commented(help));
|
||||
let body = message.get(&help)?;
|
||||
|
||||
let quotes = if chase.is_empty() {
|
||||
""
|
||||
} else {
|
||||
"Quotes (lines starting with '>') will be preserved. Please remove those that you do not intend to keep.\n"
|
||||
};
|
||||
|
||||
let mut buffer = term::format::html::commented(format!("HTML comments, such as this one, are deleted before posting.\n{quotes}Saving an empty file aborts the operation.").as_str());
|
||||
buffer.push('\n');
|
||||
|
||||
for comment in chase.iter().rev() {
|
||||
buffer.reserve(2);
|
||||
buffer.push('\n');
|
||||
comment_quoted(comment, &mut buffer);
|
||||
}
|
||||
|
||||
if let Some(id) = missing {
|
||||
buffer.push('\n');
|
||||
buffer.push_str(
|
||||
term::format::html::commented(
|
||||
format!("The comment with ID {id} that was replied to could not be found.")
|
||||
.as_str(),
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(edit) = edit {
|
||||
if !chase.is_empty() {
|
||||
buffer.push_str(
|
||||
"\n<!-- The contents of the comment you are editing follow below this line. -->\n",
|
||||
);
|
||||
}
|
||||
buffer.reserve(2 + edit.len());
|
||||
buffer.push('\n');
|
||||
buffer.push_str(edit);
|
||||
}
|
||||
|
||||
let body = message.get(&buffer)?;
|
||||
|
||||
if body.is_empty() {
|
||||
anyhow::bail!("aborting operation due to empty comment");
|
||||
}
|
||||
Ok((body, reply_to))
|
||||
Ok(body)
|
||||
}
|
||||
|
||||
fn comment_quoted(comment: &thread::Comment, buffer: &mut String) {
|
||||
let body = comment.body();
|
||||
let lines = body.lines();
|
||||
|
||||
let hint = {
|
||||
let (lower, upper) = lines.size_hint();
|
||||
upper.unwrap_or(lower)
|
||||
};
|
||||
|
||||
buffer.push_str(format!("{} wrote:\n", comment.author()).as_str());
|
||||
buffer.reserve(body.len() + hint * 2);
|
||||
|
||||
for line in lines {
|
||||
buffer.push('>');
|
||||
if !line.is_empty() {
|
||||
buffer.push(' ');
|
||||
}
|
||||
buffer.push_str(line);
|
||||
buffer.push('\n');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue