1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
//! TTY detection and capability utilities using Rust's `IsTerminal` trait.
//!
//! This module provides safe and convenient TTY detection across the codebase,
//! abstracting away platform differences for TTY detection.
//!
//! # Usage
//!
//! ```rust
//! use vtcode_core::utils::tty::TtyExt;
//! use std::io;
//!
//! // Check if stdout is a TTY
//! if io::stdout().is_tty_ext() {
//! // Apply terminal-specific features
//! }
//!
//! // Check if stdin is a TTY
//! if io::stdin().is_tty_ext() {
//! // Interactive input available
//! }
//! ```
use std::io;
use std::io::IsTerminal;
use vtcode_commons::color_policy::no_color_env_active;
/// Extension trait for TTY detection on standard I/O streams.
///
/// This trait extends `IsTerminal` to provide convenient methods
/// for checking TTY capabilities with better error handling.
pub trait TtyExt {
/// Returns `true` if this stream is connected to a terminal.
///
/// This is a convenience wrapper around Rust's `IsTerminal` trait
/// that provides consistent behavior across the codebase.
fn is_tty_ext(&self) -> bool;
/// Returns `true` if this stream supports ANSI color codes.
///
/// This checks both TTY status and common environment variables
/// that might disable color output.
fn supports_color(&self) -> bool;
/// Returns `true` if this stream supports interactive features.
///
/// Interactive features include cursor movement, color, and other
/// terminal capabilities that require a real terminal.
fn is_interactive(&self) -> bool;
}
impl TtyExt for io::Stdout {
fn is_tty_ext(&self) -> bool {
self.is_terminal()
}
fn supports_color(&self) -> bool {
if !self.is_terminal() {
return false;
}
// Check NO_COLOR with strict non-empty semantics.
if no_color_env_active() {
return false;
}
// Check for FORCE_COLOR environment variable
if std::env::var_os("FORCE_COLOR").is_some() {
return true;
}
true
}
fn is_interactive(&self) -> bool {
self.is_terminal() && self.supports_color()
}
}
impl TtyExt for io::Stderr {
fn is_tty_ext(&self) -> bool {
self.is_terminal()
}
fn supports_color(&self) -> bool {
if !self.is_terminal() {
return false;
}
// Check NO_COLOR with strict non-empty semantics.
if no_color_env_active() {
return false;
}
// Check for FORCE_COLOR environment variable
if std::env::var_os("FORCE_COLOR").is_some() {
return true;
}
true
}
fn is_interactive(&self) -> bool {
self.is_terminal() && self.supports_color()
}
}
impl TtyExt for io::Stdin {
fn is_tty_ext(&self) -> bool {
self.is_terminal()
}
fn supports_color(&self) -> bool {
// Stdin doesn't output color, but we check if it's interactive
self.is_terminal()
}
fn is_interactive(&self) -> bool {
self.is_terminal()
}
}
/// TTY capabilities that can be queried for feature detection.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TtyCapabilities {
/// Whether the terminal supports ANSI color codes.
pub color: bool,
/// Whether the terminal supports cursor movement and manipulation.
pub cursor: bool,
/// Whether the terminal supports bracketed paste mode.
pub bracketed_paste: bool,
/// Whether the terminal supports focus change events.
pub focus_events: bool,
/// Whether the terminal supports mouse input.
pub mouse: bool,
/// Whether the terminal supports keyboard enhancement flags.
pub keyboard_enhancement: bool,
}
impl TtyCapabilities {
/// Detect the capabilities of the current terminal.
///
/// This function queries the terminal to determine which features
/// are available. It should be called once at application startup
/// and the results cached for later use.
///
/// # Returns
///
/// Returns `Some(TtyCapabilities)` if stderr is a TTY, otherwise `None`.
pub fn detect() -> Option<Self> {
let stderr = io::stderr();
if !stderr.is_terminal() {
return None;
}
Some(Self {
color: stderr.supports_color(),
cursor: true, // All TTYs support basic cursor movement
bracketed_paste: true, // Assume support, will fail gracefully if not
focus_events: true, // Assume support, will fail gracefully if not
mouse: true, // Assume support, will fail gracefully if not
keyboard_enhancement: true, // Assume support, will fail gracefully if not
})
}
/// Returns `true` if the terminal supports all advanced features.
pub fn is_fully_featured(&self) -> bool {
self.color
&& self.cursor
&& self.bracketed_paste
&& self.focus_events
&& self.mouse
&& self.keyboard_enhancement
}
/// Returns `true` if the terminal supports basic TUI features.
pub fn is_basic_tui(&self) -> bool {
self.color && self.cursor
}
}
/// Check if the application is running in an interactive TTY context.
///
/// This is useful for deciding whether to use rich terminal features
/// or fall back to plain text output.
pub fn is_interactive_session() -> bool {
io::stderr().is_terminal() && io::stdin().is_terminal()
}
/// Get the current terminal dimensions.
///
/// Returns `Some((width, height))` if the terminal size can be determined,
/// otherwise `None`.
pub fn terminal_size() -> Option<(u16, u16)> {
terminal_size::terminal_size().map(|(terminal_size::Width(w), terminal_size::Height(h))| (w, h))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tty_detection() {
// These tests verify the TTY detection logic works
// Note: In actual test environments, these may vary
let stdout = io::stdout();
let stderr = io::stderr();
let stdin = io::stdin();
// Just verify the methods don't panic
let _ = stdout.is_terminal();
let _ = stderr.is_terminal();
let _ = stdin.is_terminal();
}
#[test]
fn test_capabilities_detection() {
// Test that capability detection doesn't panic
let caps = TtyCapabilities::detect();
// In a test environment, this might be None
// Just verify the method works
let _ = caps.is_some() || caps.is_none();
}
#[test]
fn test_interactive_session() {
// Test interactive session detection
let _ = is_interactive_session();
}
}