bat icon indicating copy to clipboard operation
bat copied to clipboard

`--style` documentation is misleading

Open TheBicPen opened this issue 7 months ago • 1 comments

The description on --style is misleading. It says "auto: same as 'full', unless the output is piped." and "full: enables all available components (default)".

This is misleading - I read it as saying that when --style=full is passed, the full decorations are enabled in a command like this: echo '' | bat --style=full | cat. This is not the case - this pipeline prints a newline rather than something like "STDIN\n 1 ". Compare the output with echo '' | bat --style=full. If the output is piped, loop_through is enabled, the SimplePrinter is selected, and the output is printed without any decorations regardless of the --style=full flag. This effectively throws away the style configuration whenever cat-compatibility mode is enabled, which is not what the documentation says.

As a secondary question, what is the point of the --style=auto option if the style is ignored when the output is piped? It provides the same functionality as --style=full --decorations=auto. It seems like having both is redundant.

TheBicPen avatar May 30 '25 22:05 TheBicPen

Seems like this is the same issue I came to report, please let me know if I should make a separate one, but when piping with the -n flag (equivalent to —style numbers) I note that no numbers are passed.

(I think this is less a documentation issue and should rather affect the behaviour to be clear!)

lmmx avatar Jun 10 '25 16:06 lmmx

I believe https://github.com/sharkdp/bat/issues/2935 is related

keith-hall avatar Sep 27 '25 20:09 keith-hall

i've also recently encountered this issue of style options (--style=header-filename in my case) not being passed when piping to another program. while asking an LLM for workarounds it informed me that setting --color=always will preserve style options when piped - which works regardless of whether coloring is part of the style, apparently

Image

pharmacologic avatar Oct 15 '25 21:10 pharmacologic

Bat vivisection

Looked into it so as to be more precise about what we are discussing:

Click to show code walkthrough
  • src/controller.rs has the control over the printer that gets used, detecting based on whether it's being piped or not

https://github.com/sharkdp/bat/blob/a730eaae0a17de2c2fd8099471a49e7419db1b54/src/controller.rs#L179-L189

Though I note that we do actually have the config here so perhaps we could either

  • Use it with the SimplePrinter (I suspect so)
  • Switch to the InteractivePrinter based on the presence of style in the style_components field of the self.config (config::Config struct)

https://github.com/sharkdp/bat/blob/a730eaae0a17de2c2fd8099471a49e7419db1b54/src/config.rs#L66-L67

It then calls print file

https://github.com/sharkdp/bat/blob/a730eaae0a17de2c2fd8099471a49e7419db1b54/src/controller.rs#L191-L199

Which will call

self.print_file_ranges(printer, writer, &mut input.reader, &line_ranges)?;

https://github.com/sharkdp/bat/blob/a730eaae0a17de2c2fd8099471a49e7419db1b54/src/controller.rs#L201-L237

which is calling the print_line method on whichever Printer[-trait implenting struct] we have, in a loop:

https://github.com/sharkdp/bat/blob/a730eaae0a17de2c2fd8099471a49e7419db1b54/src/controller.rs#L298-L321

(Which means either the SimplePrinter or the InteractivePrinter)

The SimplePrinter writes to the handle (presumably STDOUT) all the time it's in the line range, but doesn't refer to the style components config field at all.

https://github.com/sharkdp/bat/blob/a730eaae0a17de2c2fd8099471a49e7419db1b54/src/printer.rs#L156-L168

The InteractivePrinter ...has a very long equivalent routine

https://github.com/sharkdp/bat/blob/a730eaae0a17de2c2fd8099471a49e7419db1b54/src/printer.rs#L604-L857

You can see the stye_components field crop up when it pushes a LineNumberDecoration onto a list of decorations

https://github.com/sharkdp/bat/blob/a730eaae0a17de2c2fd8099471a49e7419db1b54/src/printer.rs#L224-L226

My initial impression is that if this were to change it may surprise people if regular piping suddenly started decorating style.

But in my case I want it to, at least when I actively pass in a style like -n. Not sure if it'd be possible to distinguish someone passing in the default style vs. it being defaulted to.

