From af28d096302e0d4ac67b655887e1561d80a1542f Mon Sep 17 00:00:00 2001 From: AAAA Date: Mon, 6 Sep 2021 14:24:39 +0000 Subject: [PATCH] term: obtain the cursor position via termios.h (#11372) --- vlib/os/os.c.v | 12 ++++ vlib/readline/readline.v | 12 ---- vlib/readline/readline_default.c.v | 4 +- vlib/readline/readline_linux.c.v | 64 +++++++++++++----- vlib/readline/readline_test.v | 2 +- vlib/readline/readline_windows.c.v | 4 ++ vlib/term/declarations_default.c.v | 10 +++ vlib/term/declarations_linux.c.v | 11 ++++ vlib/term/term_nix.c.v | 94 ++++++++++++--------------- vlib/term/term_test.v | 10 +-- vlib/term/term_windows.c.v | 4 +- vlib/term/ui/color.v | 7 +- vlib/term/ui/declarations_default.c.v | 10 +++ vlib/term/ui/declarations_linux.c.v | 11 ++++ vlib/term/ui/input_nix.c.v | 4 +- vlib/term/ui/input_windows.c.v | 10 +-- vlib/term/ui/termios_nix.c.v | 29 +++------ vlib/term/ui/ui.v | 7 +- vlib/term/ui/ui_test.v | 8 +++ 19 files changed, 190 insertions(+), 123 deletions(-) create mode 100644 vlib/term/declarations_default.c.v create mode 100644 vlib/term/declarations_linux.c.v create mode 100644 vlib/term/ui/declarations_default.c.v create mode 100644 vlib/term/ui/declarations_linux.c.v create mode 100644 vlib/term/ui/ui_test.v diff --git a/vlib/os/os.c.v b/vlib/os/os.c.v index 12369d07ee..8dccd86a17 100644 --- a/vlib/os/os.c.v +++ b/vlib/os/os.c.v @@ -1008,3 +1008,15 @@ pub fn glob(patterns ...string) ?[]string { matches.sort() return matches } + +pub fn last_error() IError { + $if windows { + code := int(C.GetLastError()) + msg := get_error_msg(code) + return error_with_code(msg, code) + } $else { + code := C.errno + msg := posix_get_error_msg(code) + return error_with_code(msg, code) + } +} diff --git a/vlib/readline/readline.v b/vlib/readline/readline.v index 4f0ebf7c40..ae41ddcf0a 100644 --- a/vlib/readline/readline.v +++ b/vlib/readline/readline.v @@ -7,18 +7,6 @@ // module readline -// Termios stores the terminal options on Linux. -struct C.termios {} - -struct Termios { -mut: - c_iflag int - c_oflag int - c_cflag int - c_lflag int - c_cc [12]int // NCCS == 12. Can't use the defined value here -} - // Winsize stores the screen information on Linux. struct Winsize { ws_row u16 diff --git a/vlib/readline/readline_default.c.v b/vlib/readline/readline_default.c.v index fcbbbb6eab..4d2107c8d5 100644 --- a/vlib/readline/readline_default.c.v +++ b/vlib/readline/readline_default.c.v @@ -10,7 +10,9 @@ module readline import os -#include +struct Termios { +} + // Only use standard os.get_line // Need implementation for readline capabilities // diff --git a/vlib/readline/readline_linux.c.v b/vlib/readline/readline_linux.c.v index eb0fda9f90..01e947befa 100644 --- a/vlib/readline/readline_linux.c.v +++ b/vlib/readline/readline_linux.c.v @@ -13,9 +13,32 @@ import os #include #include +const cclen = 10 + +// Termios stores the terminal options on Linux. +struct C.termios { +mut: + c_iflag int + c_oflag int + c_cflag int + c_lflag int + c_line byte + c_cc [cclen]int +} + +struct Termios { +mut: + c_iflag u32 + c_oflag u32 + c_cflag u32 + c_lflag u32 + c_line byte + c_cc [cclen]int +} + fn C.tcgetattr(fd int, termios_p &C.termios) int -fn C.tcsetattr(fd int, optional_actions int, termios_p &C.termios) int +fn C.tcsetattr(fd int, optional_actions int, const_termios_p &C.termios) int fn C.raise(sig int) @@ -47,18 +70,22 @@ enum Action { // Please note that `enable_raw_mode` catches the `SIGUSER` (CTRL + C) signal. // For a method that does please see `enable_raw_mode_nosig`. pub fn (mut r Readline) enable_raw_mode() { - if C.tcgetattr(0, unsafe { &C.termios(&r.orig_termios) }) == -1 { + if unsafe { C.tcgetattr(0, &C.termios(&r.orig_termios)) } != 0 { r.is_tty = false r.is_raw = false return } - mut raw := r.orig_termios + mut raw := C.termios{} + unsafe { vmemcpy(&raw, &r.orig_termios, int(sizeof(raw))) } + // println('> r.orig_termios: $r.orig_termios') + // println('> raw: $raw') raw.c_iflag &= ~(C.BRKINT | C.ICRNL | C.INPCK | C.ISTRIP | C.IXON) raw.c_cflag |= C.CS8 raw.c_lflag &= ~(C.ECHO | C.ICANON | C.IEXTEN | C.ISIG) - raw.c_cc[C.VMIN] = 1 - raw.c_cc[C.VTIME] = 0 - C.tcsetattr(0, C.TCSADRAIN, unsafe { &C.termios(&raw) }) + raw.c_cc[C.VMIN] = byte(1) + raw.c_cc[C.VTIME] = byte(0) + unsafe { C.tcsetattr(0, C.TCSADRAIN, &raw) } + // println('> after raw: $raw') r.is_raw = true r.is_tty = true } @@ -68,18 +95,19 @@ pub fn (mut r Readline) enable_raw_mode() { // Please note that `enable_raw_mode_nosig` does not catch the `SIGUSER` (CTRL + C) signal // as opposed to `enable_raw_mode`. pub fn (mut r Readline) enable_raw_mode_nosig() { - if C.tcgetattr(0, unsafe { &C.termios(&r.orig_termios) }) == -1 { + if unsafe { C.tcgetattr(0, &C.termios(&r.orig_termios)) } != 0 { r.is_tty = false r.is_raw = false return } - mut raw := r.orig_termios + mut raw := C.termios{} + unsafe { vmemcpy(&raw, &r.orig_termios, int(sizeof(raw))) } raw.c_iflag &= ~(C.BRKINT | C.ICRNL | C.INPCK | C.ISTRIP | C.IXON) raw.c_cflag |= C.CS8 raw.c_lflag &= ~(C.ECHO | C.ICANON | C.IEXTEN) - raw.c_cc[C.VMIN] = 1 - raw.c_cc[C.VTIME] = 0 - C.tcsetattr(0, C.TCSADRAIN, unsafe { &C.termios(&raw) }) + raw.c_cc[C.VMIN] = byte(1) + raw.c_cc[C.VTIME] = byte(0) + unsafe { C.tcsetattr(0, C.TCSADRAIN, &raw) } r.is_raw = true r.is_tty = true } @@ -88,7 +116,7 @@ pub fn (mut r Readline) enable_raw_mode_nosig() { // For a description of raw mode please see the `enable_raw_mode` method. pub fn (mut r Readline) disable_raw_mode() { if r.is_raw { - C.tcsetattr(0, C.TCSADRAIN, unsafe { &C.termios(&r.orig_termios) }) + unsafe { C.tcsetattr(0, C.TCSADRAIN, &C.termios(&r.orig_termios)) } r.is_raw = false } } @@ -122,7 +150,7 @@ pub fn (mut r Readline) read_line_utf8(prompt string) ?[]rune { } print(r.prompt) for { - C.fflush(C.stdout) + unsafe { C.fflush(C.stdout) } c := r.read_char() a := r.analyse(c) if r.execute(a, c) { @@ -311,7 +339,7 @@ fn (mut r Readline) execute(a Action, c int) bool { // get_screen_columns returns the number of columns (`width`) in the terminal. fn get_screen_columns() int { ws := Winsize{} - cols := if C.ioctl(1, C.TIOCGWINSZ, &ws) == -1 { 80 } else { int(ws.ws_col) } + cols := if unsafe { C.ioctl(1, C.TIOCGWINSZ, &ws) } == -1 { 80 } else { int(ws.ws_col) } return cols } @@ -542,10 +570,12 @@ fn (mut r Readline) suspend() { r.disable_raw_mode() if !is_standalone { // We have to SIGSTOP the parent v process - ppid := C.getppid() - C.kill(ppid, C.SIGSTOP) + unsafe { + ppid := C.getppid() + C.kill(ppid, C.SIGSTOP) + } } - C.raise(C.SIGSTOP) + unsafe { C.raise(C.SIGSTOP) } r.enable_raw_mode() r.refresh_line() if r.is_tty { diff --git a/vlib/readline/readline_test.v b/vlib/readline/readline_test.v index ab5154f38d..a3613e85af 100644 --- a/vlib/readline/readline_test.v +++ b/vlib/readline/readline_test.v @@ -5,7 +5,7 @@ fn no_lines(s string) string { } fn test_struct_readline() { - // mut rl := Readline{} + // mut rl := readline.Readline{} // eprintln('rl: $rl') // line := rl.read_line('Please, enter your name: ') or { panic(err) } // eprintln('line: $line') diff --git a/vlib/readline/readline_windows.c.v b/vlib/readline/readline_windows.c.v index 887efdb912..bcd28f3fc9 100644 --- a/vlib/readline/readline_windows.c.v +++ b/vlib/readline/readline_windows.c.v @@ -10,6 +10,10 @@ module readline import os +// needed for parity with readline_default.c.v +struct Termios { +} + // Only use standard os.get_line // Need implementation for readline capabilities // diff --git a/vlib/term/declarations_default.c.v b/vlib/term/declarations_default.c.v new file mode 100644 index 0000000000..ab55adf25f --- /dev/null +++ b/vlib/term/declarations_default.c.v @@ -0,0 +1,10 @@ +module term + +pub struct C.termios { +mut: + c_iflag int + c_oflag int + c_cflag int + c_lflag int + c_cc [10]int +} diff --git a/vlib/term/declarations_linux.c.v b/vlib/term/declarations_linux.c.v new file mode 100644 index 0000000000..20464d40f9 --- /dev/null +++ b/vlib/term/declarations_linux.c.v @@ -0,0 +1,11 @@ +module term + +pub struct C.termios { +mut: + c_iflag int + c_oflag int + c_cflag int + c_lflag int + c_line byte + c_cc [10]int +} diff --git a/vlib/term/term_nix.c.v b/vlib/term/term_nix.c.v index 45a0a9b841..c0c37c8b5a 100644 --- a/vlib/term/term_nix.c.v +++ b/vlib/term/term_nix.c.v @@ -13,6 +13,10 @@ pub: ws_ypixel u16 } +fn C.tcgetattr(fd int, ptr &C.termios) int + +fn C.tcsetattr(fd int, action int, const_ptr &C.termios) + fn C.ioctl(fd int, request u64, arg voidptr) int // get_terminal_size returns a number of colums and rows of terminal window. @@ -21,70 +25,58 @@ pub fn get_terminal_size() (int, int) { return default_columns_size, default_rows_size } w := C.winsize{} - C.ioctl(1, u64(C.TIOCGWINSZ), &w) + unsafe { C.ioctl(1, u64(C.TIOCGWINSZ), &w) } 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 { +pub fn get_cursor_position() ?Coord { if os.is_atty(1) <= 0 || os.getenv('TERM') == 'dumb' { - return Coord{ - x: 0 - y: 0 - } + return Coord{0, 0} } - // TODO: use termios.h, C.tcgetattr & C.tcsetattr directly, - // instead of using `stty` - mut oldsettings := os.execute('stty -g') - if oldsettings.exit_code < 0 { - oldsettings = os.Result{} + + old_state := C.termios{} + if unsafe { C.tcgetattr(0, &old_state) } != 0 { + return os.last_error() } - 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 + defer { + // restore the old terminal state: + unsafe { C.tcsetattr(0, C.TCSANOW, &old_state) } + } + + mut state := C.termios{} + if unsafe { C.tcgetattr(0, &state) } != 0 { + return os.last_error() + } + state.c_lflag &= int(~(u32(C.ICANON) | u32(C.ECHO))) + unsafe { C.tcsetattr(0, C.TCSANOW, &state) } + + print('\e[6n') + mut x := 0 mut y := 0 + mut stage := byte(0) + + // ESC [ YYY `;` XXX `R` + for { - ch = C.getchar() - b := byte(ch) - i++ - if i >= 15 { - panic('C.getchar() called too many times') - } - // state management: - if b == `R` { + w := unsafe { C.getchar() } + if w < 0 { + return error_with_code('Failed to read from stdin', 888) + } else if w == `[` || w == `;` { + stage++ + } else if `0` <= w && w <= `9` { + match stage { + // converting string values to int: + 1 { y = y * 10 + int(w - `0`) } + 2 { x = x * 10 + int(w - `0`) } + else {} + } + } else if w == `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 } + return Coord{x, y} } // set_terminal_title change the terminal title diff --git a/vlib/term/term_test.v b/vlib/term/term_test.v index 00f9293402..114b302cf7 100644 --- a/vlib/term/term_test.v +++ b/vlib/term/term_test.v @@ -57,9 +57,9 @@ fn test_header() { 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() +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 // @@ -67,13 +67,13 @@ fn test_get_cursor_position() { x: 10 y: 11 ) - cursor_position_2 := term.get_cursor_position() + cursor_position_2 := term.get_cursor_position() ? // term.set_cursor_position( x: 5 y: 6 ) - cursor_position_3 := term.get_cursor_position() + cursor_position_3 := term.get_cursor_position() ? // term.set_cursor_position(original_position) eprintln('original_position: $original_position') diff --git a/vlib/term/term_windows.c.v b/vlib/term/term_windows.c.v index 1d2f0329fb..db55b62f08 100644 --- a/vlib/term/term_windows.c.v +++ b/vlib/term/term_windows.c.v @@ -69,13 +69,15 @@ pub fn get_terminal_size() (int, int) { } // get_cursor_position returns a Coord containing the current cursor position -pub fn get_cursor_position() Coord { +pub fn get_cursor_position() ?Coord { mut res := Coord{} if os.is_atty(1) > 0 && os.getenv('TERM') != 'dumb' { info := C.CONSOLE_SCREEN_BUFFER_INFO{} if C.GetConsoleScreenBufferInfo(C.GetStdHandle(C.STD_OUTPUT_HANDLE), &info) { res.x = info.dwCursorPosition.X res.y = info.dwCursorPosition.Y + } else { + return os.last_error() } } return res diff --git a/vlib/term/ui/color.v b/vlib/term/ui/color.v index 3e0a0bbf11..0e31e77cfe 100644 --- a/vlib/term/ui/color.v +++ b/vlib/term/ui/color.v @@ -4,10 +4,9 @@ module ui -const ( - value_range = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]! - color_table = init_color_table() -) +const value_range = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]! + +pub const color_table = init_color_table() [direct_array_access] fn init_color_table() []int { diff --git a/vlib/term/ui/declarations_default.c.v b/vlib/term/ui/declarations_default.c.v new file mode 100644 index 0000000000..27c1053c11 --- /dev/null +++ b/vlib/term/ui/declarations_default.c.v @@ -0,0 +1,10 @@ +module ui + +struct C.termios { +mut: + c_iflag int + c_oflag int + c_cflag int + c_lflag int + c_cc [10]int +} diff --git a/vlib/term/ui/declarations_linux.c.v b/vlib/term/ui/declarations_linux.c.v new file mode 100644 index 0000000000..43ec7aa409 --- /dev/null +++ b/vlib/term/ui/declarations_linux.c.v @@ -0,0 +1,11 @@ +module ui + +struct C.termios { +mut: + c_iflag int + c_oflag int + c_cflag int + c_lflag int + c_line byte + c_cc [10]int +} diff --git a/vlib/term/ui/input_nix.c.v b/vlib/term/ui/input_nix.c.v index e806fb8c1a..d11f3b4bd8 100644 --- a/vlib/term/ui/input_nix.c.v +++ b/vlib/term/ui/input_nix.c.v @@ -8,9 +8,7 @@ mut: read_buf []byte } -const ( - ctx_ptr = &Context(0) -) +const ctx_ptr = &Context(0) pub fn init(cfg Config) &Context { mut ctx := &Context{ diff --git a/vlib/term/ui/input_windows.c.v b/vlib/term/ui/input_windows.c.v index bd9782d150..6f08f2a934 100644 --- a/vlib/term/ui/input_windows.c.v +++ b/vlib/term/ui/input_windows.c.v @@ -6,11 +6,11 @@ module ui import os import time -const ( - buf_size = 64 - ctx_ptr = &Context(0) - stdin_at_startup = u32(0) -) +const buf_size = 64 + +const ctx_ptr = &Context(0) + +const stdin_at_startup = u32(0) struct ExtraContext { mut: diff --git a/vlib/term/ui/termios_nix.c.v b/vlib/term/ui/termios_nix.c.v index fb5ff76b25..9bbaed3308 100644 --- a/vlib/term/ui/termios_nix.c.v +++ b/vlib/term/ui/termios_nix.c.v @@ -10,27 +10,18 @@ import time #include #include -fn C.tcgetattr(fd int, termios_p &C.termios) int - -fn C.tcsetattr(fd int, optional_actions int, termios_p &C.termios) int - -fn C.ioctl(fd int, request u64, arg voidptr) int - -struct C.termios { -mut: - c_iflag u32 - c_lflag u32 - c_cc [32]byte -} - struct C.winsize { ws_row u16 ws_col u16 } -const ( - termios_at_startup = get_termios() -) +fn C.tcgetattr(fd int, termios_p &C.termios) int + +fn C.tcsetattr(fd int, optional_actions int, const_termios_p &C.termios) int + +fn C.ioctl(fd int, request u64, arg voidptr) int + +const termios_at_startup = get_termios() [inline] fn get_termios() C.termios { @@ -74,11 +65,11 @@ fn (mut ctx Context) termios_setup() ? { if ctx.cfg.capture_events { // Set raw input mode by unsetting ICANON and ECHO, // as well as disable e.g. ctrl+c and ctrl.z - termios.c_iflag &= ~u32(C.IGNBRK | C.BRKINT | C.PARMRK | C.IXON) - termios.c_lflag &= ~u32(C.ICANON | C.ISIG | C.ECHO | C.IEXTEN | C.TOSTOP) + termios.c_iflag &= ~(C.IGNBRK | C.BRKINT | C.PARMRK | C.IXON) + termios.c_lflag &= ~(C.ICANON | C.ISIG | C.ECHO | C.IEXTEN | C.TOSTOP) } else { // Set raw input mode by unsetting ICANON and ECHO - termios.c_lflag &= ~u32(C.ICANON | C.ECHO) + termios.c_lflag &= ~(C.ICANON | C.ECHO) } if ctx.cfg.hide_cursor { diff --git a/vlib/term/ui/ui.v b/vlib/term/ui/ui.v index 6ba3d7c357..78e43eb522 100644 --- a/vlib/term/ui/ui.v +++ b/vlib/term/ui/ui.v @@ -18,10 +18,9 @@ pub fn (c Color) hex() string { // Synchronized Updates spec, designed to avoid tearing during renders // https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec -const ( - bsu = '\x1bP=1s\x1b\\' - esu = '\x1bP=2s\x1b\\' -) +const bsu = '\x1bP=1s\x1b\\' + +const esu = '\x1bP=2s\x1b\\' // write puts the string `s` into the print buffer. [inline] diff --git a/vlib/term/ui/ui_test.v b/vlib/term/ui/ui_test.v new file mode 100644 index 0000000000..5817bf8a65 --- /dev/null +++ b/vlib/term/ui/ui_test.v @@ -0,0 +1,8 @@ +import term.ui + +// This test just ensures that programs importing term.ui can compile + +fn test_a_simple_term_ui_program_can_be_compiled() { + println(ui.color_table) + assert true +}