Skip to main content

nu_command/platform/
is_terminal.rs

1use nu_engine::command_prelude::*;
2use nu_protocol::OutDest;
3use std::io::IsTerminal as _;
4
5#[derive(Clone)]
6pub struct IsTerminal;
7
8impl Command for IsTerminal {
9    fn name(&self) -> &str {
10        "is-terminal"
11    }
12
13    fn signature(&self) -> Signature {
14        Signature::build("is-terminal")
15            .input_output_type(Type::Nothing, Type::Bool)
16            .switch("stdin", "Check if stdin is a terminal.", Some('i'))
17            .switch("stdout", "Check if stdout is a terminal.", Some('o'))
18            .switch("stderr", "Check if stderr is a terminal.", Some('e'))
19            .category(Category::Platform)
20    }
21
22    fn description(&self) -> &str {
23        "Check if stdin, stdout, or stderr is a terminal."
24    }
25
26    fn examples(&self) -> Vec<Example<'_>> {
27        vec![
28            Example {
29                description: "Check if stdout is a terminal (default when no flag is specified).",
30                example: "is-terminal",
31                result: None,
32            },
33            Example {
34                description: "Return false when output is piped to another command.",
35                example: "is-terminal | to text",
36                result: Some(Value::test_string("false")),
37            },
38            Example {
39                description: "Return false when output is collected into a variable.",
40                example: "let x = (is-terminal); $x",
41                result: Some(Value::test_bool(false)),
42            },
43            Example {
44                description: r#"Return "terminal attached" if standard input is attached to a terminal, and "no terminal" if not."#,
45                example: r#"if (is-terminal --stdin) { "terminal attached" } else { "no terminal" }"#,
46                result: Some(Value::test_string("terminal attached")),
47            },
48        ]
49    }
50
51    fn search_terms(&self) -> Vec<&str> {
52        vec!["input", "output", "stdin", "stdout", "stderr", "tty"]
53    }
54
55    fn run(
56        &self,
57        engine_state: &EngineState,
58        stack: &mut Stack,
59        call: &Call,
60        _input: PipelineData,
61    ) -> Result<PipelineData, ShellError> {
62        let stdin = call.has_flag(engine_state, stack, "stdin")?;
63        let stdout = call.has_flag(engine_state, stack, "stdout")?;
64        let stderr = call.has_flag(engine_state, stack, "stderr")?;
65
66        let is_terminal = match (stdin, stdout, stderr) {
67            (true, false, false) => std::io::stdin().is_terminal(),
68            (false, true, false) => stream_is_terminal(stack, Stream::Stdout),
69            (false, false, true) => stream_is_terminal(stack, Stream::Stderr),
70            (false, false, false) => stream_is_terminal(stack, Stream::Stdout),
71            _ => {
72                return Err(ShellError::IncompatibleParametersSingle {
73                    msg: "Only one stream may be checked".into(),
74                    span: call.arguments_span(),
75                });
76            }
77        };
78
79        Ok(PipelineData::value(
80            Value::bool(is_terminal, call.head),
81            None,
82        ))
83    }
84}
85
86enum Stream {
87    Stdout,
88    Stderr,
89}
90
91fn stream_is_terminal(stack: &Stack, stream: Stream) -> bool {
92    let pipe_dest = match stream {
93        Stream::Stdout => stack.pipe_stdout(),
94        Stream::Stderr => stack.pipe_stderr(),
95    };
96
97    match pipe_dest {
98        Some(
99            OutDest::Pipe
100            | OutDest::PipeSeparate
101            | OutDest::Value
102            | OutDest::Null
103            | OutDest::File(_)
104            | OutDest::Inherit,
105        ) => false,
106        Some(OutDest::Print) | None => match stream {
107            Stream::Stdout => std::io::stdout().is_terminal(),
108            Stream::Stderr => std::io::stderr().is_terminal(),
109        },
110    }
111}