Maybe we need:

  • a 3rd printer, neither Simple nor Interactive, but de-coloured?
  • an extra config flag, to record if the user explicitly set a flag?
    • As an initial approximation we could simply set this based on the presence of non-default/non-empty style components
      • ...in which case it doesn't need to be a new struct field at all
  • ❇️ something else... ❇️

Proposed solution?

If it'd be OK to gate on just plain(), then adding at line 179 in controller.rs:

             && self.config.style_components.plain() {

you'd get

louis 🌟 ~/dev/bat $ echo "Hello" | ./target/debug/bat | cat
─────┬──────────────────────────────────────────────────────────────────────────
     │ STDIN
─────┼──────────────────────────────────────────────────────────────────────────
   1 │ Hello
─────┴──────────────────────────────────────────────────────────────────────────
louis 🌟 ~/dev/bat $ echo "Hello" | ./target/debug/bat -p | cat
Hello

and

louis 🌟 ~/dev/bat $ echo "Hello" | ./target/debug/bat -n | cat
   1 Hello
louis 🌟 ~/dev/bat $ ./target/debug/bat Cargo.toml -r :4 | cat
─────┬──────────────────────────────────────────────────────────────────────────
     │ File: Cargo.toml
─────┼──────────────────────────────────────────────────────────────────────────
   1 │ [package]
   2 │ authors = ["David Peter <[email protected]>"]
   3 │ categories = ["command-line-utilities"]
   4 │ description = "A cat(1) clone with wings."
─────┴──────────────────────────────────────────────────────────────────────────

I should add that the colour is handled appropriately:

Image

You can pipe to clipboard and not get ANSI codes in the string 🥳

lmmx avatar Oct 16 '25 11:10 lmmx

Ah! I've just found there was an abandoned PR with the same discussion!

  • #2983

From the comment there it sounds like it in fact is the preference to retain the behaviour of plain as default when piping. I'd really like to be able to copy what I see on my terminal to clipboard, so I'd be pro-switching from "plain as default when piped" to "default style as default when piped". I guess it comes down to whether people have workflows/habits that depend on piping plainly

The penultimate comment on that PR seems to suggest that -n should be piped through

I think I've found a simpler approach. The following should do the trick:

diff --git a/src/bin/bat/app.rs b/src/bin/bat/app.rs
index 8f69870f..cf385c88 100644
--- a/src/bin/bat/app.rs
+++ b/src/bin/bat/app.rs
@@ -251,9 +251,10 @@ impl App {
             loop_through: !(self.interactive_output
                 || self.matches.get_one::<String>("color").map(|s| s.as_str()) == Some("always")
                 || self
                     .matches
                     .get_one::<String>("decorations")
                     .map(|s| s.as_str())
                     == Some("always")
-                || self.matches.get_flag("force-colorization")),
+                || self.matches.get_flag("force-colorization")
+                || self.matches.get_flag("number")),
             tab_width: self

That way, whenever -n/--number is specified the default InteractivePrinter is used instead of the SimplePrinter.

but that's not what's in the code at present

Moreover I think they have omitted the use of the --style flag which is meant to behave equivalently

You can basically see if someone has set the style to numbers by checking for the flag's presence AND the config being set for numbers, i.e. also this:

|| (self.matches.contains_id("style") && style_components.numbers())

In fact though that produces a bug where you end up piping the full style options (header, grid) only if you have numbers in that CSV style! So obviously what you actually want is to make all styles pipeable if set explicitly via --style (which would therefore leave the default behaviour alone)

You'd want to make an exception for when the style is set as plain (that should continue to use the simple printer)

|| (self.matches.contains_id("style") && !style_components.plain())

My interpretation of the docs here is that --style=default means it should pipe, and --style=auto should not

            * default: enables recommended style components (default).
            * full: enables all available components.
            * auto: same as 'default', unless the output is piped.

So there'd also be a switch on the style not being set to auto (but default would be OK)

It turns out that condition is actually sufficient, so auto must already set the style config to plain, it "just works"!

lmmx avatar Oct 16 '25 11:10 lmmx

i've also recently encountered this issue of style options (--style=header-filename in my case) not being passed when piping to another program. while asking an LLM for workarounds it informed me that setting --color=always will preserve style options when piped - which works regardless of whether coloring is part of the style, apparently Image

Now you no longer need the --color=always :-)

Image

lmmx avatar Oct 16 '25 13:10 lmmx