Skip to content

Commit

Permalink
formatters: implement raw() for FormatRecorder
Browse files Browse the repository at this point in the history
This adds `raw_escape_sequence(...)` support for things that use
FormatRecorder like wrapped text / `fill(...)` / `indent(...)`.

Change-Id: Id00000004248b10feb2acd54d90115b783fac0ff
  • Loading branch information
avamsi committed Oct 13, 2024
1 parent fb93394 commit 92103f4
Show file tree
Hide file tree
Showing 8 changed files with 93 additions and 29 deletions.
3 changes: 2 additions & 1 deletion cli/src/commands/evolog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@ pub(crate) fn cmd_evolog(
commits.truncate(n);
}
if !args.no_graph {
let mut graph = get_graphlog(graph_style, formatter.raw());
let mut raw_output = formatter.raw();
let mut graph = get_graphlog(graph_style, raw_output.as_mut());
for commit in commits {
let edges = commit
.predecessor_ids()
Expand Down
3 changes: 2 additions & 1 deletion cli/src/commands/log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@ pub(crate) fn cmd_log(
let limit = args.limit.or(args.deprecated_limit).unwrap_or(usize::MAX);

if !args.no_graph {
let mut graph = get_graphlog(graph_style, formatter.raw());
let mut raw_output = formatter.raw();
let mut graph = get_graphlog(graph_style, raw_output.as_mut());
let forward_iter = TopoGroupedGraphIterator::new(revset.iter_graph());
let iter: Box<dyn Iterator<Item = _>> = if args.reversed {
Box::new(ReverseGraphIterator::new(forward_iter))
Expand Down
3 changes: 2 additions & 1 deletion cli/src/commands/operation/diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,8 @@ pub fn show_op_diff(
writeln!(formatter, "Changed commits:")
})?;
if let Some(graph_style) = graph_style {
let mut graph = get_graphlog(graph_style, formatter.raw());
let mut raw_output = formatter.raw();
let mut graph = get_graphlog(graph_style, raw_output.as_mut());

let graph_iter =
TopoGroupedGraphIterator::new(ordered_change_ids.iter().map(|change_id| {
Expand Down
3 changes: 2 additions & 1 deletion cli/src/commands/operation/log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,8 @@ fn do_op_log(
let limit = args.limit.or(args.deprecated_limit).unwrap_or(usize::MAX);
let iter = op_walk::walk_ancestors(slice::from_ref(current_op)).take(limit);
if !args.no_graph {
let mut graph = get_graphlog(graph_style, formatter.raw());
let mut raw_output = formatter.raw();
let mut graph = get_graphlog(graph_style, raw_output.as_mut());
for op in iter {
let op = op?;
let mut edges = vec![];
Expand Down
15 changes: 10 additions & 5 deletions cli/src/diff_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,10 +365,15 @@ impl<'a> DiffRenderer<'a> {
tool,
)
}
DiffToolMode::Dir => {
generate_diff(ui, formatter.raw(), from_tree, to_tree, matcher, tool)
.map_err(DiffRenderError::DiffGenerate)
}
DiffToolMode::Dir => generate_diff(
ui,
formatter.raw().as_mut(),
from_tree,
to_tree,
matcher,
tool,
)
.map_err(DiffRenderError::DiffGenerate),
}?;
}
}
Expand Down Expand Up @@ -1098,7 +1103,7 @@ pub fn show_file_by_file_diff(

invoke_external_diff(
ui,
formatter.raw(),
formatter.raw().as_mut(),
tool,
&maplit::hashmap! {
"left" => left_path.to_str().expect("temp_dir should be valid utf-8"),
Expand Down
89 changes: 71 additions & 18 deletions cli/src/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ use itertools::Itertools;
pub trait Formatter: Write {
/// Returns the backing `Write`. This is useful for writing data that is
/// already formatted, such as in the graphical log.
fn raw(&mut self) -> &mut dyn Write;
fn raw(&mut self) -> Box<dyn Write + '_>;

fn push_label(&mut self, label: &str) -> io::Result<()>;

Expand Down Expand Up @@ -203,8 +203,8 @@ impl<W: Write> Write for PlainTextFormatter<W> {
}

impl<W: Write> Formatter for PlainTextFormatter<W> {
fn raw(&mut self) -> &mut dyn Write {
&mut self.output
fn raw(&mut self) -> Box<dyn Write + '_> {
Box::new(self.output.by_ref())
}

fn push_label(&mut self, _label: &str) -> io::Result<()> {
Expand Down Expand Up @@ -238,8 +238,8 @@ impl<W: Write> Write for SanitizingFormatter<W> {
}

impl<W: Write> Formatter for SanitizingFormatter<W> {
fn raw(&mut self) -> &mut dyn Write {
&mut self.output
fn raw(&mut self) -> Box<dyn Write + '_> {
Box::new(self.output.by_ref())
}

fn push_label(&mut self, _label: &str) -> io::Result<()> {
Expand Down Expand Up @@ -541,8 +541,8 @@ impl<W: Write> Write for ColorFormatter<W> {
}

impl<W: Write> Formatter for ColorFormatter<W> {
fn raw(&mut self) -> &mut dyn Write {
&mut self.output
fn raw(&mut self) -> Box<dyn Write + '_> {
Box::new(self.output.by_ref())
}

fn push_label(&mut self, label: &str) -> io::Result<()> {
Expand Down Expand Up @@ -578,13 +578,14 @@ impl<W: Write> Drop for ColorFormatter<W> {
#[derive(Clone, Debug, Default)]
pub struct FormatRecorder {
data: Vec<u8>,
label_ops: Vec<(usize, LabelOp)>,
ops: Vec<(usize, FormatOp)>,
}

#[derive(Clone, Debug, Eq, PartialEq)]
enum LabelOp {
enum FormatOp {
PushLabel(String),
PopLabel,
RawEscapeSequence(Vec<u8>),
}

impl FormatRecorder {
Expand All @@ -596,8 +597,8 @@ impl FormatRecorder {
&self.data
}

fn push_label_op(&mut self, op: LabelOp) {
self.label_ops.push((self.data.len(), op));
fn push_op(&mut self, op: FormatOp) {
self.ops.push((self.data.len(), op));
}

pub fn replay(&self, formatter: &mut dyn Formatter) -> io::Result<()> {
Expand All @@ -619,11 +620,15 @@ impl FormatRecorder {
}
Ok(())
};
for (pos, op) in &self.label_ops {
for (pos, op) in &self.ops {
flush_data(formatter, *pos)?;
match op {
LabelOp::PushLabel(label) => formatter.push_label(label)?,
LabelOp::PopLabel => formatter.pop_label()?,
FormatOp::PushLabel(label) => formatter.push_label(label)?,
FormatOp::PopLabel => formatter.pop_label()?,
FormatOp::RawEscapeSequence(raw_escape_sequence) => {
// TODO(#4631): process "buffered" labels.
formatter.raw().write_all(raw_escape_sequence)?;
}
}
}
flush_data(formatter, self.data.len())
Expand All @@ -641,18 +646,31 @@ impl Write for FormatRecorder {
}
}

struct RawEscapeSequenceRecorder<'a>(&'a mut FormatRecorder);

impl<'a> Write for RawEscapeSequenceRecorder<'a> {
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
self.0.push_op(FormatOp::RawEscapeSequence(data.to_vec()));
Ok(data.len())
}

fn flush(&mut self) -> io::Result<()> {
self.0.flush()
}
}

impl Formatter for FormatRecorder {
fn raw(&mut self) -> &mut dyn Write {
panic!("raw output isn't supported by FormatRecorder")
fn raw(&mut self) -> Box<dyn Write + '_> {
Box::new(RawEscapeSequenceRecorder(self))
}

fn push_label(&mut self, label: &str) -> io::Result<()> {
self.push_label_op(LabelOp::PushLabel(label.to_owned()));
self.push_op(FormatOp::PushLabel(label.to_owned()));
Ok(())
}

fn pop_label(&mut self) -> io::Result<()> {
self.push_label_op(LabelOp::PopLabel);
self.push_op(FormatOp::PopLabel);
Ok(())
}
}
Expand Down Expand Up @@ -1244,4 +1262,39 @@ mod tests {
String::from_utf8(output).unwrap(),
@"<< outer1 >><< inner1 inner2 >><< outer2 >>");
}

#[test]
fn test_raw_format_recorder() {
// Note: similar to test_format_recorder above
let mut recorder = FormatRecorder::new();
write!(recorder.raw(), " outer1 ").unwrap();
recorder.push_label("inner").unwrap();
write!(recorder.raw(), " inner1 ").unwrap();
write!(recorder.raw(), " inner2 ").unwrap();
recorder.pop_label().unwrap();
write!(recorder.raw(), " outer2 ").unwrap();

// No non-raw output to label
let config = config_from_string(r#" colors.inner = "red" "#);
let mut output: Vec<u8> = vec![];
let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
recorder.replay(&mut formatter).unwrap();
drop(formatter);
insta::assert_snapshot!(
String::from_utf8(output).unwrap(), @" outer1 inner1 inner2 outer2 ");

let mut output: Vec<u8> = vec![];
let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
recorder
.replay_with(&mut formatter, |_formatter, range| {
panic!(
"Called with {:?} when all output should be raw",
str::from_utf8(&recorder.data()[range]).unwrap()
);
})
.unwrap();
drop(formatter);
insta::assert_snapshot!(
String::from_utf8(output).unwrap(), @" outer1 inner1 inner2 outer2 ");
}
}
2 changes: 1 addition & 1 deletion cli/src/templater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -709,7 +709,7 @@ impl<'a> TemplateFormatter<'a> {
move |formatter| TemplateFormatter::new(formatter, error_handler)
}

pub fn raw(&mut self) -> &mut dyn Write {
pub fn raw(&mut self) -> Box<dyn Write + '_> {
self.formatter.raw()
}

Expand Down
4 changes: 3 additions & 1 deletion docs/templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ The following functions are defined.
the content. The `label` is evaluated as a space-separated string.
* `raw_escape_sequence(content: Template) -> Template`: Preserves any escape
sequences in `content` (i.e., bypasses sanitization) and strips labels.
Note: Doesn't yet work with wrapped output / `fill(...)` / `indent(...)`.
Note: This function is intended for escape sequences and as such, it's output
is expected to be invisible / of no display width. Outputting content with
nonzero display width may break wrapping, indentation etc.
* `if(condition: Boolean, then: Template[, else: Template]) -> Template`:
Conditionally evaluate `then`/`else` template content.
* `coalesce(content: Template...) -> Template`: Returns the first **non-empty**
Expand Down

0 comments on commit 92103f4

Please sign in to comment.