diff --git a/.gitmodules b/.gitmodules
index b64280c6..20e0bbb4 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -49,9 +49,6 @@
[submodule "assets/themes/zenburn"]
path = assets/themes/zenburn
url = https://github.com/colinta/zenburn.git
-[submodule "assets/syntaxes/Kotlin"]
- path = assets/syntaxes/02_Extra/Kotlin
- url = https://github.com/vkostyukov/kotlin-sublime-package
[submodule "assets/syntaxes/Elm"]
path = assets/syntaxes/02_Extra/Elm
url = https://github.com/elm-community/SublimeElmLanguageSupport
@@ -281,3 +278,6 @@
[submodule "assets/syntaxes/02_Extra/COBOL"]
path = assets/syntaxes/02_Extra/COBOL
url = https://github.com/adukhan99/sublime_cobol.git
+[submodule "assets/syntaxes/02_Extra/Kotlin"]
+ path = assets/syntaxes/02_Extra/Kotlin
+ url = https://github.com/guille/sublime-kotlin
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bd76e2e7..090a019c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,9 @@
## Bugfixes
- Fix `--list-themes` unconditionally probing the terminal via OSC 10/11 even when `--theme` was set to an explicit value, see #3700 (regression introduced in bc42149a). (@optimistiCli)
+- Fix inverted `$LESSCLOSE` warning so bat warns on nonzero exit, not on success. See #3654 (@cuiweixie)
+- Sanitize control characters in filenames before displaying them in the file header, error messages, and the terminal title, preventing ANSI escape injection via crafted filenames. Closes #3054, see #3691 (@curious-rabbit)
+- Report initial input read errors instead of treating them as empty input. Closes #3002, see #3706 (@lawrence3699)
- Treat ZIP archives as binary content based on their magic header, see #3686 (@officialasishkumar)
- Fix i686 `.deb` package using incorrect architecture name (`i686` instead of `i386`), preventing installation on Debian. Closes #3611, see #3650 (@Sim-hu)
- Fix inconsistent `.deb` MUSL package names (aarch64-musl used `arm64` instead of `musl-linux-arm64`, and `musleabihf` target missed `bat-musl` prefix). Closes #3482, see #3642 (@mvanhorn)
@@ -37,6 +40,7 @@
- Fixed test compatibility with future Cargo build directory changes, see #3550 (@nmacl)
- Fixed bug caused by using `--plain` and `--terminal-width=N` flags simultaneously, see #3529 (@H4k1l)
- Fixed syntax tests path, see #3610 (@foxfromworld)
+- Fix zsh tab completion word-splitting language names containing spaces (e.g. `HTML (Jinja2)`, `Apache Conf`), see #3693 (@YoshKoz)
## Other
- Use git version of cross. See #3533 (@OctopusET)
@@ -54,6 +58,7 @@
- Fixed manpage syntax so that ANSI escape codes don't get incorrectly highlighted and thus broken, see #3586 (@BlueElectivire)
- Map several Google Cloud CLI config files to their appropriate syntax #3635 (@victor-gp)
- Map all ignore dotfiles to Git Ignore syntax #3636 (@victor-gp)
+- Improved Kotlin syntax, see #3699 (@guille)
## Themes
diff --git a/README.md b/README.md
index d6ef3381..375ec6d7 100644
--- a/README.md
+++ b/README.md
@@ -19,20 +19,6 @@
[Русский]
-### Sponsors
-
-A special *thank you* goes to our biggest sponsors:
-
-
-
-
-
- Warp, the intelligent terminal
-
- Available on MacOS, Linux, Windows
-
-
-
### Syntax highlighting
`bat` supports syntax highlighting for a large number of programming and markup
@@ -208,12 +194,6 @@ Note that the [Manpage syntax](assets/syntaxes/02_Extra/Manpage.sublime-syntax)
The [`prettybat`](https://github.com/eth-p/bat-extras/blob/master/doc/prettybat.md) script is a wrapper that will format code and print it with `bat`.
-#### `Warp`
-
-
-
-
-
#### Highlighting `--help` messages
You can use `bat` to colorize help text: `$ cp --help | bat -plhelp`
diff --git a/assets/completions/bat.zsh.in b/assets/completions/bat.zsh.in
index 3c3f81d1..f8de56e4 100644
--- a/assets/completions/bat.zsh.in
+++ b/assets/completions/bat.zsh.in
@@ -90,7 +90,7 @@ _{{PROJECT_EXECUTABLE}}_main() {
languages)
local IFS=$'\n'
local -a languages
- languages=( $({{PROJECT_EXECUTABLE}} --list-languages | awk -F':|,' '{ for (i = 1; i <= NF; ++i) printf("%s:%s\n", $i, $1) }') )
+ languages=( ${(f)"$({{PROJECT_EXECUTABLE}} --list-languages | awk -F':|,' '{ for (i = 1; i <= NF; ++i) printf("%s:%s\n", $i, $1) }')"} )
_describe 'language' languages && ret=0
;;
diff --git a/assets/syntaxes/02_Extra/Idris2 b/assets/syntaxes/02_Extra/Idris2
index bbfe50e0..4d8eb35a 160000
--- a/assets/syntaxes/02_Extra/Idris2
+++ b/assets/syntaxes/02_Extra/Idris2
@@ -1 +1 @@
-Subproject commit bbfe50e023e0edc74f5e0c003eb946528d49279f
+Subproject commit 4d8eb35a38254d422030e77b4933530008dd3c6e
diff --git a/assets/syntaxes/02_Extra/Kotlin b/assets/syntaxes/02_Extra/Kotlin
index aeeed278..c3536941 160000
--- a/assets/syntaxes/02_Extra/Kotlin
+++ b/assets/syntaxes/02_Extra/Kotlin
@@ -1 +1 @@
-Subproject commit aeeed2780b04aea3d293c547c24cae27cafef0c5
+Subproject commit c353694169c047bd746c93c214289fc52d152cae
diff --git a/assets/syntaxes/02_Extra/Kotlin.sublime-syntax b/assets/syntaxes/02_Extra/Kotlin.sublime-syntax
deleted file mode 100644
index eab59192..00000000
--- a/assets/syntaxes/02_Extra/Kotlin.sublime-syntax
+++ /dev/null
@@ -1,398 +0,0 @@
-%YAML 1.2
----
-# http://www.sublimetext.com/docs/3/syntax.html
-name: Kotlin
-file_extensions:
- - kt
- - kts
-scope: source.Kotlin
-contexts:
- main:
- - include: comments
- - match: '^\s*(package)\b(?:\s*([^ ;$]+)\s*)?'
- captures:
- 1: keyword.other.kotlin
- 2: entity.name.package.kotlin
- - include: imports
- - include: statements
- classes:
- - match: (?"
- pop: true
- - include: generics
- - match: \(
- push:
- - match: \)
- pop: true
- - include: parameters
- - match: (:)
- captures:
- 1: keyword.operator.declaration.kotlin
- push:
- - match: "(?={|$)"
- pop: true
- - match: \w+
- scope: entity.other.inherited-class.kotlin
- - match: \(
- push:
- - match: \)
- pop: true
- - include: expressions
- - match: '\{'
- push:
- - match: '\}'
- pop: true
- - include: statements
- comments:
- - match: /\*
- captures:
- 0: punctuation.definition.comment.kotlin
- push:
- - meta_scope: comment.block.kotlin
- - match: \*/
- captures:
- 0: punctuation.definition.comment.kotlin
- pop: true
- - match: \s*((//).*$\n?)
- captures:
- 1: comment.line.double-slash.kotlin
- 2: punctuation.definition.comment.kotlin
- constants:
- - match: \b(true|false|null|this|super)\b
- scope: constant.language.kotlin
- - match: '\b((0(x|X)[0-9a-fA-F]*)|(([0-9]+\.?[0-9]*)|(\.[0-9]+))((e|E)(\+|-)?[0-9]+)?)([LlFf])?\b'
- scope: constant.numeric.kotlin
- - match: '\b([A-Z][A-Z0-9_]+)\b'
- scope: constant.other.kotlin
- expressions:
- - match: \(
- push:
- - match: \)
- pop: true
- - include: expressions
- - include: types
- - include: strings
- - include: constants
- - include: comments
- - include: keywords
- functions:
- - match: (?=\s*\b(?:fun)\b)
- push:
- - match: '(?=$|\})'
- pop: true
- - match: \b(fun)\b
- captures:
- 1: keyword.other.kotlin
- push:
- - match: (?=\()
- pop: true
- - match: <
- push:
- - match: ">"
- pop: true
- - include: generics
- - match: '([\.<\?>\w]+\.)?(\w+)'
- captures:
- 2: entity.name.function.kotlin
- - match: \(
- push:
- - match: \)
- pop: true
- - include: parameters
- - match: (:)
- captures:
- 1: keyword.operator.declaration.kotlin
- push:
- - match: "(?={|=|$)"
- pop: true
- - include: types
- - match: '\{'
- push:
- - match: '(?=\})'
- pop: true
- - include: statements
- - match: (=)
- captures:
- 1: keyword.operator.assignment.kotlin
- push:
- - match: (?=$)
- pop: true
- - include: expressions
- generics:
- - match: (:)
- captures:
- 1: keyword.operator.declaration.kotlin
- push:
- - match: (?=,|>)
- pop: true
- - include: types
- - include: keywords
- - match: \w+
- scope: storage.type.generic.kotlin
- getters-and-setters:
- - match: \b(get)\b\s*\(\s*\)
- captures:
- 1: entity.name.function.kotlin
- push:
- - match: '\}|(?=\bset\b)|$'
- pop: true
- - match: (=)
- captures:
- 1: keyword.operator.assignment.kotlin
- push:
- - match: (?=$|\bset\b)
- pop: true
- - include: expressions
- - match: '\{'
- push:
- - match: '\}'
- pop: true
- - include: expressions
- - match: \b(set)\b\s*(?=\()
- captures:
- 1: entity.name.function.kotlin
- push:
- - match: '\}|(?=\bget\b)|$'
- pop: true
- - match: \(
- push:
- - match: \)
- pop: true
- - include: parameters
- - match: (=)
- captures:
- 1: keyword.operator.assignment.kotlin
- push:
- - match: (?=$|\bset\b)
- pop: true
- - include: expressions
- - match: '\{'
- push:
- - match: '\}'
- pop: true
- - include: expressions
- imports:
- - match: '^\s*(import)\s+[^ $]+\s+(as)?'
- captures:
- 1: keyword.other.kotlin
- 2: keyword.other.kotlin
- keywords:
- - match: \b(var|val|public|private|protected|abstract|final|sealed|enum|open|attribute|annotation|override|inline|vararg|in|out|internal|data|tailrec|operator|infix|const|yield|typealias|typeof|reified|suspend)\b
- scope: storage.modifier.kotlin
- - match: \b(try|catch|finally|throw)\b
- scope: keyword.control.catch-exception.kotlin
- - match: \b(if|else|while|for|do|return|when|where|break|continue)\b
- scope: keyword.control.kotlin
- - match: \b(in|is|!in|!is|as|as\?|assert)\b
- scope: keyword.operator.kotlin
- - match: (==|!=|===|!==|<=|>=|<|>)
- scope: keyword.operator.comparison.kotlin
- - match: (=)
- scope: keyword.operator.assignment.kotlin
- - match: (::)
- scope: keyword.operator.kotlin
- - match: (:)
- scope: keyword.operator.declaration.kotlin
- - match: \b(by)\b
- scope: keyword.other.by.kotlin
- - match: (\?\.)
- scope: keyword.operator.safenav.kotlin
- - match: (\.)
- scope: keyword.operator.dot.kotlin
- - match: (\?:)
- scope: keyword.operator.elvis.kotlin
- - match: (\-\-|\+\+)
- scope: keyword.operator.increment-decrement.kotlin
- - match: (\+=|\-=|\*=|\/=)
- scope: keyword.operator.arithmetic.assign.kotlin
- - match: (\.\.)
- scope: keyword.operator.range.kotlin
- - match: (\-|\+|\*|\/|%)
- scope: keyword.operator.arithmetic.kotlin
- - match: (!|&&|\|\|)
- scope: keyword.operator.logical.kotlin
- - match: (;)
- scope: punctuation.terminator.kotlin
- namespaces:
- - match: \b(namespace)\b
- scope: keyword.other.kotlin
- - match: '\{'
- push:
- - match: '\}'
- pop: true
- - include: statements
- parameters:
- - match: (:)
- captures:
- 1: keyword.operator.declaration.kotlin
- push:
- - match: (?=,|\)|=)
- pop: true
- - include: types
- - match: (=)
- captures:
- 1: keyword.operator.declaration.kotlin
- push:
- - match: (?=,|\))
- pop: true
- - include: expressions
- - include: keywords
- - match: \w+
- scope: variable.parameter.function.kotlin
- statements:
- - include: namespaces
- - include: typedefs
- - include: classes
- - include: functions
- - include: variables
- - include: getters-and-setters
- - include: expressions
- strings:
- - match: '"""'
- captures:
- 0: punctuation.definition.string.begin.kotlin
- push:
- - meta_scope: string.quoted.third.kotlin
- - match: '"""'
- captures:
- 0: punctuation.definition.string.end.kotlin
- pop: true
- - match: '(\$\w+|\$\{[^\}]+\})'
- scope: variable.parameter.template.kotlin
- - match: \\.
- scope: constant.character.escape.kotlin
- - match: '"'
- captures:
- 0: punctuation.definition.string.begin.kotlin
- push:
- - meta_scope: string.quoted.double.kotlin
- - match: '"'
- captures:
- 0: punctuation.definition.string.end.kotlin
- pop: true
- - match: '(\$\w+|\$\{[^\}]+\})'
- scope: variable.parameter.template.kotlin
- - match: \\.
- scope: constant.character.escape.kotlin
- - match: "'"
- captures:
- 0: punctuation.definition.string.begin.kotlin
- push:
- - meta_scope: string.quoted.single.kotlin
- - match: "'"
- captures:
- 0: punctuation.definition.string.end.kotlin
- pop: true
- - match: \\.
- scope: constant.character.escape.kotlin
- - match: "`"
- captures:
- 0: punctuation.definition.string.begin.kotlin
- push:
- - meta_scope: string.quoted.single.kotlin
- - match: "`"
- captures:
- 0: punctuation.definition.string.end.kotlin
- pop: true
- typedefs:
- - match: (?=\s*(?:type))
- push:
- - match: (?=$)
- pop: true
- - match: \b(type)\b
- scope: keyword.other.kotlin
- - match: <
- push:
- - match: ">"
- pop: true
- - include: generics
- - include: expressions
- types:
- - match: \b(Nothing|Any|Unit|String|CharSequence|Int|Boolean|Char|Long|Double|Float|Short|Byte|dynamic)\b
- scope: storage.type.buildin.kotlin
- - match: \b(IntArray|BooleanArray|CharArray|LongArray|DoubleArray|FloatArray|ShortArray|ByteArray)\b
- scope: storage.type.buildin.array.kotlin
- - match: \b(Array|Collection|List|Map|Set|MutableList|MutableMap|MutableSet|Sequence)<\b
- captures:
- 1: storage.type.buildin.collection.kotlin
- push:
- - match: ">"
- pop: true
- - include: types
- - include: keywords
- - match: \w+<
- push:
- - match: ">"
- pop: true
- - include: types
- - include: keywords
- - match: '\{'
- push:
- - match: '\}'
- pop: true
- - include: statements
- - match: \(
- push:
- - match: \)
- pop: true
- - include: types
- - match: (->)
- scope: keyword.operator.declaration.kotlin
- variables:
- - match: (?=\s*\b(?:var|val)\b)
- push:
- - match: (?=:|=|(\b(by)\b)|$)
- pop: true
- - match: \b(var|val)\b
- captures:
- 1: keyword.other.kotlin
- push:
- - match: (?=:|=|(\b(by)\b)|$)
- pop: true
- - match: <
- push:
- - match: ">"
- pop: true
- - include: generics
- - match: '([\.<\?>\w]+\.)?(\w+)'
- captures:
- 2: entity.name.variable.kotlin
- - match: (:)
- captures:
- 1: keyword.operator.declaration.kotlin
- push:
- - match: (?==|$)
- pop: true
- - include: types
- - include: getters-and-setters
- - match: \b(by)\b
- captures:
- 1: keyword.other.kotlin
- push:
- - match: (?=$)
- pop: true
- - include: expressions
- - match: (=)
- captures:
- 1: keyword.operator.assignment.kotlin
- push:
- - match: (?=$)
- pop: true
- - include: expressions
- - include: getters-and-setters
diff --git a/doc/sponsors.md b/doc/sponsors.md
index 24509077..641e5205 100644
--- a/doc/sponsors.md
+++ b/doc/sponsors.md
@@ -10,5 +10,3 @@ No issue will have a different priority based on sponsorship status of the
reporter.
Contributions from anybody are most welcomed, please see our [`CONTRIBUTING.md`](../CONTRIBUTING.md) guide.
-
-If you want to see our biggest sponsors, check the top of [`README.md`](../README.md#sponsors).
diff --git a/doc/sponsors/graphite-logo.jpeg b/doc/sponsors/graphite-logo.jpeg
deleted file mode 100644
index 00443818..00000000
Binary files a/doc/sponsors/graphite-logo.jpeg and /dev/null differ
diff --git a/doc/sponsors/warp-logo.png b/doc/sponsors/warp-logo.png
deleted file mode 100644
index f99dd38c..00000000
Binary files a/doc/sponsors/warp-logo.png and /dev/null differ
diff --git a/doc/sponsors/warp-pack-header.png b/doc/sponsors/warp-pack-header.png
deleted file mode 100644
index ed7efe6a..00000000
Binary files a/doc/sponsors/warp-pack-header.png and /dev/null differ
diff --git a/src/assets.rs b/src/assets.rs
index 29247bd7..9483f032 100644
--- a/src/assets.rs
+++ b/src/assets.rs
@@ -158,7 +158,9 @@ impl HighlightingAssets {
let syntax_match = mapping.get_syntax_for(path);
if let Some(MappingTarget::MapToUnknown) = syntax_match {
- return Err(Error::UndetectedSyntax(path.to_string_lossy().into()));
+ return Err(Error::UndetectedSyntax(
+ crate::preprocessor::sanitize_for_terminal(&path.to_string_lossy()),
+ ));
}
if let Some(MappingTarget::MapTo(syntax_name)) = syntax_match {
@@ -175,13 +177,17 @@ impl HighlightingAssets {
) {
(Some(syntax), _) => Ok(syntax),
- (_, Some(MappingTarget::MapExtensionToUnknown)) => {
- Err(Error::UndetectedSyntax(path.to_string_lossy().into()))
- }
+ (_, Some(MappingTarget::MapExtensionToUnknown)) => Err(Error::UndetectedSyntax(
+ crate::preprocessor::sanitize_for_terminal(&path.to_string_lossy()),
+ )),
_ => self
.get_syntax_for_file_extension(file_name, &mapping.ignored_suffixes)?
- .ok_or_else(|| Error::UndetectedSyntax(path.to_string_lossy().into())),
+ .ok_or_else(|| {
+ Error::UndetectedSyntax(crate::preprocessor::sanitize_for_terminal(
+ &path.to_string_lossy(),
+ ))
+ }),
}
}
diff --git a/src/bin/bat/main.rs b/src/bin/bat/main.rs
index 5184cf99..1d89f8ec 100644
--- a/src/bin/bat/main.rs
+++ b/src/bin/bat/main.rs
@@ -258,7 +258,9 @@ pub fn list_themes(
fn set_terminal_title_to(new_terminal_title: String) {
let osc_command_for_setting_terminal_title = "\x1b]0;";
let osc_end_command = "\x07";
- print!("{osc_command_for_setting_terminal_title}{new_terminal_title}{osc_end_command}");
+ // Prevent BEL/ESC/C1 bytes in the title from terminating or nesting the OSC.
+ let safe_title = bat::sanitize_for_terminal(&new_terminal_title);
+ print!("{osc_command_for_setting_terminal_title}{safe_title}{osc_end_command}");
io::stdout().flush().unwrap();
}
diff --git a/src/input.rs b/src/input.rs
index 30f13f98..facd64bf 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -207,7 +207,7 @@ impl<'a> Input<'a> {
kind: OpenedInputKind::StdIn,
description,
metadata: self.metadata,
- reader: InputReader::new(stdin),
+ reader: InputReader::try_new(stdin)?,
})
}
@@ -216,34 +216,34 @@ impl<'a> Input<'a> {
description,
metadata: self.metadata,
reader: {
- let mut file = File::open(&path)
- .map_err(|e| format!("'{}': {e}", path.to_string_lossy()))?;
+ let path_display =
+ crate::preprocessor::sanitize_for_terminal(&path.to_string_lossy());
+ let mut file =
+ File::open(&path).map_err(|e| format!("'{path_display}': {e}"))?;
if file.metadata()?.is_dir() {
- return Err(format!("'{}' is a directory.", path.to_string_lossy()).into());
+ return Err(format!("'{path_display}' is a directory.").into());
}
if let Some(stdout) = stdout_identifier {
- let input_identifier = Identifier::try_from(file).map_err(|e| {
- format!("{}: Error identifying file: {e}", path.to_string_lossy())
- })?;
+ let input_identifier = Identifier::try_from(file)
+ .map_err(|e| format!("{path_display}: Error identifying file: {e}"))?;
if stdout.surely_conflicts_with(&input_identifier) {
return Err(format!(
- "IO circle detected. The input from '{}' is also an output. Aborting to avoid infinite loop.",
- path.to_string_lossy()
+ "IO circle detected. The input from '{path_display}' is also an output. Aborting to avoid infinite loop.",
)
.into());
}
file = input_identifier.into_inner().expect("The file was lost in the clircle::Identifier, this should not have happened...");
}
- InputReader::new(BufReader::new(file))
+ InputReader::try_new(BufReader::new(file))?
},
}),
InputKind::CustomReader(reader) => Ok(OpenedInput {
description,
kind: OpenedInputKind::CustomReader,
metadata: self.metadata,
- reader: InputReader::new(BufReader::new(reader)),
+ reader: InputReader::try_new(BufReader::new(reader))?,
}),
}
}
@@ -257,24 +257,29 @@ pub(crate) struct InputReader<'a> {
}
impl<'a> InputReader<'a> {
- pub(crate) fn new(mut reader: R) -> InputReader<'a> {
+ #[cfg(test)]
+ pub(crate) fn new(reader: R) -> InputReader<'a> {
+ Self::try_new(reader).expect("reading the first line failed")
+ }
+
+ pub(crate) fn try_new(mut reader: R) -> io::Result> {
let mut first_line = vec![];
- reader.read_until(b'\n', &mut first_line).ok();
+ reader.read_until(b'\n', &mut first_line)?;
let content_type = inspect_content_type(&first_line);
if content_type == Some(ContentType::UTF_16LE) {
- read_utf16_line(&mut reader, &mut first_line, 0x00, 0x0A).ok();
+ read_utf16_line(&mut reader, &mut first_line, 0x00, 0x0A)?;
} else if content_type == Some(ContentType::UTF_16BE) {
- read_utf16_line(&mut reader, &mut first_line, 0x0A, 0x00).ok();
+ read_utf16_line(&mut reader, &mut first_line, 0x0A, 0x00)?;
}
- InputReader {
+ Ok(InputReader {
inner: Box::new(reader),
first_line,
content_type,
unbuffered: false,
- }
+ })
}
pub(crate) fn read_line(&mut self, buf: &mut Vec) -> io::Result {
@@ -405,6 +410,27 @@ fn non_zip_pk_prefix_is_not_treated_as_binary() {
);
}
+#[test]
+fn input_open_returns_initial_read_errors() {
+ struct FailingRead;
+
+ impl Read for FailingRead {
+ fn read(&mut self, _buf: &mut [u8]) -> io::Result {
+ Err(io::Error::other("initial read failed"))
+ }
+ }
+
+ let input = Input::from_reader(Box::new(FailingRead));
+ let result = input.open(io::empty(), None);
+
+ assert!(result.is_err());
+ assert!(result
+ .err()
+ .unwrap()
+ .to_string()
+ .contains("initial read failed"));
+}
+
#[test]
fn utf16le() {
let content = b"\xFF\xFE\x73\x00\x0A\x00\x64\x00";
diff --git a/src/lessopen.rs b/src/lessopen.rs
index 966bc2c0..c7593b53 100644
--- a/src/lessopen.rs
+++ b/src/lessopen.rs
@@ -155,7 +155,7 @@ impl LessOpenPreprocessor {
Ok(OpenedInput {
kind,
- reader: InputReader::new(BufReader::new(
+ reader: InputReader::try_new(BufReader::new(
if matches!(self.kind, LessOpenKind::TempFile) {
let lessopen_string = match String::from_utf8(lessopen_stdout) {
Ok(string) => string,
@@ -192,7 +192,7 @@ impl LessOpenPreprocessor {
.map(|s| s.replacen("%s", &path_str, 1).replacen("%s", "-", 1)),
}
},
- )),
+ ))?,
metadata: input.metadata,
description: input.description,
})
@@ -261,7 +261,7 @@ impl Drop for Preprocessed {
}
};
- if lessclose_output.status.success() {
+ if !lessclose_output.status.success() {
bat_warning!("$LESSCLOSE exited with nonzero exit code",)
};
}
diff --git a/src/lib.rs b/src/lib.rs
index 4c60f10e..685214dc 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -54,6 +54,7 @@ mod vscreen;
pub(crate) mod wrapping;
pub use nonprintable_notation::{BinaryBehavior, NonprintableNotation};
+pub use preprocessor::sanitize_for_terminal;
pub use preprocessor::StripAnsiMode;
pub use pretty_printer::{Input, PrettyPrinter, Syntax};
pub use syntax_mapping::{MappingTarget, SyntaxMapping};
diff --git a/src/preprocessor.rs b/src/preprocessor.rs
index 6b4e2935..a34f9f9e 100644
--- a/src/preprocessor.rs
+++ b/src/preprocessor.rs
@@ -149,6 +149,35 @@ pub fn strip_ansi(line: &str) -> String {
buffer
}
+/// Escape C0, DEL, and C1 control characters so a string from an untrusted
+/// filename or path can be safely written to the terminal.
+pub fn sanitize_for_terminal(input: &str) -> String {
+ if !input
+ .chars()
+ .any(|c| matches!(c, '\x00'..='\x08' | '\x0A'..='\x1F' | '\x7F'..='\u{9F}'))
+ {
+ return input.to_owned();
+ }
+
+ let mut out = String::with_capacity(input.len() + 8);
+ for c in input.chars() {
+ match c {
+ '\t' => out.push('\t'),
+ '\x00'..='\x1F' => {
+ out.push('^');
+ out.push(char::from_u32(0x40 + c as u32).unwrap_or('?'));
+ }
+ '\x7F' => out.push_str("^?"),
+ '\u{80}'..='\u{9F}' => {
+ use std::fmt::Write as _;
+ let _ = write!(out, "\\u{{{:x}}}", c as u32);
+ }
+ other => out.push(other),
+ }
+ }
+ out
+}
+
/// Strips overstrike sequences (backspace formatting) from input.
///
/// Overstrike formatting is used by man pages and some help output:
@@ -261,3 +290,40 @@ fn test_strip_overstrike() {
// Unicode with overstrike
assert_eq!(strip_overstrike("ä\x08äöü", 2), "äöü");
}
+
+#[test]
+fn test_sanitize_for_terminal_passthrough() {
+ assert_eq!(sanitize_for_terminal(""), "");
+ assert_eq!(sanitize_for_terminal("hello.txt"), "hello.txt");
+ assert_eq!(sanitize_for_terminal("résumé.pdf"), "résumé.pdf");
+ assert_eq!(sanitize_for_terminal("日本語.md"), "日本語.md");
+ assert_eq!(
+ sanitize_for_terminal("path/with spaces/file.log"),
+ "path/with spaces/file.log"
+ );
+ assert_eq!(sanitize_for_terminal("a\tb"), "a\tb");
+}
+
+#[test]
+fn test_sanitize_for_terminal_c0_controls() {
+ assert_eq!(
+ sanitize_for_terminal("\x1b[31mINJECTED\x1b[0m.txt"),
+ "^[[31mINJECTED^[[0m.txt"
+ );
+ assert_eq!(sanitize_for_terminal("bad\x07rest"), "bad^Grest");
+ assert_eq!(sanitize_for_terminal("\x00\x08\n\r\x7F"), "^@^H^J^M^?");
+ assert_eq!(sanitize_for_terminal("\u{9b}31m"), "\\u{9b}31m");
+ assert_eq!(
+ sanitize_for_terminal("\u{9d}0;pwned\x07"),
+ "\\u{9d}0;pwned^G"
+ );
+}
+
+#[test]
+fn test_sanitize_for_terminal_idempotent_on_sanitized() {
+ let dirty = "\x1b]0;pwned\x07file.txt";
+ let clean = sanitize_for_terminal(dirty);
+ assert_eq!(sanitize_for_terminal(&clean), clean);
+ assert!(!clean.contains('\x1b'));
+ assert!(!clean.contains('\x07'));
+}
diff --git a/src/printer.rs b/src/printer.rs
index c58c914d..17f7aa30 100644
--- a/src/printer.rs
+++ b/src/printer.rs
@@ -29,7 +29,9 @@ use crate::error::*;
use crate::input::OpenedInput;
use crate::line_range::{MaxBufferedLineNumber, RangeCheckResult};
use crate::output::OutputHandle;
-use crate::preprocessor::{expand_tabs, replace_nonprintable, strip_ansi, strip_overstrike};
+use crate::preprocessor::{
+ expand_tabs, replace_nonprintable, sanitize_for_terminal, strip_ansi, strip_overstrike,
+};
use crate::style::StyleComponent;
use crate::terminal::{as_terminal_escaped, to_ansi_color};
use crate::vscreen::{AnsiStyle, EscapeSequence, EscapeSequenceIterator};
@@ -489,7 +491,7 @@ impl Printer for InteractivePrinter<'_> {
(but will be present if the output of 'bat' is piped). You can use 'bat -A' \
to show the binary file contents.",
Yellow.paint("[bat warning]"),
- input.description.summary(),
+ sanitize_for_terminal(&input.description.summary()),
)?;
} else if self.config.style_components.grid() {
self.print_horizontal_line(handle, '┬')?;
@@ -543,9 +545,11 @@ impl Printer for InteractivePrinter<'_> {
"{}{}{mode}",
description
.kind()
- .map(|kind| format!("{kind}: "))
+ .map(|kind| format!("{}: ", sanitize_for_terminal(kind)))
.unwrap_or_else(|| "".into()),
- self.colors.header_value.paint(description.title()),
+ self.colors
+ .header_value
+ .paint(sanitize_for_terminal(description.title())),
);
self.print_header_multiline_component(handle, &header_filename)
}
diff --git a/tests/syntax-tests/highlighted/Kotlin/test.kt b/tests/syntax-tests/highlighted/Kotlin/test.kt
index dcb1560e..bb17aba2 100644
--- a/tests/syntax-tests/highlighted/Kotlin/test.kt
+++ b/tests/syntax-tests/highlighted/Kotlin/test.kt
@@ -1,85 +1,85 @@
-[38;2;249;38;114mimport[0m[38;2;248;248;242m kotlin.math.*[0m
+[38;2;249;38;114mimport[0m[38;2;248;248;242m [0m[38;2;248;248;242mkotlin[0m[38;2;248;248;242m.[0m[38;2;248;248;242mmath[0m[38;2;248;248;242m.[0m[38;2;249;38;114m*[0m
-[38;2;249;38;114mdata[0m[38;2;248;248;242m [0m[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[38;2;166;226;46mExample[0m[38;2;248;248;242m([0m
-[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[3;38;2;253;151;31mname[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mString[0m[38;2;248;248;242m,[0m
-[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[3;38;2;253;151;31mnumbers[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mList[0m[38;2;248;248;242m<[0m[3;38;2;102;217;239mInt[0m[38;2;248;248;242m?>[0m
+[38;2;249;38;114mdata[0m[38;2;248;248;242m [0m[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[4;38;2;102;217;239mExample[0m[38;2;248;248;242m([0m
+[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[3;38;2;253;151;31mname[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mString[0m[38;2;248;248;242m,[0m
+[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[3;38;2;253;151;31mnumbers[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mList[0m[38;2;248;248;242m<[0m[3;38;2;166;226;46mInt[0m[38;2;249;38;114m?[0m[38;2;248;248;242m>[0m
[38;2;248;248;242m)[0m
-[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46minterface[0m[38;2;248;248;242m [0m[38;2;166;226;46mJokeInterface[0m[38;2;248;248;242m {[0m
-[38;2;248;248;242m [0m[38;2;166;226;46mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46misFunny[0m[38;2;248;248;242m()[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mBoolean[0m
+[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46minterface[0m[38;2;248;248;242m [0m[38;2;248;248;242mJokeInterface[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
+[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46misFunny[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mBoolean[0m
[38;2;248;248;242m}[0m
-[38;2;249;38;114mabstract[0m[38;2;248;248;242m [0m[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[38;2;166;226;46mAbstractJoke[0m[38;2;248;248;242m [0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;4;38;2;166;226;46mJokeInterface[0m[38;2;248;248;242m {[0m
-[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46misFunny[0m[38;2;248;248;242m() [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;190;132;255mfalse[0m
-[38;2;248;248;242m [0m[38;2;249;38;114mabstract[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46mcontent[0m[38;2;248;248;242m()[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mString[0m
+[38;2;249;38;114mabstract[0m[38;2;248;248;242m [0m[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[4;38;2;102;217;239mAbstractJoke[0m[38;2;248;248;242m [0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mJokeInterface[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
+[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46misFunny[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;190;132;255mfalse[0m
+[38;2;248;248;242m [0m[38;2;249;38;114mabstract[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46mcontent[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mString[0m
[38;2;248;248;242m}[0m
-[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[38;2;166;226;46mJoke[0m[38;2;248;248;242m [0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;4;38;2;166;226;46mAbstractJoke[0m[38;2;248;248;242m() {[0m
-[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46misFunny[0m[38;2;248;248;242m()[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mBoolean[0m[38;2;248;248;242m {[0m
+[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[4;38;2;102;217;239mJoke[0m[38;2;248;248;242m [0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mAbstractJoke[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
+[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46misFunny[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mBoolean[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
[38;2;248;248;242m [0m[38;2;249;38;114mreturn[0m[38;2;248;248;242m [0m[38;2;190;132;255mtrue[0m
-[38;2;248;248;242m }[0m
-[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46mcontent[0m[38;2;248;248;242m()[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mString[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mcontent of joke here, haha[0m[38;2;230;219;116m"[0m
+[38;2;248;248;242m [0m[38;2;248;248;242m}[0m
+[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46mcontent[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mString[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mcontent of joke here, haha[0m[38;2;230;219;116m"[0m
[38;2;248;248;242m}[0m
-[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[38;2;166;226;46mDelegatedJoke[0m[38;2;248;248;242m([0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[3;38;2;253;151;31mjoke[0m[38;2;249;38;114m:[0m[38;2;248;248;242m Joke) [0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;4;38;2;166;226;46mJokeInterface[0m[38;2;248;248;242m [0m[3;4;38;2;166;226;46mby[0m[38;2;248;248;242m [0m[3;4;38;2;166;226;46mjoke[0m[38;2;248;248;242m {[0m
-[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[38;2;166;226;46mnumber[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mLong[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;190;132;255m123L[0m
+[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[4;38;2;102;217;239mDelegatedJoke[0m[38;2;248;248;242m([0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[3;38;2;253;151;31mjoke[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mJoke[0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mJokeInterface[0m[38;2;248;248;242m [0m[38;2;249;38;114mby[0m[38;2;248;248;242m [0m[38;2;255;255;255mjoke[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
+[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[38;2;255;255;255mnumber[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mLong[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;190;132;255m123L[0m
-[38;2;248;248;242m [0m[38;2;249;38;114mcompanion [0m[38;2;249;38;114mobject[0m[38;2;248;248;242m {[0m
-[38;2;248;248;242m [0m[38;2;249;38;114mconst[0m[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[38;2;166;226;46msomeConstant[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116msome constant text[0m[38;2;230;219;116m"[0m
-[38;2;248;248;242m }[0m
+[38;2;248;248;242m [0m[38;2;249;38;114mcompanion[0m[38;2;248;248;242m [0m[38;2;249;38;114mobject[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
+[38;2;248;248;242m [0m[38;2;249;38;114mconst[0m[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[38;2;255;255;255msomeConstant[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116msome constant text[0m[38;2;230;219;116m"[0m
+[38;2;248;248;242m [0m[38;2;248;248;242m}[0m
[38;2;248;248;242m}[0m
-[38;2;249;38;114mobject[0m[38;2;248;248;242m [0m[38;2;166;226;46mSomeSingleton[0m
+[38;2;249;38;114mobject[0m[38;2;248;248;242m [0m[4;38;2;102;217;239mSomeSingleton[0m
-[38;2;249;38;114msealed[0m[38;2;248;248;242m [0m[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[38;2;166;226;46mShape[0m[38;2;248;248;242m {[0m
-[38;2;248;248;242m [0m[38;2;249;38;114mabstract[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46marea[0m[38;2;248;248;242m()[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mDouble[0m
+[38;2;249;38;114msealed[0m[38;2;248;248;242m [0m[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[4;38;2;102;217;239mShape[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
+[38;2;248;248;242m [0m[38;2;249;38;114mabstract[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46marea[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mDouble[0m
[38;2;248;248;242m}[0m
-[38;2;249;38;114mdata[0m[38;2;248;248;242m [0m[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[38;2;166;226;46mSquare[0m[38;2;248;248;242m([0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[3;38;2;253;151;31msideLength[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mDouble[0m[38;2;248;248;242m) [0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;4;38;2;166;226;46mShape[0m[38;2;248;248;242m() {[0m
-[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46marea[0m[38;2;248;248;242m()[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mDouble[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m sideLength[0m[38;2;249;38;114m.[0m[38;2;248;248;242mpow([0m[38;2;190;132;255m2[0m[38;2;248;248;242m)[0m
+[38;2;249;38;114mdata[0m[38;2;248;248;242m [0m[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[4;38;2;102;217;239mSquare[0m[38;2;248;248;242m([0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[3;38;2;253;151;31msideLength[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mDouble[0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mShape[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
+[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46marea[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mDouble[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;255;255;255msideLength[0m[38;2;248;248;242m.[0m[38;2;248;248;242mpow[0m[38;2;248;248;242m([0m[38;2;190;132;255m2[0m[38;2;248;248;242m)[0m
[38;2;248;248;242m}[0m
-[38;2;249;38;114mobject[0m[38;2;248;248;242m [0m[38;2;166;226;46mPoint[0m[38;2;248;248;242m [0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;4;38;2;166;226;46mShape[0m[38;2;248;248;242m() {[0m
-[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46marea[0m[38;2;248;248;242m() [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;249;38;114m.[0m[38;2;190;132;255m0[0m
+[38;2;249;38;114mobject[0m[38;2;248;248;242m [0m[4;38;2;102;217;239mPoint[0m[38;2;248;248;242m [0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mShape[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
+[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46marea[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;190;132;255m.0[0m
[38;2;248;248;242m}[0m
-[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[38;2;166;226;46mCircle[0m[38;2;248;248;242m([0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[3;38;2;253;151;31mradius[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mDouble[0m[38;2;248;248;242m) [0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;4;38;2;166;226;46mShape[0m[38;2;248;248;242m() {[0m
-[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46marea[0m[38;2;248;248;242m()[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mDouble[0m[38;2;248;248;242m {[0m
-[38;2;248;248;242m [0m[38;2;249;38;114mreturn[0m[38;2;248;248;242m [0m[38;2;190;132;255mPI[0m[38;2;248;248;242m [0m[38;2;249;38;114m*[0m[38;2;248;248;242m radius [0m[38;2;249;38;114m*[0m[38;2;248;248;242m radius[0m
-[38;2;248;248;242m }[0m
+[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[4;38;2;102;217;239mCircle[0m[38;2;248;248;242m([0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[3;38;2;253;151;31mradius[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mDouble[0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mShape[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
+[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46marea[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mDouble[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
+[38;2;248;248;242m [0m[38;2;249;38;114mreturn[0m[38;2;248;248;242m [0m[38;2;255;255;255mPI[0m[38;2;248;248;242m [0m[38;2;249;38;114m*[0m[38;2;248;248;242m [0m[38;2;255;255;255mradius[0m[38;2;248;248;242m [0m[38;2;249;38;114m*[0m[38;2;248;248;242m [0m[38;2;255;255;255mradius[0m
+[38;2;248;248;242m [0m[38;2;248;248;242m}[0m
[38;2;248;248;242m}[0m
-[38;2;249;38;114mfun[0m[38;2;248;248;242m String.[0m[38;2;166;226;46mextensionMethod[0m[38;2;248;248;242m() [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mtest[0m[38;2;230;219;116m"[0m
+[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mString[0m[38;2;248;248;242m.[0m[38;2;166;226;46mextensionMethod[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mtest[0m[38;2;230;219;116m"[0m
-[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46mmain[0m[38;2;248;248;242m() {[0m
-[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[38;2;166;226;46mname[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;230;219;116m"""[0m
+[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46mmain[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
+[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[38;2;255;255;255mname[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;230;219;116m"""[0m
[38;2;230;219;116m multiline[0m
[38;2;230;219;116m string[0m
[38;2;230;219;116m [0m
[38;2;230;219;116m some numbers: 123123 42[0m
-[38;2;230;219;116m [0m[38;2;230;219;116m"""[0m[38;2;249;38;114m.[0m[38;2;248;248;242mtrimIndent()[0m
-[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[38;2;166;226;46mexample[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m Example(name [0m[38;2;249;38;114m=[0m[38;2;248;248;242m name, numbers [0m[38;2;249;38;114m=[0m[38;2;248;248;242m listOf([0m[38;2;190;132;255m512[0m[38;2;248;248;242m, [0m[38;2;190;132;255m42[0m[38;2;248;248;242m, [0m[38;2;190;132;255mnull[0m[38;2;248;248;242m, [0m[38;2;249;38;114m-[0m[38;2;190;132;255m1[0m[38;2;248;248;242m))[0m
+[38;2;230;219;116m [0m[38;2;230;219;116m"""[0m[38;2;248;248;242m.[0m[38;2;248;248;242mtrimIndent[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m
+[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[38;2;255;255;255mexample[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;248;248;242mExample[0m[38;2;248;248;242m([0m[38;2;255;255;255mname[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;255;255;255mname[0m[38;2;248;248;242m,[0m[38;2;248;248;242m [0m[38;2;255;255;255mnumbers[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;248;248;242mlistOf[0m[38;2;248;248;242m([0m[38;2;190;132;255m512[0m[38;2;248;248;242m,[0m[38;2;248;248;242m [0m[38;2;190;132;255m42[0m[38;2;248;248;242m,[0m[38;2;248;248;242m [0m[38;2;190;132;255mnull[0m[38;2;248;248;242m,[0m[38;2;248;248;242m [0m[38;2;249;38;114m-[0m[38;2;190;132;255m1[0m[38;2;248;248;242m)[0m[38;2;248;248;242m)[0m
-[38;2;248;248;242m example[0m[38;2;249;38;114m.[0m[38;2;248;248;242mnumbers[0m
-[38;2;248;248;242m [0m[38;2;249;38;114m.[0m[38;2;248;248;242mfilterNotNull()[0m
-[38;2;248;248;242m [0m[38;2;249;38;114m.[0m[38;2;248;248;242mforEach { println(it) }[0m
+[38;2;248;248;242m [0m[38;2;255;255;255mexample[0m[38;2;248;248;242m.[0m[38;2;255;255;255mnumbers[0m
+[38;2;248;248;242m [0m[38;2;248;248;242m.[0m[38;2;248;248;242mfilterNotNull[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m
+[38;2;248;248;242m [0m[38;2;248;248;242m.[0m[38;2;248;248;242mforEach[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m[38;2;248;248;242m [0m[38;2;248;248;242mprintln[0m[38;2;248;248;242m([0m[38;2;255;255;255mit[0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;248;248;242m}[0m
-[38;2;248;248;242m setOf(Joke(), DelegatedJoke(Joke())[0m[38;2;249;38;114m.[0m[38;2;248;248;242mjoke)[0m
-[38;2;248;248;242m [0m[38;2;249;38;114m.[0m[38;2;248;248;242mfilter(JokeInterface[0m[38;2;249;38;114m::[0m[38;2;248;248;242misFunny)[0m
-[38;2;248;248;242m [0m[38;2;249;38;114m.[0m[38;2;248;248;242mmap(AbstractJoke[0m[38;2;249;38;114m::[0m[38;2;248;248;242mcontent)[0m
-[38;2;248;248;242m [0m[38;2;249;38;114m.[0m[38;2;248;248;242mforEachIndexed { index[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mInt[0m[38;2;248;248;242m, joke [0m[38;2;249;38;114m->[0m
-[38;2;248;248;242m println([0m[38;2;230;219;116m"[0m[38;2;230;219;116mI heard a funny joke(#[0m[3;38;2;253;151;31m${index + 1}[0m[38;2;230;219;116m): [0m[3;38;2;253;151;31m$joke[0m[38;2;230;219;116m"[0m[38;2;248;248;242m)[0m
-[38;2;248;248;242m }[0m
+[38;2;248;248;242m [0m[38;2;248;248;242msetOf[0m[38;2;248;248;242m([0m[38;2;248;248;242mJoke[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m,[0m[38;2;248;248;242m [0m[38;2;248;248;242mDelegatedJoke[0m[38;2;248;248;242m([0m[38;2;248;248;242mJoke[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m)[0m[38;2;248;248;242m.[0m[38;2;255;255;255mjoke[0m[38;2;248;248;242m)[0m
+[38;2;248;248;242m [0m[38;2;248;248;242m.[0m[38;2;248;248;242mfilter[0m[38;2;248;248;242m([0m[38;2;255;255;255mJokeInterface[0m[38;2;248;248;242m::[0m[38;2;248;248;242misFunny[0m[38;2;248;248;242m)[0m
+[38;2;248;248;242m [0m[38;2;248;248;242m.[0m[38;2;248;248;242mmap[0m[38;2;248;248;242m([0m[38;2;255;255;255mAbstractJoke[0m[38;2;248;248;242m::[0m[38;2;248;248;242mcontent[0m[38;2;248;248;242m)[0m
+[38;2;248;248;242m [0m[38;2;248;248;242m.[0m[38;2;248;248;242mforEachIndexed[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m[38;2;248;248;242m [0m[38;2;255;255;255mindex[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mInt[0m[38;2;248;248;242m,[0m[38;2;248;248;242m [0m[3;38;2;253;151;31mjoke[0m[38;2;248;248;242m [0m[38;2;249;38;114m->[0m
+[38;2;248;248;242m [0m[38;2;248;248;242mprintln[0m[38;2;248;248;242m([0m[38;2;230;219;116m"[0m[38;2;230;219;116mI heard a funny joke(#[0m[38;2;248;248;242m${[0m[38;2;255;255;255mindex[0m[38;2;248;248;242m [0m[38;2;249;38;114m+[0m[38;2;248;248;242m [0m[38;2;190;132;255m1[0m[38;2;248;248;242m}[0m[38;2;230;219;116m): [0m[38;2;255;255;255m$joke[0m[38;2;230;219;116m"[0m[38;2;248;248;242m)[0m
+[38;2;248;248;242m [0m[38;2;248;248;242m}[0m
-[38;2;248;248;242m listOf(Square([0m[38;2;190;132;255m12.3[0m[38;2;248;248;242m), Point, Circle([0m[38;2;190;132;255m5.2[0m[38;2;248;248;242m))[0m
-[38;2;248;248;242m [0m[38;2;249;38;114m.[0m[38;2;248;248;242massociateWith(Shape[0m[38;2;249;38;114m::[0m[38;2;248;248;242marea)[0m
-[38;2;248;248;242m [0m[38;2;249;38;114m.[0m[38;2;248;248;242mtoList()[0m
-[38;2;248;248;242m [0m[38;2;249;38;114m.[0m[38;2;248;248;242msortedBy { it[0m[38;2;249;38;114m.[0m[38;2;248;248;242msecond }[0m
-[38;2;248;248;242m [0m[38;2;249;38;114m.[0m[38;2;248;248;242mforEach {[0m
-[38;2;248;248;242m println([0m[38;2;230;219;116m"[0m[3;38;2;253;151;31m${it.first}[0m[38;2;230;219;116m: [0m[3;38;2;253;151;31m${it.second}[0m[38;2;230;219;116m"[0m[38;2;248;248;242m)[0m
-[38;2;248;248;242m }[0m
+[38;2;248;248;242m [0m[38;2;248;248;242mlistOf[0m[38;2;248;248;242m([0m[38;2;248;248;242mSquare[0m[38;2;248;248;242m([0m[38;2;190;132;255m12.3[0m[38;2;248;248;242m)[0m[38;2;248;248;242m,[0m[38;2;248;248;242m [0m[38;2;255;255;255mPoint[0m[38;2;248;248;242m,[0m[38;2;248;248;242m [0m[38;2;248;248;242mCircle[0m[38;2;248;248;242m([0m[38;2;190;132;255m5.2[0m[38;2;248;248;242m)[0m[38;2;248;248;242m)[0m
+[38;2;248;248;242m [0m[38;2;248;248;242m.[0m[38;2;248;248;242massociateWith[0m[38;2;248;248;242m([0m[38;2;255;255;255mShape[0m[38;2;248;248;242m::[0m[38;2;248;248;242marea[0m[38;2;248;248;242m)[0m
+[38;2;248;248;242m [0m[38;2;248;248;242m.[0m[38;2;248;248;242mtoList[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m
+[38;2;248;248;242m [0m[38;2;248;248;242m.[0m[38;2;248;248;242msortedBy[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m[38;2;248;248;242m [0m[38;2;255;255;255mit[0m[38;2;248;248;242m.[0m[38;2;255;255;255msecond[0m[38;2;248;248;242m [0m[38;2;248;248;242m}[0m
+[38;2;248;248;242m [0m[38;2;248;248;242m.[0m[38;2;248;248;242mforEach[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
+[38;2;248;248;242m [0m[38;2;248;248;242mprintln[0m[38;2;248;248;242m([0m[38;2;230;219;116m"[0m[38;2;248;248;242m${[0m[38;2;255;255;255mit[0m[38;2;248;248;242m.[0m[38;2;255;255;255mfirst[0m[38;2;248;248;242m}[0m[38;2;230;219;116m: [0m[38;2;248;248;242m${[0m[38;2;255;255;255mit[0m[38;2;248;248;242m.[0m[38;2;255;255;255msecond[0m[38;2;248;248;242m}[0m[38;2;230;219;116m"[0m[38;2;248;248;242m)[0m
+[38;2;248;248;242m [0m[38;2;248;248;242m}[0m
-[38;2;248;248;242m println([0m[38;2;230;219;116m"[0m[38;2;230;219;116msome string[0m[38;2;230;219;116m"[0m[38;2;249;38;114m.[0m[38;2;248;248;242mextensionMethod())[0m
+[38;2;248;248;242m [0m[38;2;248;248;242mprintln[0m[38;2;248;248;242m([0m[38;2;230;219;116m"[0m[38;2;230;219;116msome string[0m[38;2;230;219;116m"[0m[38;2;248;248;242m.[0m[38;2;248;248;242mextensionMethod[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m)[0m
-[38;2;248;248;242m require(SomeSingleton[0m[38;2;249;38;114m::[0m[38;2;248;248;242mclass[0m[38;2;249;38;114m.[0m[38;2;248;248;242msimpleName [0m[38;2;249;38;114m==[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mSomeSingletonName[0m[38;2;230;219;116m"[0m[38;2;248;248;242m) { [0m[38;2;230;219;116m"[0m[38;2;230;219;116msomething does not seem right...[0m[38;2;230;219;116m"[0m[38;2;248;248;242m }[0m
+[38;2;248;248;242m [0m[38;2;248;248;242mrequire[0m[38;2;248;248;242m([0m[38;2;255;255;255mSomeSingleton[0m[38;2;248;248;242m::[0m[38;2;249;38;114mclass[0m[38;2;248;248;242m.[0m[38;2;255;255;255msimpleName[0m[38;2;248;248;242m [0m[38;2;249;38;114m==[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mSomeSingletonName[0m[38;2;230;219;116m"[0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116msomething does not seem right...[0m[38;2;230;219;116m"[0m[38;2;248;248;242m [0m[38;2;248;248;242m}[0m
[38;2;248;248;242m}[0m