mirror of
https://github.com/martinvonz/jj.git
synced 2024-11-28 09:14:04 +00:00
templates: add raw_escape_sequence
Templates can be formatted (using labels) and are usually sanitized (unless for plain text output). `raw_escape_sequence(content)` bypasses both. ```toml 'hyperlink(url, text)' = ''' raw_escape_sequence("\e]8;;" ++ url ++ "\e\\") ++ text ++ raw_escape_sequence("\e]8;;\e\\") ''' ``` In this example, `raw_escape_sequence` not only outputs the intended escape codes, it also strips away any escape codes that might otherwise be part of the `url` (from any labels attached to the `url` content). Not all formatters (namely FormatRecorder) are supported yet. Change-Id: Id00000004492dbf39e50f3b7090706839d1d8d45
This commit is contained in:
parent
a6aa25c9eb
commit
fb93394610
4 changed files with 74 additions and 0 deletions
|
@ -35,6 +35,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|||
* String literals in filesets, revsets and templates now support hex bytes
|
||||
(with `\e` as escape / shorthand for `\x1b`).
|
||||
|
||||
* New template function `raw_escape_sequence(...)` preserves escape sequences.
|
||||
|
||||
### Fixed bugs
|
||||
|
||||
* Error on `trunk()` revset resolution is now handled gracefully.
|
||||
|
|
|
@ -39,6 +39,7 @@ use crate::templater::ListTemplate;
|
|||
use crate::templater::Literal;
|
||||
use crate::templater::PlainTextFormattedProperty;
|
||||
use crate::templater::PropertyPlaceholder;
|
||||
use crate::templater::RawEscapeSequenceTemplate;
|
||||
use crate::templater::ReformatTemplate;
|
||||
use crate::templater::SeparateTemplate;
|
||||
use crate::templater::SizeHint;
|
||||
|
@ -1116,6 +1117,17 @@ fn builtin_functions<'a, L: TemplateLanguage<'a> + ?Sized>() -> TemplateBuildFun
|
|||
content, labels,
|
||||
))))
|
||||
});
|
||||
map.insert(
|
||||
"raw_escape_sequence",
|
||||
|language, diagnostics, build_ctx, function| {
|
||||
let [content_node] = function.expect_exact_arguments()?;
|
||||
let content =
|
||||
expect_template_expression(language, diagnostics, build_ctx, content_node)?;
|
||||
Ok(L::wrap_template(Box::new(RawEscapeSequenceTemplate(
|
||||
content,
|
||||
))))
|
||||
},
|
||||
);
|
||||
map.insert("if", |language, diagnostics, build_ctx, function| {
|
||||
let ([condition_node, true_node], [false_node]) = function.expect_arguments()?;
|
||||
let condition =
|
||||
|
@ -2334,6 +2346,47 @@ mod tests {
|
|||
@"[38;5;1mtext[39m");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_raw_escape_sequence_function_strip_labels() {
|
||||
let mut env = TestTemplateEnv::new();
|
||||
env.add_color("error", crossterm::style::Color::DarkRed);
|
||||
env.add_color("warning", crossterm::style::Color::DarkYellow);
|
||||
|
||||
insta::assert_snapshot!(
|
||||
env.render_ok(r#"raw_escape_sequence(label("error warning", "text"))"#),
|
||||
@"text",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_raw_escape_sequence_function_ansi_escape() {
|
||||
let env = TestTemplateEnv::new();
|
||||
|
||||
// Sanitize ANSI escape without raw_escape_sequence
|
||||
insta::assert_snapshot!(env.render_ok(r#""\e""#), @"␛");
|
||||
insta::assert_snapshot!(env.render_ok(r#""\x1b""#), @"␛");
|
||||
insta::assert_snapshot!(env.render_ok(r#""\x1B""#), @"␛");
|
||||
insta::assert_snapshot!(
|
||||
env.render_ok(r#""]8;;"
|
||||
++ "http://example.com"
|
||||
++ "\e\\"
|
||||
++ "Example"
|
||||
++ "\x1b]8;;\x1B\\""#),
|
||||
@r#"␛]8;;http://example.com␛\Example␛]8;;␛\"#);
|
||||
|
||||
// Don't sanitize ANSI escape with raw_escape_sequence
|
||||
insta::assert_snapshot!(env.render_ok(r#"raw_escape_sequence("\e")"#), @"");
|
||||
insta::assert_snapshot!(env.render_ok(r#"raw_escape_sequence("\x1b")"#), @"");
|
||||
insta::assert_snapshot!(env.render_ok(r#"raw_escape_sequence("\x1B")"#), @"");
|
||||
insta::assert_snapshot!(
|
||||
env.render_ok(r#"raw_escape_sequence("]8;;"
|
||||
++ "http://example.com"
|
||||
++ "\e\\"
|
||||
++ "Example"
|
||||
++ "\x1b]8;;\x1B\\")"#),
|
||||
@r#"]8;;http://example.com\Example]8;;\"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_coalesce_function() {
|
||||
let mut env = TestTemplateEnv::new();
|
||||
|
|
|
@ -16,6 +16,7 @@ use std::cell::RefCell;
|
|||
use std::error;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::iter;
|
||||
use std::rc::Rc;
|
||||
|
||||
|
@ -184,6 +185,17 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
pub struct RawEscapeSequenceTemplate<T>(pub T);
|
||||
|
||||
impl<T: Template> Template for RawEscapeSequenceTemplate<T> {
|
||||
fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
|
||||
let rewrap = formatter.rewrap_fn();
|
||||
let mut raw_formatter = PlainTextFormatter::new(formatter.raw());
|
||||
// TODO(#4631): process "buffered" labels.
|
||||
self.0.format(&mut rewrap(&mut raw_formatter))
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders contents in order, and returns the first non-empty output.
|
||||
pub struct CoalesceTemplate<T>(pub Vec<T>);
|
||||
|
||||
|
@ -697,6 +709,10 @@ impl<'a> TemplateFormatter<'a> {
|
|||
move |formatter| TemplateFormatter::new(formatter, error_handler)
|
||||
}
|
||||
|
||||
pub fn raw(&mut self) -> &mut dyn Write {
|
||||
self.formatter.raw()
|
||||
}
|
||||
|
||||
pub fn labeled<S: AsRef<str>>(
|
||||
&mut self,
|
||||
label: S,
|
||||
|
|
|
@ -47,6 +47,9 @@ The following functions are defined.
|
|||
non-empty lines by the given `prefix`.
|
||||
* `label(label: Template, content: Template) -> Template`: Apply label to
|
||||
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(...)`.
|
||||
* `if(condition: Boolean, then: Template[, else: Template]) -> Template`:
|
||||
Conditionally evaluate `then`/`else` template content.
|
||||
* `coalesce(content: Template...) -> Template`: Returns the first **non-empty**
|
||||
|
|
Loading…
Reference in a new issue