Skip to content

Commit

Permalink
new: added functions to get terminal size (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
sttk committed Mar 14, 2024
1 parent 23edf77 commit 2855ffc
Show file tree
Hide file tree
Showing 9 changed files with 290 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rustver }}
- run: cargo test
- run: cargo test -- --show-output

cover:
name: Coverage for Rust ${{ matrix.rustver }} on ${{ matrix.os }}
Expand Down
12 changes: 11 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,14 @@ categories = ["text-processing"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
icu = "1.4.0"
icu = "1.4"

[target.'cfg(unix)'.dependencies]
libc = "0.2"

[target.'cfg(windows)'.dependencies.windows]
version = "0.53"
features = [
"Win32_Foundation",
"Win32_System_Console",
]
2 changes: 1 addition & 1 deletion build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ doc() {
}

if [[ "$#" == "0" ]]; then
clean
#clean
format
compile
test
Expand Down
34 changes: 34 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,41 @@
mod char_buffer;
mod line_iter;
mod linebreak;
mod terminal;
mod unicode;

pub use line_iter::LineIter;
pub use terminal::Size;
pub use unicode::{char_width, is_print, text_width};

/// Returns the column number of the current terminal.
///
/// If failing to retrieve the column number, this function returns the
/// tentative value `80`.
/// This is because this crate would be used on character output terminals,
/// and errors occure only in special circumstances such as during CI
/// execution.
/// In such circumstances, it is assumed that returning a tentative value would
/// be beneficial than returning an error.
pub fn term_cols() -> u16 {
match terminal::term_cols() {
Ok(cols) => cols,
Err(_) => 80,
}
}

/// Returns the size of the current terminal.
///
/// If failing to retrieve the column number, this function returns the
/// tentative size `{ col: 80, row: 24 }`.
/// This is because this crate would be used on character output terminals,
/// and errors occure only in special circumstances such as during CI
/// execution.
/// In such circumstances, it is assumed that returning a tentative value would
/// be beneficial than returning an error.
pub fn term_size() -> Size {
match terminal::term_size() {
Ok(size) => size,
Err(_) => Size { col: 80, row: 24 },
}
}
103 changes: 103 additions & 0 deletions src/terminal/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Copyright (C) 2024 Takayuki Sato. All Rights Reserved.
// This program is free software under MIT License.
// See the file LICENSE in this distribution for more details.

/// `Size` is the struct for storing the size of the current terminal.
#[derive(Debug)]
pub struct Size {
/// The column number of the terminal.
pub col: u16,
/// The row number of the terminal.
pub row: u16,
}

#[cfg(unix)]
mod unix;
#[cfg(unix)]
pub use self::unix::{term_cols, term_size};

#[cfg(windows)]
mod windows;
#[cfg(windows)]
pub use self::windows::{term_cols, term_size};

#[cfg(not(any(unix, windows)))]
mod unknown;
#[cfg(not(any(unix, windows)))]
pub use self::unknown::{term_cols, term_size};

#[cfg(test)]
mod test_of_term_cols {
use super::*;

#[cfg(unix)]
#[test]
fn test_get_terminal_cols() {
match term_cols() {
Ok(c) => println!("term cols = {}", c),
Err(e) => {
println!("term cols error = {}", e.to_string());
assert_eq!(e.raw_os_error().unwrap(), 25); // NOTTY
}
}
}

#[cfg(windows)]
#[test]
fn test_get_terminal_cols() {
match term_cols() {
Ok(c) => println!("term cols = {}", c),
Err(e) => {
println!("term cols error = {}", e.to_string());
assert_eq!(e.raw_os_error().unwrap() & 0xffff, 6); // Invalid Handler
}
}
}

#[cfg(not(any(unix, windows)))]
#[test]
fn test_get_terminal_cols() {
match term_cols() {
Ok(cols) => assert!(false),
Err(e) => assert_eq!(e.kind(), ErrorKind::Unsupported),
}
}
}

#[cfg(test)]
mod test_of_term_size {
use super::*;

#[cfg(unix)]
#[test]
fn test_get_terminal_size() {
match term_size() {
Ok(sz) => println!("term size = {} x {}", sz.col, sz.row),
Err(e) => {
println!("term size error = {}", e.to_string());
assert_eq!(e.raw_os_error().unwrap(), 25); // NOTTY
}
}
}

#[cfg(windows)]
#[test]
fn test_get_terminal_size() {
match term_size() {
Ok(sz) => println!("term size = {} x {}", sz.col, sz.row),
Err(e) => {
println!("term size error = {}", e.to_string());
assert_eq!(e.raw_os_error().unwrap() & 0xffff, 6); // Invalid Handler
}
}
}

#[cfg(not(any(unix, windows)))]
#[test]
fn test_get_terminal_cols() {
match term_cols() {
Ok(cols) => assert!(false),
Err(e) => assert_eq!(e.kind(), ErrorKind::Unsupported),
}
}
}
38 changes: 38 additions & 0 deletions src/terminal/unix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (C) 2024 Takayuki Sato. All Rights Reserved.
// This program is free software under MIT License.
// See the file LICENSE in this distribution for more details.

use super::Size;
use libc::{ioctl, winsize, STDOUT_FILENO, TIOCGWINSZ};
use std::io;

pub fn term_cols() -> Result<u16, io::Error> {
let mut ws = winsize {
ws_row: 0,
ws_col: 0,
ws_xpixel: 0,
ws_ypixel: 0,
};
let r = unsafe { ioctl(STDOUT_FILENO, TIOCGWINSZ, &mut ws) };
match r {
0 => Ok(ws.ws_col as u16),
_ => Err(io::Error::last_os_error()),
}
}

pub fn term_size() -> Result<Size, io::Error> {
let mut ws = winsize {
ws_row: 0,
ws_col: 0,
ws_xpixel: 0,
ws_ypixel: 0,
};
let r = unsafe { ioctl(STDOUT_FILENO, TIOCGWINSZ, &mut ws) };
match r {
0 => Ok(Size {
col: ws.ws_col as u16,
row: ws.ws_row as u16,
}),
_ => Err(io::Error::last_os_error()),
}
}
14 changes: 14 additions & 0 deletions src/terminal/unknown.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (C) 2024 Takayuki Sato. All Rights Reserved.
// This program is free software under MIT License.
// See the file LICENSE in this distribution for more details.

use super::Size;
use std::io;

pub fn term_cols() -> Result<u16, io::Error> {
Error::new(ErrorKind::Unsupported)
}

pub fn term_size() -> Result<u16, io::Error> {
Error::new(ErrorKind::Unsupported)
}
64 changes: 64 additions & 0 deletions src/terminal/windows.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright (C) 2024 Takayuki Sato. All Rights Reserved.
// This program is free software under MIT License.
// See the file LICENSE in this distribution for more details.

use super::Size;
use std::io;

use windows::Win32::System::Console::{
GetConsoleScreenBufferInfo, GetStdHandle, CONSOLE_CHARACTER_ATTRIBUTES,
CONSOLE_SCREEN_BUFFER_INFO, COORD, SMALL_RECT, STD_OUTPUT_HANDLE,
};

pub fn term_cols() -> Result<u16, io::Error> {
let mut bi = CONSOLE_SCREEN_BUFFER_INFO {
dwSize: COORD { X: 0, Y: 0 },
dwCursorPosition: COORD { X: 0, Y: 0 },
wAttributes: CONSOLE_CHARACTER_ATTRIBUTES(0),
srWindow: SMALL_RECT {
Left: 0,
Top: 0,
Right: 0,
Bottom: 0,
},
dwMaximumWindowSize: COORD { X: 0, Y: 0 },
};

let h = match unsafe { GetStdHandle(STD_OUTPUT_HANDLE) } {
Ok(h) => h,
Err(e) => return Err(io::Error::from_raw_os_error(e.code().0)),
};

match unsafe { GetConsoleScreenBufferInfo(h, &mut bi) } {
Ok(_) => Ok((bi.srWindow.Right - bi.srWindow.Left + 1) as u16),
Err(e) => Err(io::Error::from_raw_os_error(e.code().0)),
}
}

pub fn term_size() -> Result<Size, io::Error> {
let mut bi = CONSOLE_SCREEN_BUFFER_INFO {
dwSize: COORD { X: 0, Y: 0 },
dwCursorPosition: COORD { X: 0, Y: 0 },
wAttributes: CONSOLE_CHARACTER_ATTRIBUTES(0),
srWindow: SMALL_RECT {
Left: 0,
Top: 0,
Right: 0,
Bottom: 0,
},
dwMaximumWindowSize: COORD { X: 0, Y: 0 },
};

let h = match unsafe { GetStdHandle(STD_OUTPUT_HANDLE) } {
Ok(h) => h,
Err(e) => return Err(io::Error::from_raw_os_error(e.code().0)),
};

match unsafe { GetConsoleScreenBufferInfo(h, &mut bi) } {
Ok(_) => Ok(Size {
col: (bi.srWindow.Right - bi.srWindow.Left + 1) as u16,
row: (bi.srWindow.Bottom - bi.srWindow.Top + 1) as u16,
}),
Err(e) => Err(io::Error::from_raw_os_error(e.code().0)),
}
}
24 changes: 24 additions & 0 deletions tests/terminal_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use linebreak::{term_cols, term_size};
use std::env;

#[test]
fn it_should_get_terminal_column_number() {
let cols = term_cols();
if env::var("CI").is_err() {
assert!(cols > 0);
} else {
assert_eq!(cols, 80);
}
}

#[test]
fn it_should_get_terminal_size() {
let size = term_size();
if env::var("CI").is_err() {
assert!(size.col > 0);
assert!(size.row > 0);
} else {
assert_eq!(size.col, 80);
assert_eq!(size.row, 24);
}
}

0 comments on commit 2855ffc

Please sign in to comment.