mirror of
https://github.com/sharkdp/bat.git
synced 2025-08-22 05:13:00 +02:00
Merge branch 'master' into master
This commit is contained in:
31
.github/workflows/CICD.yml
vendored
31
.github/workflows/CICD.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- crate_metadata
|
||||
- ensure_cargo_fmt
|
||||
- lint
|
||||
- min_version
|
||||
- license_checks
|
||||
- test_with_new_syntaxes_and_themes
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
name: Extract crate metadata
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Extract crate information
|
||||
id: crate_metadata
|
||||
run: |
|
||||
@@ -51,15 +51,16 @@ jobs:
|
||||
homepage: ${{ steps.crate_metadata.outputs.homepage }}
|
||||
msrv: ${{ steps.crate_metadata.outputs.msrv }}
|
||||
|
||||
ensure_cargo_fmt:
|
||||
name: Ensure 'cargo fmt' has been run
|
||||
lint:
|
||||
name: Ensure code quality
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt
|
||||
- uses: actions/checkout@v4
|
||||
components: rustfmt,clippy
|
||||
- uses: actions/checkout@v5
|
||||
- run: cargo fmt -- --check
|
||||
- run: cargo clippy --locked --all-targets --all-features -- -D warnings
|
||||
|
||||
min_version:
|
||||
name: Minimum supported rust version
|
||||
@@ -67,15 +68,11 @@ jobs:
|
||||
needs: crate_metadata
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
uses: actions/checkout@v5
|
||||
- name: Install rust toolchain (v${{ needs.crate_metadata.outputs.msrv }})
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ needs.crate_metadata.outputs.msrv }}
|
||||
components: clippy
|
||||
- name: Run clippy (on minimum supported rust version to prevent warnings we can't fix)
|
||||
run: cargo clippy --locked --all-targets ${{ env.MSRV_FEATURES }}
|
||||
- name: Run tests
|
||||
run: cargo test --locked ${{ env.MSRV_FEATURES }}
|
||||
|
||||
@@ -83,7 +80,7 @@ jobs:
|
||||
name: License checks
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: true # we especially want to perform license checks on submodules
|
||||
- run: tests/scripts/license-checks.sh
|
||||
@@ -93,7 +90,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Git checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: true # we need all syntax and theme submodules
|
||||
- name: Install Rust toolchain
|
||||
@@ -122,7 +119,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Git checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
- name: Prepare environment variables
|
||||
run: |
|
||||
echo "BAT_SYSTEM_CONFIG_PREFIX=$GITHUB_WORKSPACE/tests/examples/system_config" >> $GITHUB_ENV
|
||||
@@ -138,7 +135,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Git checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Check documentation
|
||||
@@ -153,7 +150,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: cargo install cargo-audit --locked
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- run: cargo audit
|
||||
|
||||
build:
|
||||
@@ -181,7 +178,7 @@ jobs:
|
||||
BUILD_CMD: cargo
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install prerequisites
|
||||
shell: bash
|
||||
|
@@ -18,6 +18,7 @@
|
||||
- Add missing mappings for various bash/zsh files, see PR #3262 (@AdamGaskins)
|
||||
- Send all bat errors to stderr by default, see #3336 (@JerryImMouse)
|
||||
- Make --map-syntax target case insensitive to match --language, see #3206 (@keith-hall)
|
||||
- Correctly determine the end of the line in UTF16LE/BE input #3369 (@keith-hall)
|
||||
|
||||
## Other
|
||||
|
||||
|
8
Cargo.lock
generated
8
Cargo.lock
generated
@@ -906,9 +906,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.169"
|
||||
version = "0.2.175"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
|
||||
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
|
||||
|
||||
[[package]]
|
||||
name = "libgit2-sys"
|
||||
@@ -996,9 +996,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.29.0"
|
||||
version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"cfg-if",
|
||||
|
@@ -101,7 +101,7 @@ tempfile = "3.16.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
[target.'cfg(unix)'.dev-dependencies]
|
||||
nix = { version = "0.29", default-features = false, features = ["term"] }
|
||||
nix = { version = "0.30", default-features = false, features = ["term"] }
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = "1.0.97"
|
||||
|
30
README.md
30
README.md
@@ -711,7 +711,7 @@ sidebar. Calling `bat` with `--tabs=0` will override it and let tabs be consumed
|
||||
|
||||
### Dark mode
|
||||
|
||||
If you make use of the dark mode feature in macOS, you might want to configure `bat` to use a different
|
||||
If you make use of the dark mode feature in **macOS**, you might want to configure `bat` to use a different
|
||||
theme based on the OS theme. The following snippet uses the `default` theme when in the _dark mode_
|
||||
and the `GitHub` theme when in the _light mode_.
|
||||
|
||||
@@ -719,6 +719,34 @@ and the `GitHub` theme when in the _light mode_.
|
||||
alias cat="bat --theme auto:system --theme-dark default --theme-light GitHub"
|
||||
```
|
||||
|
||||
The same dark mode feature is now available in **GNOME** and affects the `org.gnome.desktop.interface color-scheme` setting. The following code converts the above to use said setting.
|
||||
|
||||
```bash
|
||||
# .bashrc
|
||||
sys_color_scheme_is_dark() {
|
||||
condition=$(gsettings get org.gnome.desktop.interface color-scheme)
|
||||
condition=$(echo "$condition" | tr -d "[:space:]'")
|
||||
if [ $condition == "prefer-dark" ]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
bat_alias_wrapper() {
|
||||
#get color scheme
|
||||
sys_color_scheme_is_dark
|
||||
if [[ $? -eq 0 ]]; then
|
||||
# bat command with dark color scheme
|
||||
bat --theme=default "$@"
|
||||
else
|
||||
# bat command with light color scheme
|
||||
bat --theme=GitHub "$@"
|
||||
fi
|
||||
}
|
||||
alias cat='bat_alias_wrapper'
|
||||
```
|
||||
|
||||
|
||||
## Configuration file
|
||||
|
||||
|
2
assets/syntaxes/02_Extra/Fish
vendored
2
assets/syntaxes/02_Extra/Fish
vendored
Submodule assets/syntaxes/02_Extra/Fish updated: 98316d4332...ef510fd759
2
assets/theme_preview.rs
vendored
2
assets/theme_preview.rs
vendored
@@ -1,5 +1,5 @@
|
||||
// Output the square of a number.
|
||||
fn print_square(num: f64) {
|
||||
let result = f64::powf(num, 2.0);
|
||||
println!("The square of {:.2} is {:.2}.", num, result);
|
||||
println!("The square of {num:.2} is {result:.2}.");
|
||||
}
|
||||
|
@@ -152,7 +152,7 @@ impl HighlightingAssets {
|
||||
&self,
|
||||
path: impl AsRef<Path>,
|
||||
mapping: &SyntaxMapping,
|
||||
) -> Result<SyntaxReferenceInSet> {
|
||||
) -> Result<SyntaxReferenceInSet<'_>> {
|
||||
let path = path.as_ref();
|
||||
|
||||
let syntax_match = mapping.get_syntax_for(path);
|
||||
@@ -191,11 +191,11 @@ impl HighlightingAssets {
|
||||
Some(theme) => theme,
|
||||
None => {
|
||||
if theme == "ansi-light" || theme == "ansi-dark" {
|
||||
bat_warning!("Theme '{}' is deprecated, using 'ansi' instead.", theme);
|
||||
bat_warning!("Theme '{theme}' is deprecated, using 'ansi' instead.");
|
||||
return self.get_theme("ansi");
|
||||
}
|
||||
if !theme.is_empty() {
|
||||
bat_warning!("Unknown theme '{}', using default.", theme)
|
||||
bat_warning!("Unknown theme '{theme}', using default.")
|
||||
}
|
||||
self.get_theme_set()
|
||||
.get(
|
||||
@@ -212,7 +212,7 @@ impl HighlightingAssets {
|
||||
language: Option<&str>,
|
||||
input: &mut OpenedInput,
|
||||
mapping: &SyntaxMapping,
|
||||
) -> Result<SyntaxReferenceInSet> {
|
||||
) -> Result<SyntaxReferenceInSet<'_>> {
|
||||
if let Some(language) = language {
|
||||
let syntax_set = self.get_syntax_set()?;
|
||||
return syntax_set
|
||||
@@ -244,14 +244,17 @@ impl HighlightingAssets {
|
||||
pub(crate) fn find_syntax_by_name(
|
||||
&self,
|
||||
syntax_name: &str,
|
||||
) -> Result<Option<SyntaxReferenceInSet>> {
|
||||
) -> Result<Option<SyntaxReferenceInSet<'_>>> {
|
||||
let syntax_set = self.get_syntax_set()?;
|
||||
Ok(syntax_set
|
||||
.find_syntax_by_name(syntax_name)
|
||||
.map(|syntax| SyntaxReferenceInSet { syntax, syntax_set }))
|
||||
}
|
||||
|
||||
fn find_syntax_by_extension(&self, e: Option<&OsStr>) -> Result<Option<SyntaxReferenceInSet>> {
|
||||
fn find_syntax_by_extension(
|
||||
&self,
|
||||
e: Option<&OsStr>,
|
||||
) -> Result<Option<SyntaxReferenceInSet<'_>>> {
|
||||
let syntax_set = self.get_syntax_set()?;
|
||||
let extension = e.and_then(|x| x.to_str()).unwrap_or_default();
|
||||
Ok(syntax_set
|
||||
@@ -259,7 +262,7 @@ impl HighlightingAssets {
|
||||
.map(|syntax| SyntaxReferenceInSet { syntax, syntax_set }))
|
||||
}
|
||||
|
||||
fn find_syntax_by_token(&self, token: &str) -> Result<Option<SyntaxReferenceInSet>> {
|
||||
fn find_syntax_by_token(&self, token: &str) -> Result<Option<SyntaxReferenceInSet<'_>>> {
|
||||
let syntax_set = self.get_syntax_set()?;
|
||||
Ok(syntax_set
|
||||
.find_syntax_by_token(token)
|
||||
@@ -270,7 +273,7 @@ impl HighlightingAssets {
|
||||
&self,
|
||||
file_name: &OsStr,
|
||||
ignored_suffixes: &IgnoredSuffixes,
|
||||
) -> Result<Option<SyntaxReferenceInSet>> {
|
||||
) -> Result<Option<SyntaxReferenceInSet<'_>>> {
|
||||
let mut syntax = self.find_syntax_by_extension(Some(file_name))?;
|
||||
if syntax.is_none() {
|
||||
syntax =
|
||||
@@ -286,7 +289,7 @@ impl HighlightingAssets {
|
||||
&self,
|
||||
file_name: &OsStr,
|
||||
ignored_suffixes: &IgnoredSuffixes,
|
||||
) -> Result<Option<SyntaxReferenceInSet>> {
|
||||
) -> Result<Option<SyntaxReferenceInSet<'_>>> {
|
||||
let mut syntax = self.find_syntax_by_extension(Path::new(file_name).extension())?;
|
||||
if syntax.is_none() {
|
||||
syntax =
|
||||
@@ -301,7 +304,7 @@ impl HighlightingAssets {
|
||||
fn get_first_line_syntax(
|
||||
&self,
|
||||
reader: &mut InputReader,
|
||||
) -> Result<Option<SyntaxReferenceInSet>> {
|
||||
) -> Result<Option<SyntaxReferenceInSet<'_>>> {
|
||||
let syntax_set = self.get_syntax_set()?;
|
||||
Ok(String::from_utf8(reader.first_line.clone())
|
||||
.ok()
|
||||
@@ -354,8 +357,7 @@ fn asset_from_cache<T: serde::de::DeserializeOwned>(
|
||||
) -> Result<T> {
|
||||
let contents = fs::read(path).map_err(|_| {
|
||||
format!(
|
||||
"Could not load cached {} '{}'",
|
||||
description,
|
||||
"Could not load cached {description} '{}'",
|
||||
path.to_string_lossy()
|
||||
)
|
||||
})?;
|
||||
|
@@ -40,15 +40,15 @@ impl AssetsMetadata {
|
||||
/// Load metadata about the stored cache file from the given folder.
|
||||
///
|
||||
/// There are several possibilities:
|
||||
/// - We find a metadata.yaml file and are able to parse it
|
||||
/// => return the contained information
|
||||
/// - We find a metadata.yaml file and but are not able to parse it
|
||||
/// => return a SerdeYamlError
|
||||
/// - We do not find a metadata.yaml file but a syntaxes.bin or themes.bin file
|
||||
/// => assume that these were created by an old version of bat and return
|
||||
/// AssetsMetadata::default() without version information
|
||||
/// - We do not find a metadata.yaml file and no cached assets
|
||||
/// => no user provided assets are available, return None
|
||||
/// - We find a `metadata.yaml` file and are able to parse it
|
||||
/// - return the contained information
|
||||
/// - We find a `metadata.yaml` file, but are not able to parse it
|
||||
/// - return a [`Error::SerdeYamlError`]
|
||||
/// - We do not find a `metadata.yaml` file but a `syntaxes.bin` or `themes.bin` file
|
||||
/// - assume that these were created by an old version of bat and return
|
||||
/// [`AssetsMetadata::default()`] without version information
|
||||
/// - We do not find a `metadata.yaml` file and no cached assets
|
||||
/// - no user provided assets are available, return `None`
|
||||
pub fn load_from_folder(path: &Path) -> Result<Option<Self>> {
|
||||
match Self::try_load_from_folder(path) {
|
||||
Ok(metadata) => Ok(Some(metadata)),
|
||||
|
@@ -47,9 +47,8 @@ fn build_theme_set(source_dir: &Path, include_integrated_assets: bool) -> Result
|
||||
let res = theme_set.add_from_folder(&theme_dir);
|
||||
if let Err(err) = res {
|
||||
println!(
|
||||
"Failed to load one or more themes from '{}' (reason: '{}')",
|
||||
"Failed to load one or more themes from '{}' (reason: '{err}')",
|
||||
theme_dir.to_string_lossy(),
|
||||
err,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
@@ -162,15 +161,10 @@ fn asset_to_cache<T: serde::Serialize>(
|
||||
description: &str,
|
||||
compressed: bool,
|
||||
) -> Result<()> {
|
||||
print!("Writing {} to {} ... ", description, path.to_string_lossy());
|
||||
print!("Writing {description} to {} ... ", path.to_string_lossy());
|
||||
let contents = asset_to_contents(asset, description, compressed)?;
|
||||
std::fs::write(path, &contents[..]).map_err(|_| {
|
||||
format!(
|
||||
"Could not save {} to {}",
|
||||
description,
|
||||
path.to_string_lossy()
|
||||
)
|
||||
})?;
|
||||
std::fs::write(path, &contents[..])
|
||||
.map_err(|_| format!("Could not save {description} to {}", path.to_string_lossy()))?;
|
||||
println!("okay");
|
||||
Ok(())
|
||||
}
|
||||
|
@@ -96,7 +96,7 @@ impl App {
|
||||
Ok(clap_app::build_app(interactive_output).get_matches_from(args))
|
||||
}
|
||||
|
||||
pub fn config(&self, inputs: &[Input]) -> Result<Config> {
|
||||
pub fn config(&self, inputs: &[Input]) -> Result<Config<'_>> {
|
||||
let style_components = self.style_components()?;
|
||||
|
||||
let extra_plain = self.matches.get_count("plain") > 1;
|
||||
@@ -338,7 +338,7 @@ impl App {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn inputs(&self) -> Result<Vec<Input>> {
|
||||
pub fn inputs(&self) -> Result<Vec<Input<'_>>> {
|
||||
let filenames: Option<Vec<&Path>> = self
|
||||
.matches
|
||||
.get_many::<PathBuf>("file-name")
|
||||
|
@@ -50,7 +50,7 @@ fn clear_asset(path: PathBuf, description: &str) {
|
||||
println!("skipped (not present)");
|
||||
}
|
||||
Err(err) => {
|
||||
println!("could not remove the cache file {:?}: {}", &path, err);
|
||||
println!("could not remove the cache file {path:?}: {err}");
|
||||
}
|
||||
Ok(_) => println!("okay"),
|
||||
}
|
||||
|
@@ -16,7 +16,7 @@ static VERSION: Lazy<String> = Lazy::new(|| {
|
||||
if git_version.is_empty() {
|
||||
crate_version!().to_string()
|
||||
} else {
|
||||
format!("{} ({})", crate_version!(), git_version)
|
||||
format!("{} ({git_version})", crate_version!())
|
||||
}
|
||||
});
|
||||
|
||||
|
@@ -88,9 +88,8 @@ pub fn generate_config_file() -> bat::error::Result<()> {
|
||||
|
||||
fs::write(&config_file, default_config).map_err(|e| {
|
||||
format!(
|
||||
"Failed to create config file at '{}': {}",
|
||||
"Failed to create config file at '{}': {e}",
|
||||
config_file.to_string_lossy(),
|
||||
e
|
||||
)
|
||||
})?;
|
||||
|
||||
|
@@ -5,7 +5,7 @@ pub fn new_file_input<'a>(file: &'a Path, name: Option<&'a Path>) -> Input<'a> {
|
||||
named(Input::ordinary_file(file), name.or(Some(file)))
|
||||
}
|
||||
|
||||
pub fn new_stdin_input(name: Option<&Path>) -> Input {
|
||||
pub fn new_stdin_input(name: Option<&Path>) -> Input<'_> {
|
||||
named(Input::stdin(), name)
|
||||
}
|
||||
|
||||
|
@@ -162,7 +162,7 @@ pub fn get_languages(config: &Config, cache_dir: &Path) -> Result<String> {
|
||||
};
|
||||
|
||||
for lang in languages {
|
||||
write!(result, "{:width$}{}", lang.name, separator, width = longest).ok();
|
||||
write!(result, "{:width$}{separator}", lang.name, width = longest).ok();
|
||||
|
||||
// Number of characters on this line so far, wrap before `desired_width`
|
||||
let mut num_chars = 0;
|
||||
@@ -173,7 +173,7 @@ pub fn get_languages(config: &Config, cache_dir: &Path) -> Result<String> {
|
||||
let new_chars = word.len() + comma_separator.len();
|
||||
if num_chars + new_chars >= desired_width {
|
||||
num_chars = 0;
|
||||
write!(result, "\n{:width$}{}", "", separator, width = longest).ok();
|
||||
write!(result, "\n{:width$}{separator}", "", width = longest).ok();
|
||||
}
|
||||
|
||||
num_chars += new_chars;
|
||||
@@ -224,9 +224,8 @@ pub fn list_themes(
|
||||
if config.colored_output {
|
||||
writeln!(
|
||||
writer,
|
||||
"Theme: {}{}\n",
|
||||
"Theme: {}{default_theme_info}\n",
|
||||
Style::new().bold().paint(theme.to_string()),
|
||||
default_theme_info
|
||||
)?;
|
||||
config.theme = theme.to_string();
|
||||
Controller::new(&config, &assets)
|
||||
@@ -363,7 +362,7 @@ fn run() -> Result<bool> {
|
||||
"fish" => println!("{}", completions::FISH_COMPLETION),
|
||||
"ps1" => println!("{}", completions::PS1_COMPLETION),
|
||||
"zsh" => println!("{}", completions::ZSH_COMPLETION),
|
||||
_ => unreachable!("No completion for shell '{}' available.", shell),
|
||||
_ => unreachable!("No completion for shell '{shell}' available."),
|
||||
}
|
||||
return Ok(true);
|
||||
}
|
||||
|
@@ -60,18 +60,16 @@ pub fn default_error_handler(error: &Error, output: &mut dyn Write) {
|
||||
Error::SerdeYamlError(_) => {
|
||||
writeln!(
|
||||
output,
|
||||
"{}: Error while parsing metadata.yaml file: {}",
|
||||
"{}: Error while parsing metadata.yaml file: {error}",
|
||||
Red.paint("[bat error]"),
|
||||
error
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
_ => {
|
||||
writeln!(
|
||||
&mut std::io::stderr().lock(),
|
||||
"{}: {}",
|
||||
"{}: {error}",
|
||||
Red.paint("[bat error]"),
|
||||
error
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
|
92
src/input.rs
92
src/input.rs
@@ -217,14 +217,14 @@ impl<'a> Input<'a> {
|
||||
metadata: self.metadata,
|
||||
reader: {
|
||||
let mut file = File::open(&path)
|
||||
.map_err(|e| format!("'{}': {}", path.to_string_lossy(), e))?;
|
||||
.map_err(|e| format!("'{}': {e}", path.to_string_lossy()))?;
|
||||
if file.metadata()?.is_dir() {
|
||||
return Err(format!("'{}' is a directory.", path.to_string_lossy()).into());
|
||||
}
|
||||
|
||||
if let Some(stdout) = stdout_identifier {
|
||||
let input_identifier = Identifier::try_from(file).map_err(|e| {
|
||||
format!("{}: Error identifying file: {}", path.to_string_lossy(), e)
|
||||
format!("{}: Error identifying file: {e}", path.to_string_lossy())
|
||||
})?;
|
||||
if stdout.surely_conflicts_with(&input_identifier) {
|
||||
return Err(format!(
|
||||
@@ -267,7 +267,9 @@ impl<'a> InputReader<'a> {
|
||||
};
|
||||
|
||||
if content_type == Some(ContentType::UTF_16LE) {
|
||||
reader.read_until(0x00, &mut first_line).ok();
|
||||
read_utf16_line(&mut reader, &mut first_line, 0x00, 0x0A).ok();
|
||||
} else if content_type == Some(ContentType::UTF_16BE) {
|
||||
read_utf16_line(&mut reader, &mut first_line, 0x0A, 0x00).ok();
|
||||
}
|
||||
|
||||
InputReader {
|
||||
@@ -283,16 +285,44 @@ impl<'a> InputReader<'a> {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let res = self.inner.read_until(b'\n', buf).map(|size| size > 0)?;
|
||||
|
||||
if self.content_type == Some(ContentType::UTF_16LE) {
|
||||
let _ = self.inner.read_until(0x00, buf);
|
||||
return read_utf16_line(&mut self.inner, buf, 0x00, 0x0A);
|
||||
}
|
||||
if self.content_type == Some(ContentType::UTF_16BE) {
|
||||
return read_utf16_line(&mut self.inner, buf, 0x0A, 0x00);
|
||||
}
|
||||
|
||||
let res = self.inner.read_until(b'\n', buf).map(|size| size > 0)?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
fn read_utf16_line<R: BufRead>(
|
||||
reader: &mut R,
|
||||
buf: &mut Vec<u8>,
|
||||
read_until_char: u8,
|
||||
preceded_by_char: u8,
|
||||
) -> io::Result<bool> {
|
||||
loop {
|
||||
let mut temp = Vec::new();
|
||||
let n = reader.read_until(read_until_char, &mut temp)?;
|
||||
if n == 0 {
|
||||
// EOF reached
|
||||
break;
|
||||
}
|
||||
buf.extend_from_slice(&temp);
|
||||
if buf.len() >= 2
|
||||
&& buf[buf.len() - 2] == preceded_by_char
|
||||
&& buf[buf.len() - 1] == read_until_char
|
||||
{
|
||||
// end of line found
|
||||
break;
|
||||
}
|
||||
// end of line not found, keep going
|
||||
}
|
||||
Ok(!buf.is_empty())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic() {
|
||||
let content = b"#!/bin/bash\necho hello";
|
||||
@@ -350,3 +380,53 @@ fn utf16le() {
|
||||
assert!(!res.unwrap());
|
||||
assert!(buffer.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn utf16le_issue3367() {
|
||||
let content = b"\xFF\xFE\x0A\x4E\x00\x4E\x0A\x4F\x00\x52\x0A\x00\
|
||||
\x6F\x00\x20\x00\x62\x00\x61\x00\x72\x00\x0A\x00\
|
||||
\x68\x00\x65\x00\x6C\x00\x6C\x00\x6F\x00\x20\x00\x77\x00\x6F\x00\x72\x00\x6C\x00\x64\x00";
|
||||
let mut reader = InputReader::new(&content[..]);
|
||||
|
||||
assert_eq!(
|
||||
b"\xFF\xFE\x0A\x4E\x00\x4E\x0A\x4F\x00\x52\x0A\x00",
|
||||
&reader.first_line[..]
|
||||
);
|
||||
|
||||
let mut buffer = vec![];
|
||||
|
||||
let res = reader.read_line(&mut buffer);
|
||||
assert!(res.is_ok());
|
||||
assert!(res.unwrap());
|
||||
assert_eq!(
|
||||
b"\xFF\xFE\x0A\x4E\x00\x4E\x0A\x4F\x00\x52\x0A\x00",
|
||||
&buffer[..]
|
||||
);
|
||||
|
||||
buffer.clear();
|
||||
|
||||
let res = reader.read_line(&mut buffer);
|
||||
assert!(res.is_ok());
|
||||
assert!(res.unwrap());
|
||||
assert_eq!(
|
||||
b"\x6F\x00\x20\x00\x62\x00\x61\x00\x72\x00\x0A\x00",
|
||||
&buffer[..]
|
||||
);
|
||||
|
||||
buffer.clear();
|
||||
|
||||
let res = reader.read_line(&mut buffer);
|
||||
assert!(res.is_ok());
|
||||
assert!(res.unwrap());
|
||||
assert_eq!(
|
||||
b"\x68\x00\x65\x00\x6C\x00\x6C\x00\x6F\x00\x20\x00\x77\x00\x6F\x00\x72\x00\x6C\x00\x64\x00",
|
||||
&buffer[..]
|
||||
);
|
||||
|
||||
buffer.clear();
|
||||
|
||||
let res = reader.read_line(&mut buffer);
|
||||
assert!(res.is_ok());
|
||||
assert!(!res.unwrap());
|
||||
assert!(buffer.is_empty());
|
||||
}
|
||||
|
@@ -39,7 +39,7 @@ impl LessOpenPreprocessor {
|
||||
// Note that $LESSCLOSE has no such requirement
|
||||
if lessopen.match_indices("%s").count() != 1 {
|
||||
let error_msg = "LESSOPEN ignored: must contain exactly one %s";
|
||||
bat_warning!("{}", error_msg);
|
||||
bat_warning!("{error_msg}");
|
||||
return Err(error_msg.into());
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ impl LessOpenPreprocessor {
|
||||
if self.preprocess_stdin {
|
||||
if let Some(stdout) = stdout_identifier {
|
||||
let input_identifier = Identifier::try_from(clircle::Stdio::Stdin)
|
||||
.map_err(|e| format!("Stdin: Error identifying file: {}", e))?;
|
||||
.map_err(|e| format!("Stdin: Error identifying file: {e}"))?;
|
||||
if stdout.surely_conflicts_with(&input_identifier) {
|
||||
return Err("IO circle detected. The input from stdin is also an output. Aborting to avoid infinite loop.".into());
|
||||
}
|
||||
|
@@ -329,7 +329,7 @@ impl<'a> InteractivePrinter<'a> {
|
||||
self.print_horizontal_line_term(handle, self.colors.grid)?;
|
||||
} else {
|
||||
let hline = "─".repeat(self.config.term_width - (self.panel_width + 1));
|
||||
let hline = format!("{}{}{}", "─".repeat(self.panel_width), grid_char, hline);
|
||||
let hline = format!("{}{grid_char}{hline}", "─".repeat(self.panel_width));
|
||||
writeln!(handle, "{}", self.colors.grid.paint(hline))?;
|
||||
}
|
||||
|
||||
@@ -343,8 +343,7 @@ impl<'a> InteractivePrinter<'a> {
|
||||
|
||||
let text_truncated: String = text.chars().take(self.panel_width - 1).collect();
|
||||
let text_filled: String = format!(
|
||||
"{}{}",
|
||||
text_truncated,
|
||||
"{text_truncated}{}",
|
||||
" ".repeat(self.panel_width - 1 - text_truncated.len())
|
||||
);
|
||||
if self.config.style_components.grid() {
|
||||
@@ -400,7 +399,7 @@ impl<'a> InteractivePrinter<'a> {
|
||||
while content_graphemes.len() > content_width {
|
||||
let (content_line, remaining) = content_graphemes.split_at(content_width);
|
||||
self.print_header_component_with_indent(handle, content_line.join("").as_str())?;
|
||||
content_graphemes = remaining.iter().cloned().collect();
|
||||
content_graphemes = remaining.to_vec();
|
||||
}
|
||||
self.print_header_component_with_indent(handle, content_graphemes.join("").as_str())
|
||||
}
|
||||
@@ -513,13 +512,12 @@ impl Printer for InteractivePrinter<'_> {
|
||||
.try_for_each(|component| match component {
|
||||
StyleComponent::HeaderFilename => {
|
||||
let header_filename = format!(
|
||||
"{}{}{}",
|
||||
"{}{}{mode}",
|
||||
description
|
||||
.kind()
|
||||
.map(|kind| format!("{kind}: "))
|
||||
.unwrap_or_else(|| "".into()),
|
||||
self.colors.header_value.paint(description.title()),
|
||||
mode
|
||||
);
|
||||
self.print_header_multiline_component(handle, &header_filename)
|
||||
}
|
||||
@@ -701,7 +699,7 @@ impl Printer for InteractivePrinter<'_> {
|
||||
"{}{}",
|
||||
as_terminal_escaped(
|
||||
style,
|
||||
&format!("{}{}", self.ansi_style, text_trimmed),
|
||||
&format!("{}{text_trimmed}", self.ansi_style),
|
||||
true_color,
|
||||
colored_output,
|
||||
italics,
|
||||
@@ -791,7 +789,7 @@ impl Printer for InteractivePrinter<'_> {
|
||||
"{}{}\n{}",
|
||||
as_terminal_escaped(
|
||||
style,
|
||||
&format!("{}{}", self.ansi_style, line_buf),
|
||||
&format!("{}{line_buf}", self.ansi_style),
|
||||
self.config.true_color,
|
||||
self.config.colored_output,
|
||||
self.config.use_italic_text,
|
||||
@@ -818,7 +816,7 @@ impl Printer for InteractivePrinter<'_> {
|
||||
"{}",
|
||||
as_terminal_escaped(
|
||||
style,
|
||||
&format!("{}{}", self.ansi_style, line_buf),
|
||||
&format!("{}{line_buf}", self.ansi_style),
|
||||
self.config.true_color,
|
||||
self.config.colored_output,
|
||||
self.config.use_italic_text,
|
||||
|
@@ -212,15 +212,15 @@ impl Attributes {
|
||||
}
|
||||
|
||||
fn update_with_charset(&mut self, kind: char, set: impl Iterator<Item = char>) -> bool {
|
||||
self.charset = format!("\x1B{}{}", kind, set.take(1).collect::<String>());
|
||||
self.charset = format!("\x1B{kind}{}", set.take(1).collect::<String>());
|
||||
true
|
||||
}
|
||||
|
||||
fn parse_color(color: u16, parameters: &mut dyn Iterator<Item = u16>) -> String {
|
||||
match color % 10 {
|
||||
8 => match parameters.next() {
|
||||
Some(5) /* 256-color */ => format!("\x1B[{};5;{}m", color, join(";", 1, parameters)),
|
||||
Some(2) /* 24-bit color */ => format!("\x1B[{};2;{}m", color, join(";", 3, parameters)),
|
||||
Some(5) /* 256-color */ => format!("\x1B[{color};5;{}m", join(";", 1, parameters)),
|
||||
Some(2) /* 24-bit color */ => format!("\x1B[{color};2;{}m", join(";", 3, parameters)),
|
||||
Some(c) => format!("\x1B[{color};{c}m"),
|
||||
_ => "".to_owned(),
|
||||
},
|
||||
|
BIN
tests/examples/test_UTF-16BE-complicated.txt
vendored
Normal file
BIN
tests/examples/test_UTF-16BE-complicated.txt
vendored
Normal file
Binary file not shown.
BIN
tests/examples/test_UTF-16BE.txt
vendored
Normal file
BIN
tests/examples/test_UTF-16BE.txt
vendored
Normal file
Binary file not shown.
BIN
tests/examples/test_UTF-16LE-complicated.txt
vendored
Normal file
BIN
tests/examples/test_UTF-16LE-complicated.txt
vendored
Normal file
Binary file not shown.
@@ -1452,6 +1452,38 @@ fn utf16() {
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("hello world\n");
|
||||
|
||||
bat()
|
||||
.arg("--plain")
|
||||
.arg("--decorations=always")
|
||||
.arg("test_UTF-16BE.txt")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("hello world\nthis is a test\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn utf16le() {
|
||||
bat()
|
||||
.arg("--decorations=always")
|
||||
.arg("--style=numbers")
|
||||
.arg("--color=never")
|
||||
.arg("test_UTF-16LE-complicated.txt")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(" 1 上一伊刀\n 2 foo bar\n 3 hello world\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn utf16be() {
|
||||
bat()
|
||||
.arg("--decorations=always")
|
||||
.arg("--style=numbers")
|
||||
.arg("--color=never")
|
||||
.arg("test_UTF-16BE-complicated.txt")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(" 1 上一伊刀\n 2 foo bar\n 3 hello world\n");
|
||||
}
|
||||
|
||||
// Regression test for https://github.com/sharkdp/bat/issues/1922
|
||||
|
@@ -75,7 +75,7 @@ fn create_sample_directory() -> Result<TempDir, git2::Error> {
|
||||
|
||||
// Copy over `sample.rs`
|
||||
let sample_path = temp_dir.path().join("sample.rs");
|
||||
println!("{:?}", &sample_path);
|
||||
println!("{sample_path:?}");
|
||||
fs::copy("tests/snapshots/sample.rs", &sample_path).expect("successful copy");
|
||||
|
||||
// Commit
|
||||
|
@@ -19,7 +19,7 @@ fn get_mocked_pagers_dir() -> PathBuf {
|
||||
pub fn from(base: &str) -> String {
|
||||
let mut cmd_and_args = shell_words::split(base).unwrap();
|
||||
let suffix = if cfg!(windows) { ".bat" } else { "" };
|
||||
let mut out_cmd = format!("{}{}", cmd_and_args.first().unwrap(), suffix);
|
||||
let mut out_cmd = format!("{}{suffix}", cmd_and_args.first().unwrap());
|
||||
|
||||
if (cmd_and_args.len() > 1) {
|
||||
out_cmd.push(' ');
|
||||
|
Reference in New Issue
Block a user