Repository: sharkdp/bat (language: rust)

Issue:
`PrettyPrinter::print` hangs due to stdin lock even if stdin is actually not used
<!-- Hey there, thank you for creating an issue! -->

**Describe the bug you encountered:**

Hello, I'm trying to use bat as library and seeing an issue.

Let's say we have the following very simple program which reads paths from stdin and show the file contents with bat.

```rust
fn main() {
    use bat::{Input, PrettyPrinter};
    use std::io::BufRead;

    for line in io::stdin().lock().lines() {
        let line = line.unwrap();
        let input = Input::from_file(&line).name(&line).kind("File");
        PrettyPrinter::new().input(input).print().unwrap();
    }
}
```

Run this command for example:

```sh
fd some-query | cargo run
```

It hangs on calling `.print()` method. This is because bat takes stdin lock at

https://github.com/sharkdp/bat/blob/6fc7ebf37a63645d9d19e71ca252f67f7074a2e9/src/controller.rs#L90

even if it does not use stdin actually.

Replacing `io::stdin().lock()` with `io::stdin()` in the program can avoid this issue, but reading stdin with taking lock on each `read` is fairly slower than taking the lock once.

**What did you expect to happen instead?**

Shows each file's content where the file paths are passed via stdin.

**How did you install `bat`?**

```toml
[dependencies]
bat = { version = "^0.18.3", default-features = false, features = ["regex-onig"] }
```

in my `Cargo.toml`.

---

**bat version and environment**

<!--
In order to reproduce your issue, please add some information about the environment
in which you're running bat. To do this, run the full `bat` command that demonstrates
the bug, and attach the `--diagnostic` option:

    bat [other options and arguments…] --diagnostic

Finally, paste the Markdown output here. Please make sure that it does not reveal any
personal information.


If you are running bat 0.17.1 or older (where --diagnostic is not available), please
run the script at

    https://github.com/sharkdp/bat/blob/master/diagnostics/info.sh

(click "Raw" to get the actual source code) and paste the Markdown output here. If you
are on Windows, please let us know your bat version and your Windows version.
-->

- bat: v0.18.3
- OS: macOS 10.15



Discussion / hints:

Fix patch:
diff --git a/src/controller.rs b/src/controller.rs
index a737f09b5a..25d88b284a 100644
--- a/src/controller.rs
+++ b/src/controller.rs
@@ -1,4 +1,4 @@
-use std::io::{self, Write};
+use std::io::{self, BufRead, Write};
 
 use crate::assets::HighlightingAssets;
 use crate::config::{Config, VisibleLines};
@@ -14,7 +14,7 @@ use crate::output::OutputType;
 use crate::paging::PagingMode;
 use crate::printer::{InteractivePrinter, Printer, SimplePrinter};
 
-use clircle::Clircle;
+use clircle::{Clircle, Identifier};
 
 pub struct Controller<'a> {
     config: &'a Config<'a>,
@@ -76,83 +76,89 @@ impl<'b> Controller<'b> {
 
         let writer = output_type.handle()?;
         let mut no_errors: bool = true;
-
         let stderr = io::stderr();
-        let print_error = |error: &Error, write: &mut dyn Write| {
-            if attached_to_pager {
-                handle_error(error, write);
-            } else {
-                handle_error(error, &mut stderr.lock());
-            }
-        };
 
         for (index, input) in inputs.into_iter().enumerate() {
-            match input.open(io::stdin().lock(), stdout_identifier.as_ref()) {
-                Err(error) => {
-                    print_error(&error, writer);
-                    no_errors = false;
+            let identifier = stdout_identifier.as_ref();
+            let is_first = index == 0;
+            let result = if input.is_stdin() {
+                self.print_input(input, writer, io::stdin().lock(), identifier, is_first)
+            } else {
+                // Use dummy stdin since stdin is actually not used (#1902)
+                self.print_input(input, writer, io::empty(), identifier, is_first)
+            };
+            if let Err(error) = result {
+                if attached_to_pager {
+                    handle_error(&error, writer);
+                } else {
+                    handle_error(&error, &mut stderr.lock());
                 }
-                Ok(mut opened_input) => {
-                    #[cfg(feature = "git")]
-                    let line_changes = if self.config.visible_lines.diff_mode()
-                        || (!self.config.loop_through && self.config.style_components.changes())
-                    {
-                        match opened_input.kind {
-                            crate::input::OpenedInputKind::OrdinaryFile(ref path) => {
-                                let diff = get_git_diff(path);
-
-                                // Skip files without Git modifications
-                                if self.config.visible_lines.diff_mode()
-                                    && diff
-                                        .as_ref()
-                                        .map(|changes| changes.is_empty())
-                                        .unwrap_or(false)
-                                {
-                                    continue;
-                                }
-
-                                diff
-                            }
-                            _ if self.config.visible_lines.diff_mode() => {
-                                // Skip non-file inputs in diff mode
-                                continue;
-                            }
-                            _ => None,
-                        }
-                    } else {
-                        None
-                    };
+                no_errors = false;
+            }
+        }
 
-                    let mut printer: Box<dyn Printer> = if self.config.loop_through {
-                        Box::new(SimplePrinter::new(self.config))
-                    } else {
-                        Box::new(InteractivePrinter::new(
-                            self.config,
-                            self.assets,
-                            &mut opened_input,
-                            #[cfg(feature = "git")]
-                            &line_changes,
-                        )?)
-                    };
-
-                    let result = self.print_file(
-                        &mut *printer,
-                        writer,
-                        &mut opened_input,
-                        index != 0,
-                        #[cfg(feature = "git")]
-                        &line_changes,
-                    );
-
-                    if let Err(error) = result {
-                        print_error(&error, writer);
-                        no_errors = false;
+        Ok(no_errors)
+    }
+
+    fn print_input<R: BufRead>(
+        &self,
+        input: Input,
+        writer: &mut dyn Write,
+        stdin: R,
+        stdout_identifier: Option<&Identifier>,
+        is_first: bool,
+    ) -> Result<()> {
+        let mut opened_input = input.open(stdin, stdout_identifier)?;
+        #[cfg(feature = "git")]
+        let line_changes = if self.config.visible_lines.diff_mode()
+            || (!self.config.loop_through && self.config.style_components.changes())
+        {
+            match opened_input.kind {
+                crate::input::OpenedInputKind::OrdinaryFile(ref path) => {
+                    let diff = get_git_diff(path);
+
+                    // Skip files without Git modifications
+                    if self.config.visible_lines.diff_mode()
+                        && diff
+                            .as_ref()
+                            .map(|changes| changes.is_empty())
+                            .unwrap_or(false)
+                    {
+                        return Ok(());
                     }
+
+                    diff
+                }
+                _ if self.config.visible_lines.diff_mode() => {
+                    // Skip non-file inputs in diff mode
+                    return Ok(());
                 }
+                _ => None,
             }
-        }
+        } else {
+            None
+        };
 
-        Ok(no_errors)
+        let mut printer: Box<dyn Printer> = if self.config.loop_through {
+            Box::new(SimplePrinter::new(self.config))
+        } else {
+            Box::new(InteractivePrinter::new(
+                self.config,
+                self.assets,
+                &mut opened_input,
+                #[cfg(feature = "git")]
+                &line_changes,
+            )?)
+        };
+
+        self.print_file(
+            &mut *printer,
+            writer,
+            &mut opened_input,
+            !is_first,
+            #[cfg(feature = "git")]
+            &line_changes,
+        )
     }
 
     fn print_file(


Test patch: