├── .gitignore ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── docs.yml │ ├── release.yml │ └── ci.yml ├── rustfmt.toml ├── .cargo └── config.toml ├── release.toml ├── tests └── basic.rs ├── Cargo.toml ├── LICENSE ├── CHANGELOG.md ├── README.md └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: sunshowers 2 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" 2 | style_edition = "2024" 3 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | xfmt = "fmt -- --config imports_granularity=Crate" 3 | -------------------------------------------------------------------------------- /release.toml: -------------------------------------------------------------------------------- 1 | sign-tag = true 2 | # Required for templates below to work 3 | consolidate-commits = false 4 | pre-release-commit-message = "[{{crate_name}}] version {{version}}" 5 | tag-message = "[{{crate_name}}] version {{version}}" 6 | tag-name = "{{version}}" 7 | publish = false 8 | dependent-version = "upgrade" 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Reference: 2 | # https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "cargo" 7 | directory: "/" 8 | schedule: 9 | interval: "daily" 10 | allow: 11 | - dependency-type: all 12 | -------------------------------------------------------------------------------- /tests/basic.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) The enable-ansi-support Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | #[test] 5 | fn test_basic() { 6 | let res = enable_ansi_support::enable_ansi_support(); 7 | println!("enable_ansi_support status: {:?}", res); 8 | if res.is_ok() { 9 | println!("\x1b[31mHello, world\x1b[0m"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "enable-ansi-support" 3 | description = "Enable ANSI escape code support on Windows 10" 4 | version = "0.3.1" 5 | edition = "2021" 6 | license = "MIT" 7 | readme = "README.md" 8 | repository = "https://github.com/sunshowers-code/enable-ansi-support" 9 | rust-version = "1.71" 10 | keywords = ["ansi", "windows", "console", "terminal", "color"] 11 | categories = ["command-line-interface", "os::windows-apis"] 12 | 13 | [target.'cfg(windows)'.dependencies] 14 | windows-sys = { version = "0.61.0", features = [ 15 | "Win32_Foundation", 16 | "Win32_Storage_FileSystem", 17 | "Win32_Security", 18 | "Win32_System_Console", 19 | ] } 20 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | 6 | name: Docs 7 | 8 | jobs: 9 | docs: 10 | name: Build and deploy documentation 11 | concurrency: ci-${{ github.ref }} 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: dtolnay/rust-toolchain@stable 16 | - uses: Swatinem/rust-cache@v2 17 | - name: Build rustdoc 18 | run: cargo doc --all-features 19 | - name: Organize 20 | run: | 21 | rm -rf target/gh-pages 22 | mkdir target/gh-pages 23 | mv target/doc target/gh-pages/rustdoc 24 | touch target/gh-pages/.nojekyll 25 | - name: Deploy 26 | uses: JamesIves/github-pages-deploy-action@releases/v4 27 | with: 28 | branch: gh-pages 29 | folder: target/gh-pages 30 | single-commit: true 31 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # adapted from https://github.com/taiki-e/cargo-hack/blob/main/.github/workflows/release.yml 2 | 3 | name: crates.io release 4 | on: 5 | push: 6 | tags: 7 | - '*' 8 | 9 | jobs: 10 | create-release: 11 | if: github.repository_owner == 'sunshowers-code' 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | with: 16 | persist-credentials: false 17 | - name: Install Rust 18 | uses: dtolnay/rust-toolchain@stable 19 | - run: cargo publish 20 | env: 21 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 22 | - uses: taiki-e/create-gh-release-action@v1 23 | with: 24 | changelog: CHANGELOG.md 25 | title: enable-ansi-support $version 26 | branch: main 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | pull_request: 6 | branches: 7 | - main 8 | 9 | name: CI 10 | 11 | jobs: 12 | lint: 13 | name: Lint 14 | # Typically one would use ubuntu-latest for clippy and rustfmt jobs. Use windows-latest in this 15 | # case because most of the code is Windows-specific. 16 | runs-on: windows-latest 17 | env: 18 | RUSTFLAGS: -D warnings 19 | steps: 20 | - uses: actions/checkout@v3 21 | - uses: dtolnay/rust-toolchain@stable 22 | with: 23 | components: rustfmt, clippy 24 | - uses: Swatinem/rust-cache@v2 25 | - name: Lint (rustfmt) 26 | run: cargo xfmt --check 27 | - name: Lint (clippy) 28 | run: cargo clippy --all-features --all-targets 29 | 30 | build: 31 | name: Build and test 32 | runs-on: ${{ matrix.os }} 33 | strategy: 34 | matrix: 35 | os: [ windows-latest, ubuntu-latest ] 36 | # 1.71 is the MSRV 37 | rust-version: [ 1.71, stable ] 38 | fail-fast: false 39 | env: 40 | RUSTFLAGS: -D warnings 41 | steps: 42 | - uses: actions/checkout@v3 43 | - uses: dtolnay/rust-toolchain@master 44 | with: 45 | toolchain: ${{ matrix.rust-version }} 46 | components: rustfmt, clippy 47 | - uses: Swatinem/rust-cache@v2 48 | - name: Build 49 | run: cargo build 50 | - name: Test 51 | run: cargo test --all-features 52 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | This project adheres to [Semantic Versioning](https://semver.org). 6 | 7 | ## [0.3.1] - 2025-10-02 8 | 9 | - Bump Rust edition to 2021. 10 | 11 | ## [0.3.0] - 2025-10-02 12 | 13 | - `windows-sys` dependency updated to version `0.61.0`. 14 | - MSRV updated to Rust 1.71. 15 | 16 | ## [0.2.1] - 2022-12-10 17 | 18 | ### Changed 19 | 20 | - `enable_ansi_support` now returns `std::io::Error` rather than the raw Win32 error code. 21 | - Internal implementation now uses the windows-sys crate. 22 | - MSRV updated to Rust 1.49, and new MSRV policy defined: updates will be considered a breaking 23 | change. 24 | 25 | ## [0.2.0] - 2022-12-10 26 | 27 | This release encountered a publishing issue. 28 | 29 | ## [0.1.2] - 2022-01-19 30 | 31 | - Update links to new repository location. 32 | 33 | ## [0.1.1] - 2021-12-06 34 | 35 | - Add example to readme -- thanks [@jam1garner](https://github.com/jam1garner)! 36 | 37 | ## [0.1.0] - 2021-12-03 38 | 39 | - Initial release. 40 | 41 | [0.3.1]: https://github.com/sunshowers-code/enable-ansi-support/releases/tag/0.3.1 42 | [0.3.0]: https://github.com/sunshowers-code/enable-ansi-support/releases/tag/0.3.0 43 | [0.2.1]: https://github.com/sunshowers-code/enable-ansi-support/releases/tag/0.2.1 44 | [0.2.0]: https://github.com/sunshowers-code/enable-ansi-support/releases/tag/0.2.0 45 | [0.1.2]: https://github.com/sunshowers-code/enable-ansi-support/releases/tag/0.1.2 46 | [0.1.1]: https://github.com/sunshowers-code/enable-ansi-support/releases/tag/0.1.1 47 | [0.1.0]: https://github.com/sunshowers-code/enable-ansi-support/releases/tag/0.1.0 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # enable-ansi-support: Enable ANSI escape code support on Windows 10 2 | 3 | [![enable-ansi-support on crates.io](https://img.shields.io/crates/v/enable-ansi-support)](https://crates.io/crates/enable-ansi-support) 4 | [![Documentation (latest release)](https://img.shields.io/badge/docs-latest%20release-brightgreen.svg)](https://docs.rs/enable-ansi-support/) 5 | [![Documentation (main)](https://img.shields.io/badge/documentation-main-purple.svg)](https://sunshowers-code.github.io/enable-ansi-support/rustdoc/enable_ansi_support/) 6 | [![License](https://img.shields.io/badge/license-Apache-green.svg)](LICENSE-APACHE) 7 | [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE) 8 | 9 | ## About 10 | 11 | This crate provides one function, `enable_ansi_support`, which allows ANSI escape codes to work on Windows 10 and above. 12 | Call `enable_ansi_support` *once*, early on in `main()`, to enable ANSI escape codes generated by crates like 13 | [`ansi_term`](https://docs.rs/ansi_term) or [`owo-colors`](https://docs.rs/owo-colors) to work on Windows just like they 14 | do on Unix platforms. 15 | 16 | On non-Windows platforms, `enable_ansi_support` is a no-op. 17 | 18 | ## Example 19 | 20 | ```rust 21 | fn main() { 22 | match enable_ansi_support::enable_ansi_support() { 23 | Ok(()) => { 24 | // ANSI escape codes were successfully enabled, or this is a non-Windows platform. 25 | // Use your terminal color library of choice here. 26 | println!("\x1b[31mHello, world\x1b[0m"); 27 | } 28 | Err(_) => { 29 | // The operation was unsuccessful, typically because it's running on an older 30 | // version of Windows. The program may choose to disable ANSI color code output in 31 | // this case. 32 | } 33 | } 34 | } 35 | ``` 36 | 37 | ## Minimum supported Rust version 38 | 39 | The minimum supported Rust version (MSRV) is **1.71**. The MSRV will be updated sparingly. 40 | 41 | ## License and credits 42 | 43 | This project is available under the terms of the [MIT license](LICENSE). It is a derivative of `ansi_term`'s 44 | [`enable_ansi_support`](https://github.com/ogham/rust-ansi-term/blob/master/src/windows.rs) 45 | (as of [this snapshot](https://github.com/ogham/rust-ansi-term/blob/ff7eba98d55ad609c7fcc8c7bb0859b37c7545cc/src/windows.rs)) 46 | with minor modifications. The upstream code is used under the terms of the MIT license: 47 | 48 | > Copyright (c) 2014 Benjamin Sago 49 | > 50 | > Permission is hereby granted, free of charge, to any person obtaining a copy 51 | > of this software and associated documentation files (the "Software"), to deal 52 | > in the Software without restriction, including without limitation the rights 53 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 54 | > copies of the Software, and to permit persons to whom the Software is 55 | > furnished to do so, subject to the following conditions: 56 | > 57 | > The above copyright notice and this permission notice shall be included in all 58 | > copies or substantial portions of the Software. 59 | > 60 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 61 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 62 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 63 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 64 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 65 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 66 | > SOFTWARE. 67 | -------------------------------------------------------------------------------- /src/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)] 56 | pub 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::{ENABLE_VIRTUAL_TERMINAL_PROCESSING, GetConsoleMode, SetConsoleMode}, 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 = 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 | std::ptr::null_mut(), 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] 119 | pub fn enable_ansi_support() -> Result<(), std::io::Error> { 120 | Ok(()) 121 | } 122 | --------------------------------------------------------------------------------