From 3f7970db52386700402101324a4b1806abc7dcb9 Mon Sep 17 00:00:00 2001 From: bpryer Date: Tue, 8 Sep 2020 14:00:10 -0500 Subject: [PATCH] term: add get_cursor_position and set_terminal_title (#6279) * added functions added: - get_cursor_position() - set_terminal_title(title string) * implement term.get_cursor_position and term.set_terminal_title on unix * Cleanup * make x,y fields of term.Coord mutable * fix vrepl compilation * use more descriptive var names in term_test.v * do not change the current terminal title in dumb terminals; do not test term.set_terminal_title outside of CI * unix: in term.set_terminal_title, return true even for dumb terminals Co-authored-by: Brent Pryer Co-authored-by: Delyan Angelov --- examples/terminal_control.v | 4 +- vlib/readline/readline_linux.c.v | 2 +- vlib/term/README.md | 6 +-- vlib/term/control.v | 4 +- vlib/term/term.v | 8 ++++ vlib/term/term_nix.c.v | 74 ++++++++++++++++++++++++++++++-- vlib/term/term_test.v | 49 ++++++++++++++++++++- vlib/term/term_windows.c.v | 34 +++++++++++++-- 8 files changed, 163 insertions(+), 18 deletions(-) diff --git a/examples/terminal_control.v b/examples/terminal_control.v index c8ef29cedf..bb5c943d25 100644 --- a/examples/terminal_control.v +++ b/examples/terminal_control.v @@ -14,7 +14,7 @@ fn main() { fn sleeping_line(x,y,size int, ch string) { mut i := 0 for i < size { - term.set_cursor_position(x+i,y) + term.set_cursor_position(x: x+i, y: y) print(term.bold(term.yellow(ch))) i++ } @@ -23,7 +23,7 @@ fn sleeping_line(x,y,size int, ch string) { fn standing_line(x,y,size int, ch string) { mut i := 0 for i < size { - term.set_cursor_position(x,y+i) + term.set_cursor_position(x: x, y: y+i) print(term.bold(term.yellow(ch))) i++ } diff --git a/vlib/readline/readline_linux.c.v b/vlib/readline/readline_linux.c.v index de0fd34ab1..cc80a3efe2 100644 --- a/vlib/readline/readline_linux.c.v +++ b/vlib/readline/readline_linux.c.v @@ -451,7 +451,7 @@ fn (mut r Readline) switch_overwrite() { } fn (mut r Readline) clear_screen() { - term.set_cursor_position(1, 1) + term.set_cursor_position(x:1, y:1) term.erase_clear() r.refresh_line() } diff --git a/vlib/term/README.md b/vlib/term/README.md index b2626372d3..3f3ab5b0b7 100644 --- a/vlib/term/README.md +++ b/vlib/term/README.md @@ -15,9 +15,9 @@ import os fn main() { term.clear() // clears the content in the terminal width, height := term.get_terminal_size() // get the size of the terminal - term.set_cursor_position(width / 2, height / 2) // now we point the cursor to the middle of the terminal + term.set_cursor_position(x: width / 2, y: height / 2) // now we point the cursor to the middle of the terminal println(term.strikethrough(term.bright_green("hello world"))) // Print green text - term.set_cursor_position(0, height) // Sets the position of the cursor to the bottom of the terminal + term.set_cursor_position(x: 0, y: height) // Sets the position of the cursor to the bottom of the terminal mut var := os.input('press q to quit: ') // Keep prompting until the user presses the q key for { @@ -68,7 +68,7 @@ term.underline(string) term.bg_(string) // sets the position of the cursor at a given place in the terminal -term.set_cursor_position(x,y) +term.set_cursor_position(term.Coord) // moves the cursor up term.cursor_up() diff --git a/vlib/term/control.v b/vlib/term/control.v index 422a968eba..d1f1a25676 100644 --- a/vlib/term/control.v +++ b/vlib/term/control.v @@ -13,8 +13,8 @@ module term // Setting cursor to the given position // x is the x coordinate // y is the y coordinate -pub fn set_cursor_position(x int, y int) { - print('\x1b[$y;$x' + 'H') +pub fn set_cursor_position(c Coord) { + print('\x1b[$c.y;$c.x' + 'H') } // n is number of cells diff --git a/vlib/term/term.v b/vlib/term/term.v index 0ce55af54a..ed776a319a 100644 --- a/vlib/term/term.v +++ b/vlib/term/term.v @@ -6,6 +6,14 @@ const ( default_columns_size = 80 default_rows_size = 25 ) + +// Coord - used by term.get_cursor_position and term.set_cursor_position +pub struct Coord { +pub mut: + x int + y int +} + // can_show_color_on_stdout returns true if colors are allowed in stdout; // returns false otherwise. pub fn can_show_color_on_stdout() bool { diff --git a/vlib/term/term_nix.c.v b/vlib/term/term_nix.c.v index 2d955a52e2..75f11c7de2 100644 --- a/vlib/term/term_nix.c.v +++ b/vlib/term/term_nix.c.v @@ -3,9 +3,7 @@ module term import os #include - #include // TIOCGWINSZ - pub struct C.winsize { pub: ws_row u16 @@ -17,11 +15,79 @@ pub: fn C.ioctl(fd int, request u64, arg voidptr) int // get_terminal_size returns a number of colums and rows of terminal window. -pub fn get_terminal_size() (int,int) { +pub fn get_terminal_size() (int, int) { if is_atty(1) <= 0 || os.getenv('TERM') == 'dumb' { return default_columns_size, default_rows_size } w := C.winsize{} C.ioctl(1, C.TIOCGWINSZ, &w) - return int(w.ws_col),int(w.ws_row) + return int(w.ws_col), int(w.ws_row) +} + +// get_cursor_position returns a Coord containing the current cursor position +pub fn get_cursor_position() Coord { + if is_atty(1) <= 0 || os.getenv('TERM') == 'dumb' { + return Coord{ + x: 0 + y: 0 + } + } + // TODO: use termios.h, C.tcgetattr & C.tcsetattr directly, + // instead of using `stty` + oldsettings := os.exec('stty -g') or { + os.Result{} + } + os.system('stty -echo -icanon time 0') + print('\033[6n') + mut ch := int(0) + mut i := 0 + // ESC [ YYY `;` XXX `R` + mut reading_x := false + mut reading_y := false + mut x := 0 + mut y := 0 + for { + ch = C.getchar() + b := byte(ch) + i++ + assert i < 15 + // state management: + if b == `R` { + break + } + if b == `[` { + reading_y = true + reading_x = false + continue + } + if b == `;` { + reading_y = false + reading_x = true + continue + } + // converting string vals to ints: + if reading_x { + x *= 10 + x += (b - byte(`0`)) + } + if reading_y { + y *= 10 + y += (b - byte(`0`)) + } + } + // restore the old terminal settings: + os.system('stty $oldsettings.output') + return Coord{ + x: x + y: y + } +} + +// set_terminal_title change the terminal title +pub fn set_terminal_title(title string) bool { + if is_atty(1) <= 0 || os.getenv('TERM') == 'dumb' { + return true + } + print('\033]0;${title}\007') + return true } diff --git a/vlib/term/term_test.v b/vlib/term/term_test.v index 70a005c765..e26b6cbec5 100644 --- a/vlib/term/term_test.v +++ b/vlib/term/term_test.v @@ -1,7 +1,8 @@ +import os import term fn test_get_terminal_size() { - cols,_ := term.get_terminal_size() + cols, _ := term.get_terminal_size() assert cols > 0 } @@ -48,10 +49,54 @@ fn test_header() { eprintln(term.header('123', '_-/\\\/')) eprintln(term.header('1234', '_-/\\/\\')) eprintln(term.header('', '-')) - */ + */ assert term_width == empty_header.len assert term_width == short_header.len assert term_width == very_long_header.len assert term_width == very_long_header_2.len assert term_width == term.header('1234', '_-/\\/\\').len } + +fn test_get_cursor_position() { + original_position := term.get_cursor_position() + cursor_position_1 := term.get_cursor_position() + assert original_position.x == cursor_position_1.x + assert original_position.y == cursor_position_1.y + // + term.set_cursor_position({ + x: 10 + y: 11 + }) + cursor_position_2 := term.get_cursor_position() + // + term.set_cursor_position({ + x: 5 + y: 6 + }) + cursor_position_3 := term.get_cursor_position() + // + term.set_cursor_position(original_position) + eprintln('original_position: $original_position') + eprintln('cursor_position_2: $cursor_position_2') + eprintln('cursor_position_3: $cursor_position_3') + // 0,0 is returned on dumb terminals + if cursor_position_2.x == 0 && cursor_position_2.y == 0 { + return + } + if cursor_position_3.x == 0 && cursor_position_3.y == 0 { + return + } + assert cursor_position_2.x == 10 + assert cursor_position_2.y == 11 + assert cursor_position_3.x == 5 + assert cursor_position_3.y == 6 +} + +fn test_set_terminal_title() { + // do not change the current terminal title outside of CI: + if os.getenv('CI') != 'true' { + return + } + title_change := term.set_terminal_title('v is awesome!') + assert title_change == true +} diff --git a/vlib/term/term_windows.c.v b/vlib/term/term_windows.c.v index 08226d57b8..2b531e4af9 100644 --- a/vlib/term/term_windows.c.v +++ b/vlib/term/term_windows.c.v @@ -2,7 +2,8 @@ module term import os -struct Coord { +pub struct Coord16 { +pub: x i16 y i16 } @@ -14,16 +15,22 @@ struct SmallRect { bottom i16 } +// win: CONSOLE_SCREEN_BUFFER_INFO +// https://docs.microsoft.com/en-us/windows/console/console-screen-buffer-info-str struct ConsoleScreenBufferInfo { - dw_size Coord - dw_cursor_position Coord + dw_size Coord16 + dw_cursor_position Coord16 w_attributes u16 sr_window SmallRect - dw_maximum_window_size Coord + dw_maximum_window_size Coord16 } +// ref - https://docs.microsoft.com/en-us/windows/console/getconsolescreenbufferinfo fn C.GetConsoleScreenBufferInfo(handle os.HANDLE, info &ConsoleScreenBufferInfo) bool +// ref - https://docs.microsoft.com/en-us/windows/console/setconsoletitle +fn C.SetConsoleTitle(title &u16) bool + // get_terminal_size returns a number of colums and rows of terminal window. pub fn get_terminal_size() (int, int) { if is_atty(1) > 0 && os.getenv('TERM') != 'dumb' { @@ -36,3 +43,22 @@ pub fn get_terminal_size() (int, int) { } return default_columns_size, default_rows_size } + +// get_cursor_position returns a Coord containing the current cursor position +pub fn get_cursor_position() Coord { + mut res := Coord{} + if is_atty(1) > 0 && os.getenv('TERM') != 'dumb' { + info := ConsoleScreenBufferInfo{} + if C.GetConsoleScreenBufferInfo(C.GetStdHandle(C.STD_OUTPUT_HANDLE), &info) { + res.x = info.dw_cursor_position.x + res.y = info.dw_cursor_position.y + } + } + return res +} + +// set_terminal_title change the terminal title +pub fn set_terminal_title(title string) bool { + title_change := C.SetConsoleTitle(title.to_wide()) + return title_change +}