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}