Files
ripgrep/src/atty.rs
Andrew Gallant e8a30cb893 Completely re-work colored output and tty handling.
This commit completely guts all of the color handling code and replaces
most of it with two new crates: wincolor and termcolor. wincolor
provides a simple API to coloring using the Windows console and
termcolor provides a platform independent coloring API tuned for
multithreaded command line programs. This required a lot more
flexibility than what the `term` crate provided, so it was dropped.
We instead switch to writing ANSI escape sequences directly and ignore
the TERMINFO database.

In addition to fixing several bugs, this commit also permits end users
to customize colors to a certain extent. For example, this command will
set the match color to magenta and the line number background to yellow:

    rg --colors 'match:fg:magenta' --colors 'line:bg:yellow' foo

For tty handling, we've adopted a hack from `git` to do tty detection in
MSYS/mintty terminals. As a result, ripgrep should get both color
detection and piping correct on Windows regardless of which terminal you
use.

Finally, switch to line buffering. Performance doesn't seem to be
impacted and it's an otherwise more user friendly option.

Fixes #37, Fixes #51, Fixes #94, Fixes #117, Fixes #182, Fixes #231
2016-11-20 11:14:52 -05:00

153 lines
4.5 KiB
Rust

/*!
This atty module contains functions for detecting whether ripgrep is being fed
from (or to) a terminal. Windows and Unix do this differently, so implement
both here.
*/
#[cfg(windows)]
use winapi::minwindef::DWORD;
#[cfg(windows)]
use winapi::winnt::HANDLE;
#[cfg(unix)]
pub fn stdin_is_readable() -> bool {
use std::fs::File;
use std::os::unix::fs::FileTypeExt;
use std::os::unix::io::{FromRawFd, IntoRawFd};
use libc;
let file = unsafe { File::from_raw_fd(libc::STDIN_FILENO) };
let md = file.metadata();
let _ = file.into_raw_fd();
let ft = match md {
Err(_) => return false,
Ok(md) => md.file_type(),
};
ft.is_file() || ft.is_fifo()
}
#[cfg(windows)]
pub fn stdin_is_readable() -> bool {
// ???
true
}
/// Returns true if there is a tty on stdin.
#[cfg(unix)]
pub fn on_stdin() -> bool {
use libc;
0 < unsafe { libc::isatty(libc::STDIN_FILENO) }
}
/// Returns true if there is a tty on stdout.
#[cfg(unix)]
pub fn on_stdout() -> bool {
use libc;
0 < unsafe { libc::isatty(libc::STDOUT_FILENO) }
}
/// Returns true if there is a tty on stdin.
#[cfg(windows)]
pub fn on_stdin() -> bool {
use kernel32::GetStdHandle;
use winapi::winbase::{
STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE,
};
unsafe {
let stdin = GetStdHandle(STD_INPUT_HANDLE);
if console_on_handle(stdin) {
// False positives aren't possible. If we got a console then
// we definitely have a tty on stdin.
return true;
}
// Otherwise, it's possible to get a false negative. If we know that
// there's a console on stdout or stderr however, then this is a true
// negative.
if console_on_fd(STD_OUTPUT_HANDLE)
|| console_on_fd(STD_ERROR_HANDLE) {
return false;
}
// Otherwise, we can't really tell, so we do a weird hack.
msys_tty_on_handle(stdin)
}
}
/// Returns true if there is a tty on stdout.
#[cfg(windows)]
pub fn on_stdout() -> bool {
use kernel32::GetStdHandle;
use winapi::winbase::{
STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE,
};
unsafe {
let stdout = GetStdHandle(STD_OUTPUT_HANDLE);
if console_on_handle(stdout) {
// False positives aren't possible. If we got a console then
// we definitely have a tty on stdout.
return true;
}
// Otherwise, it's possible to get a false negative. If we know that
// there's a console on stdin or stderr however, then this is a true
// negative.
if console_on_fd(STD_INPUT_HANDLE) || console_on_fd(STD_ERROR_HANDLE) {
return false;
}
// Otherwise, we can't really tell, so we do a weird hack.
msys_tty_on_handle(stdout)
}
}
/// Returns true if there is an MSYS tty on the given handle.
#[cfg(windows)]
fn msys_tty_on_handle(handle: HANDLE) -> bool {
use std::ffi::OsString;
use std::mem;
use std::os::raw::c_void;
use std::os::windows::ffi::OsStringExt;
use std::slice;
use kernel32::{GetFileInformationByHandleEx};
use winapi::fileapi::FILE_NAME_INFO;
use winapi::minwinbase::FileNameInfo;
use winapi::minwindef::MAX_PATH;
unsafe {
let size = mem::size_of::<FILE_NAME_INFO>();
let mut name_info_bytes = vec![0u8; size + MAX_PATH];
let res = GetFileInformationByHandleEx(
handle,
FileNameInfo,
&mut *name_info_bytes as *mut _ as *mut c_void,
name_info_bytes.len() as u32);
if res == 0 {
return true;
}
let name_info: FILE_NAME_INFO =
*(name_info_bytes[0..size].as_ptr() as *const FILE_NAME_INFO);
let name_bytes =
&name_info_bytes[size..size + name_info.FileNameLength as usize];
let name_u16 = slice::from_raw_parts(
name_bytes.as_ptr() as *const u16, name_bytes.len() / 2);
let name = OsString::from_wide(name_u16)
.as_os_str().to_string_lossy().into_owned();
name.contains("msys-") || name.contains("-pty")
}
}
/// Returns true if there is a console on the given file descriptor.
#[cfg(windows)]
unsafe fn console_on_fd(fd: DWORD) -> bool {
use kernel32::GetStdHandle;
console_on_handle(GetStdHandle(fd))
}
/// Returns true if there is a console on the given handle.
#[cfg(windows)]
fn console_on_handle(handle: HANDLE) -> bool {
use kernel32::GetConsoleMode;
let mut out = 0;
unsafe { GetConsoleMode(handle, &mut out) != 0 }
}