enable_ansi_support/
lib.rs

1// Copyright (c) The enable-ansi-support Contributors
2// SPDX-License-Identifier: MIT
3
4//! Enable ANSI code support on Windows 10 and above.
5//!
6//! This crate provides one function, `enable_ansi_support`, which allows [ANSI escape codes]
7//! to work on Windows 10 and above.
8//!
9//! Call `enable_ansi_support` *once*, early on in `main()`, to enable ANSI escape codes generated
10//! by crates like
11//! [`ansi_term`](https://docs.rs/ansi_term) or [`owo-colors`](https://docs.rs/owo-colors)
12//! to work on Windows just like they do on Unix platforms.
13//!
14//! ## Examples
15//!
16//! ```rust
17//! fn main() {
18//!     match enable_ansi_support::enable_ansi_support() {
19//!         Ok(()) => {
20//!             // ANSI escape codes were successfully enabled, or this is a non-Windows platform.
21//!             println!("\x1b[31mHello, world\x1b[0m");
22//!         }
23//!         Err(_) => {
24//!             // The operation was unsuccessful, typically because it's running on an older
25//!             // version of Windows. The program may choose to disable ANSI color code output in
26//!             // this case.
27//!         }
28//!     }
29//!
30//!     // Use your terminal color library of choice here.
31//! }
32//! ```
33//!
34//! ## How it works
35//!
36//! `enable_ansi_support` uses Windows API calls to alter the properties of the console that
37//! the program is running in. See the
38//! [Windows documentation](https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences)
39//! for more information.
40//!
41//! On non-Windows platforms, `enable_ansi_support` is a no-op.
42//!
43//! [ANSI escape codes]: https://en.wikipedia.org/wiki/ANSI_escape_code
44#![allow(clippy::needless_doctest_main)]
45
46/// Enables ANSI code support on Windows 10.
47///
48/// Returns an [`io::Error`](std::io::Error) with the Windows error code in it if unsuccessful.
49///
50/// On non-Windows platforms, this is a no-op that always returns `Ok(())`.
51///
52/// # Examples
53///
54/// See the [crate documentation](crate).
55#[cfg(windows)]
56pub fn enable_ansi_support() -> Result<(), std::io::Error> {
57    // ref: https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#EXAMPLE_OF_ENABLING_VIRTUAL_TERMINAL_PROCESSING @@ https://archive.is/L7wRJ#76%
58
59    use std::{ffi::OsStr, iter::once, os::windows::ffi::OsStrExt};
60
61    use windows_sys::Win32::{
62        Foundation::INVALID_HANDLE_VALUE,
63        Storage::FileSystem::{
64            CreateFileW, FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_SHARE_WRITE, OPEN_EXISTING,
65        },
66        System::Console::{GetConsoleMode, SetConsoleMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING},
67    };
68
69    unsafe {
70        // ref: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
71        // Using `CreateFileW("CONOUT$", ...)` to retrieve the console handle works correctly even if STDOUT and/or STDERR are redirected
72        let console_out_name: Vec<u16> =
73            OsStr::new("CONOUT$").encode_wide().chain(once(0)).collect();
74        let console_handle = CreateFileW(
75            console_out_name.as_ptr(),
76            FILE_GENERIC_READ | FILE_GENERIC_WRITE,
77            FILE_SHARE_WRITE,
78            std::ptr::null(),
79            OPEN_EXISTING,
80            0,
81            0,
82        );
83        if console_handle == INVALID_HANDLE_VALUE {
84            return Err(std::io::Error::last_os_error());
85        }
86
87        // ref: https://docs.microsoft.com/en-us/windows/console/getconsolemode
88        let mut console_mode = 0;
89        if 0 == GetConsoleMode(console_handle, &mut console_mode) {
90            return Err(std::io::Error::last_os_error());
91        }
92
93        // VT processing not already enabled?
94        if console_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 {
95            // https://docs.microsoft.com/en-us/windows/console/setconsolemode
96            if 0 == SetConsoleMode(
97                console_handle,
98                console_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING,
99            ) {
100                return Err(std::io::Error::last_os_error());
101            }
102        }
103    }
104
105    Ok(())
106}
107
108/// Enables ANSI code support on Windows 10.
109///
110/// Returns an [`io::Error`](std::io::Error) with the Windows error code in it if unsuccessful.
111///
112/// On non-Windows platforms, this is a no-op that always returns `Ok(())`.
113///
114/// # Examples
115///
116/// See the [crate documentation](crate).
117#[cfg(not(windows))]
118#[inline]
119pub fn enable_ansi_support() -> Result<(), std::io::Error> {
120    Ok(())
121}