diff --git a/.github/workflows/linux_ci.yml b/.github/workflows/linux_ci.yml index 4adb0ef99b..40f7f8020e 100644 --- a/.github/workflows/linux_ci.yml +++ b/.github/workflows/linux_ci.yml @@ -99,6 +99,10 @@ jobs: run: | ./v examples/password ./v examples/password/password_ci.vsh + - name: Test readline + run: | + ./v examples/readline/readline_ci.v + ./v examples/readline/readline.vsh ubuntu-tcc-boehm-gc: diff --git a/.github/workflows/macos_ci.yml b/.github/workflows/macos_ci.yml index 9961db76e8..8ac8261238 100644 --- a/.github/workflows/macos_ci.yml +++ b/.github/workflows/macos_ci.yml @@ -113,3 +113,7 @@ jobs: run: | ./v examples/password ./v examples/password/password_ci.vsh + - name: Test readline + run: | + ./v examples/readline/readline_ci.v + ./v examples/readline/readline.vsh diff --git a/examples/readline/correct.expect b/examples/readline/correct.expect new file mode 100755 index 0000000000..2381e8406b --- /dev/null +++ b/examples/readline/correct.expect @@ -0,0 +1,9 @@ +#!/usr/bin/expect +spawn ./readline_ci +send "a" +expect "got 97" +send "1" +expect "got 49" +send "q" +expect "Goodbye." +expect eof diff --git a/examples/readline/readline.vsh b/examples/readline/readline.vsh new file mode 100644 index 0000000000..8368bcd995 --- /dev/null +++ b/examples/readline/readline.vsh @@ -0,0 +1,2 @@ +chdir('examples/readline')! +assert execute('./correct.expect').exit_code == 0 diff --git a/examples/readline/readline_ci.v b/examples/readline/readline_ci.v new file mode 100644 index 0000000000..76ab34f5ea --- /dev/null +++ b/examples/readline/readline_ci.v @@ -0,0 +1,29 @@ +module main + +import readline + +fn main() { + run() or { panic('${err}') } +} + +fn run() ! { + $if windows { + eprintln('skipping test on windows for now') + return + } $else { + mut r := readline.Readline{} + r.enable_raw_mode_nosig() + defer { + r.disable_raw_mode() + } + + for { + entered := r.read_char()! + if entered == `q` { + break + } + println('got ${entered}') + } + println('Goodbye.') + } +} diff --git a/vlib/os/password_nix.c.v b/vlib/os/password_nix.c.v index acd46cf7f7..84c14d2974 100644 --- a/vlib/os/password_nix.c.v +++ b/vlib/os/password_nix.c.v @@ -1,19 +1,6 @@ module os -#include - -pub struct C.termios { -mut: - c_iflag int - c_oflag int - c_cflag int - c_lflag int - c_cc [20]u8 -} - -fn C.tcgetattr(fd int, ptr &C.termios) int - -fn C.tcsetattr(fd int, action int, const_ptr &C.termios) int +import term.termios // input_password prompts the user for a password-like secret. It disables // the terminal echo during user input and resets it back to normal when done. @@ -22,19 +9,19 @@ pub fn input_password(prompt string) !string { return error('Could not obtain password discretely.') } - old_state := C.termios{} - if unsafe { C.tcgetattr(0, &old_state) } != 0 { + mut old_state := termios.Termios{} + if termios.tcgetattr(0, mut old_state) != 0 { return last_error() } defer { - unsafe { C.tcsetattr(0, C.TCSANOW, &old_state) } + termios.tcsetattr(0, C.TCSANOW, mut old_state) println('') } mut new_state := old_state - new_state.c_lflag &= int(~u32(C.ECHO)) // Disable echoing of characters - unsafe { C.tcsetattr(0, C.TCSANOW, &new_state) } + new_state.c_lflag &= termios.invert(C.ECHO) // Disable echoing of characters + termios.tcsetattr(0, C.TCSANOW, mut new_state) password := input_opt(prompt) or { return error('Failed to read password') } diff --git a/vlib/readline/readline.v b/vlib/readline/readline.v index b2f520ec75..5a81b5d5c1 100644 --- a/vlib/readline/readline.v +++ b/vlib/readline/readline.v @@ -7,6 +7,8 @@ // module readline +import term.termios + // Winsize stores the screen information on Linux. struct Winsize { ws_row u16 @@ -20,9 +22,9 @@ struct Winsize { pub struct Readline { mut: is_raw bool - orig_termios Termios // Linux - current []rune // Line being edited - cursor int // Cursor position + orig_termios termios.Termios // Linux + current []rune // Line being edited + cursor int // Cursor position overwrite bool cursor_row_offset int prompt string diff --git a/vlib/readline/readline_darwin.c.v b/vlib/readline/readline_darwin.c.v deleted file mode 100644 index 01f9d12d51..0000000000 --- a/vlib/readline/readline_darwin.c.v +++ /dev/null @@ -1,599 +0,0 @@ -// Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. -// -// Serves as more advanced input method -// based on the work of https://github.com/AmokHuginnsson/replxx -// -module readline - -import term -import os - -#include -#include - -// https://github.com/apple/darwin-xnu/blob/main/bsd/sys/termios.h - -const cclen = 20 - -// Termios stores the terminal options -pub struct C.termios { -mut: - c_iflag int - c_oflag int - c_cflag int - c_lflag int - c_cc [cclen]u8 - c_ispeed int - c_ospeed int -} - -struct Termios { -mut: - c_iflag int - c_oflag int - c_cflag int - c_lflag int - c_cc [cclen]u8 - c_ispeed int - c_ospeed int - padding [200]char // Note: the padding here is larger than necessary, but that is better than overwriting the fields after 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.raise(sig int) - -fn C.getppid() int - -// Action defines what actions to be executed. -enum Action { - eof - nothing - insert_character - commit_line - delete_left - delete_right - move_cursor_left - move_cursor_right - move_cursor_begining - move_cursor_end - move_cursor_word_left - move_cursor_word_right - history_previous - history_next - overwrite - clear_screen - suspend -} - -// enable_raw_mode enables the raw mode of the terminal. -// In raw mode all key presses are directly sent to the program and no interpretation is done. -// 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 unsafe { C.tcgetattr(0, &C.termios(&r.orig_termios)) } != 0 { - r.is_tty = false - r.is_raw = false - return - } - 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] = u8(1) - raw.c_cc[C.VTIME] = u8(0) - unsafe { C.tcsetattr(0, C.TCSADRAIN, &raw) } - // println('> after raw: $raw') - r.is_raw = true - r.is_tty = true -} - -// enable_raw_mode_nosig enables the raw mode of the terminal. -// In raw mode all key presses are directly sent to the program and no interpretation is done. -// 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 unsafe { C.tcgetattr(0, &C.termios(&r.orig_termios)) } != 0 { - r.is_tty = false - r.is_raw = false - return - } - 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] = u8(1) - raw.c_cc[C.VTIME] = u8(0) - unsafe { C.tcsetattr(0, C.TCSADRAIN, &raw) } - r.is_raw = true - r.is_tty = true -} - -// disable_raw_mode disables the raw mode of the terminal. -// 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 { - unsafe { C.tcsetattr(0, C.TCSADRAIN, &C.termios(&r.orig_termios)) } - r.is_raw = false - } -} - -// read_char reads a single character. -pub fn (r Readline) read_char() !int { - return int(term.utf8_getchar() or { return err }) -} - -// read_line_utf8 blocks execution in a loop and awaits user input -// characters from a terminal until `EOF` or `Enter` key is encountered -// in the input stream. -// read_line_utf8 returns the complete input line as an UTF-8 encoded `[]rune` or -// an error if the line is empty. -// The `prompt` `string` is output as a prefix text for the input capturing. -// read_line_utf8 is the main method of the `readline` module and `Readline` struct. -pub fn (mut r Readline) read_line_utf8(prompt string) ![]rune { - r.current = []rune{} - r.cursor = 0 - r.prompt = prompt - r.search_index = 0 - r.prompt_offset = get_prompt_offset(prompt) - if r.previous_lines.len <= 1 { - r.previous_lines << []rune{} - r.previous_lines << []rune{} - } else { - r.previous_lines[0] = []rune{} - } - if !r.is_raw { - r.enable_raw_mode() - } - print(r.prompt) - for { - unsafe { C.fflush(C.stdout) } - c := r.read_char() or { return err } - a := r.analyse(c) - if r.execute(a, c) { - break - } - } - r.previous_lines[0] = []rune{} - r.search_index = 0 - r.disable_raw_mode() - if r.current.len == 0 { - return error('empty line') - } - return r.current -} - -// read_line does the same as `read_line_utf8` but returns user input as a `string`. -// (As opposed to `[]rune` returned by `read_line_utf8`). -pub fn (mut r Readline) read_line(prompt string) !string { - s := r.read_line_utf8(prompt)! - return s.string() -} - -// read_line_utf8 blocks execution in a loop and awaits user input -// characters from a terminal until `EOF` or `Enter` key is encountered -// in the input stream. -// read_line_utf8 returns the complete input line as an UTF-8 encoded `[]rune` or -// an error if the line is empty. -// The `prompt` `string` is output as a prefix text for the input capturing. -// read_line_utf8 is the main method of the `readline` module and `Readline` struct. -// NOTE that this version of `read_line_utf8` is a standalone function without -// persistent functionalities (e.g. history). -pub fn read_line_utf8(prompt string) ![]rune { - mut r := Readline{} - s := r.read_line_utf8(prompt)! - return s -} - -// read_line does the same as `read_line_utf8` but returns user input as a `string`. -// (As opposed to `[]rune` as returned by `read_line_utf8`). -// NOTE that this version of `read_line` is a standalone function without -// persistent functionalities (e.g. history). -pub fn read_line(prompt string) !string { - mut r := Readline{} - s := r.read_line(prompt)! - return s -} - -// analyse returns an `Action` based on the type of input byte given in `c`. -fn (r Readline) analyse(c int) Action { - if c > 255 { - return Action.insert_character - } - match u8(c) { - `\0`, 0x3, 0x4, 255 { - return .eof - } // NUL, End of Text, End of Transmission - `\n`, `\r` { - return .commit_line - } - `\f` { - return .clear_screen - } // CTRL + L - `\b`, 127 { - return .delete_left - } // BS, DEL - 27 { - return r.analyse_control() - } // ESC - 1 { - return .move_cursor_begining - } // ^A - 5 { - return .move_cursor_end - } // ^E - 26 { - return .suspend - } // CTRL + Z, SUB - else { - if c >= ` ` { - return Action.insert_character - } - return Action.nothing - } - } -} - -// analyse_control returns an `Action` based on the type of input read by `read_char`. -fn (r Readline) analyse_control() Action { - c := r.read_char() or { panic('Control sequence incomplete') } - match u8(c) { - `[` { - sequence := r.read_char() or { panic('Control sequence incomplete') } - match u8(sequence) { - `C` { return .move_cursor_right } - `D` { return .move_cursor_left } - `B` { return .history_next } - `A` { return .history_previous } - `H` { return .move_cursor_begining } - `F` { return .move_cursor_end } - `1` { return r.analyse_extended_control() } - `2`, `3` { return r.analyse_extended_control_no_eat(u8(sequence)) } - else {} - } - } - else {} - } - /* - //TODO -match c { - case `[`: - sequence := r.read_char()? - match sequence { - case `C`: return .move_cursor_right - case `D`: return .move_cursor_left - case `B`: return .history_next - case `A`: return .history_previous - case `1`: return r.analyse_extended_control() - case `2`: return r.analyse_extended_control_no_eat(sequence) - case `3`: return r.analyse_extended_control_no_eat(sequence) - case `9`: - foo() - bar() - else: - } - else: -} - */ - return .nothing -} - -// analyse_extended_control returns an `Action` based on the type of input read by `read_char`. -// analyse_extended_control specialises in cursor control. -fn (r Readline) analyse_extended_control() Action { - r.read_char() or { panic('Control sequence incomplete') } // Removes ; - c := r.read_char() or { panic('Control sequence incomplete') } - match u8(c) { - `5` { - direction := r.read_char() or { panic('Control sequence incomplete') } - match u8(direction) { - `C` { return .move_cursor_word_right } - `D` { return .move_cursor_word_left } - else {} - } - } - else {} - } - return .nothing -} - -// analyse_extended_control_no_eat returns an `Action` based on the type of input byte given in `c`. -// analyse_extended_control_no_eat specialises in detection of delete and insert keys. -fn (r Readline) analyse_extended_control_no_eat(last_c u8) Action { - c := r.read_char() or { panic('Control sequence incomplete') } - match u8(c) { - `~` { - match last_c { - `3` { return .delete_right } // Suppr key - `2` { return .overwrite } - else {} - } - } - else {} - } - return .nothing -} - -// execute executes the corresponding methods on `Readline` based on `a Action` and `c int` arguments. -fn (mut r Readline) execute(a Action, c int) bool { - match a { - .eof { return r.eof() } - .insert_character { r.insert_character(c) } - .commit_line { return r.commit_line() } - .delete_left { r.delete_character() } - .delete_right { r.suppr_character() } - .move_cursor_left { r.move_cursor_left() } - .move_cursor_right { r.move_cursor_right() } - .move_cursor_begining { r.move_cursor_begining() } - .move_cursor_end { r.move_cursor_end() } - .move_cursor_word_left { r.move_cursor_word_left() } - .move_cursor_word_right { r.move_cursor_word_right() } - .history_previous { r.history_previous() } - .history_next { r.history_next() } - .overwrite { r.switch_overwrite() } - .clear_screen { r.clear_screen() } - .suspend { r.suspend() } - else {} - } - return false -} - -// get_screen_columns returns the number of columns (`width`) in the terminal. -fn get_screen_columns() int { - ws := Winsize{} - cols := if unsafe { C.ioctl(1, C.TIOCGWINSZ, &ws) } == -1 { 80 } else { int(ws.ws_col) } - return cols -} - -// shift_cursor warps the cursor to `xpos` with `yoffset`. -fn shift_cursor(xpos int, yoffset int) { - if yoffset != 0 { - if yoffset > 0 { - term.cursor_down(yoffset) - } else { - term.cursor_up(-yoffset) - } - } - // Absolute X position - print('\x1b[${xpos + 1}G') -} - -// calculate_screen_position returns a position `[x, y]int` based on various terminal attributes. -fn calculate_screen_position(x_in int, y_in int, screen_columns int, char_count int, inp []int) []int { - mut out := inp.clone() - mut x := x_in - mut y := y_in - out[0] = x - out[1] = y - for chars_remaining := char_count; chars_remaining > 0; { - chars_this_row := if (x + chars_remaining) < screen_columns { - chars_remaining - } else { - screen_columns - x - } - out[0] = x + chars_this_row - out[1] = y - chars_remaining -= chars_this_row - x = 0 - y++ - } - if out[0] == screen_columns { - out[0] = 0 - out[1]++ - } - return out -} - -// get_prompt_offset computes the length of the `prompt` `string` argument. -fn get_prompt_offset(prompt string) int { - mut len := 0 - for i := 0; i < prompt.len; i++ { - if prompt[i] == `\e` { - for ; i < prompt.len && prompt[i] != `m`; i++ { - } - } else { - len = len + 1 - } - } - return prompt.len - len -} - -// refresh_line redraws the current line, including the prompt. -fn (mut r Readline) refresh_line() { - mut end_of_input := [0, 0] - end_of_input = calculate_screen_position(r.prompt.len, 0, get_screen_columns(), r.current.len, - end_of_input) - end_of_input[1] += r.current.filter(it == `\n`).len - mut cursor_pos := [0, 0] - cursor_pos = calculate_screen_position(r.prompt.len, 0, get_screen_columns(), r.cursor, - cursor_pos) - shift_cursor(0, -r.cursor_row_offset) - term.erase_toend() - print(r.prompt) - print(r.current.string()) - if end_of_input[0] == 0 && end_of_input[1] > 0 { - print('\n') - } - shift_cursor(cursor_pos[0] - r.prompt_offset, -(end_of_input[1] - cursor_pos[1])) - r.cursor_row_offset = cursor_pos[1] -} - -// eof ends the line *without* a newline. -fn (mut r Readline) eof() bool { - r.previous_lines.insert(1, r.current) - r.cursor = r.current.len - if r.is_tty { - r.refresh_line() - } - return true -} - -// insert_character inserts the character `c` at current cursor position. -fn (mut r Readline) insert_character(c int) { - if !r.overwrite || r.cursor == r.current.len { - r.current.insert(r.cursor, c) - } else { - r.current[r.cursor] = rune(c) - } - r.cursor++ - // Refresh the line to add the new character - if r.is_tty { - r.refresh_line() - } -} - -// Removes the character behind cursor. -fn (mut r Readline) delete_character() { - if r.cursor <= 0 { - return - } - r.cursor-- - r.current.delete(r.cursor) - r.refresh_line() -} - -// suppr_character removes (suppresses) the character in front of the cursor. -fn (mut r Readline) suppr_character() { - if r.cursor >= r.current.len { - return - } - r.current.delete(r.cursor) - r.refresh_line() -} - -// commit_line adds a line break and then stops the main loop. -fn (mut r Readline) commit_line() bool { - r.previous_lines.insert(1, r.current) - r.current << `\n` - r.cursor = r.current.len - if r.is_tty { - r.refresh_line() - println('') - } - return true -} - -// move_cursor_left moves the cursor relative one cell to the left. -fn (mut r Readline) move_cursor_left() { - if r.cursor > 0 { - r.cursor-- - r.refresh_line() - } -} - -// move_cursor_right moves the cursor relative one cell to the right. -fn (mut r Readline) move_cursor_right() { - if r.cursor < r.current.len { - r.cursor++ - r.refresh_line() - } -} - -// move_cursor_begining moves the cursor to the beginning of the current line. -fn (mut r Readline) move_cursor_begining() { - r.cursor = 0 - r.refresh_line() -} - -// move_cursor_end moves the cursor to the end of the current line. -fn (mut r Readline) move_cursor_end() { - r.cursor = r.current.len - r.refresh_line() -} - -// is_break_character returns true if the character is considered as a word-breaking character. -fn (r Readline) is_break_character(c string) bool { - break_characters := ' \t\v\f\a\b\r\n`~!@#$%^&*()-=+[{]}\\|;:\'",<.>/?' - return break_characters.contains(c) -} - -// move_cursor_word_left moves the cursor relative one word length worth to the left. -fn (mut r Readline) move_cursor_word_left() { - if r.cursor > 0 { - for ; r.cursor > 0 && r.is_break_character(r.current[r.cursor - 1].str()); r.cursor-- { - } - for ; r.cursor > 0 && !r.is_break_character(r.current[r.cursor - 1].str()); r.cursor-- { - } - r.refresh_line() - } -} - -// move_cursor_word_right moves the cursor relative one word length worth to the right. -fn (mut r Readline) move_cursor_word_right() { - if r.cursor < r.current.len { - for ; r.cursor < r.current.len && r.is_break_character(r.current[r.cursor].str()); r.cursor++ { - } - for ; r.cursor < r.current.len && !r.is_break_character(r.current[r.cursor].str()); r.cursor++ { - } - r.refresh_line() - } -} - -// switch_overwrite toggles Readline `overwrite` mode on/off. -fn (mut r Readline) switch_overwrite() { - r.overwrite = !r.overwrite -} - -// clear_screen clears the current terminal window contents and positions the cursor at top left. -fn (mut r Readline) clear_screen() { - term.set_cursor_position(x: 1, y: 1) - term.erase_clear() - r.refresh_line() -} - -// history_previous sets current line to the content of the previous line in the history buffer. -fn (mut r Readline) history_previous() { - if r.search_index + 2 >= r.previous_lines.len { - return - } - if r.search_index == 0 { - r.previous_lines[0] = r.current - } - r.search_index++ - prev_line := r.previous_lines[r.search_index] - if r.skip_empty && prev_line == [] { - r.history_previous() - } else { - r.current = prev_line - r.cursor = r.current.len - r.refresh_line() - } -} - -// history_next sets current line to the content of the next line in the history buffer. -fn (mut r Readline) history_next() { - if r.search_index <= 0 { - return - } - r.search_index-- - r.current = r.previous_lines[r.search_index] - r.cursor = r.current.len - r.refresh_line() -} - -// suspend sends the `SIGSTOP` signal to the terminal. -fn (mut r Readline) suspend() { - is_standalone := os.getenv('VCHILD') != 'true' - r.disable_raw_mode() - if !is_standalone { - // We have to SIGSTOP the parent v process - unsafe { - ppid := C.getppid() - C.kill(ppid, C.SIGSTOP) - } - } - unsafe { C.raise(C.SIGSTOP) } - r.enable_raw_mode() - r.refresh_line() - if r.is_tty { - r.refresh_line() - } -} diff --git a/vlib/readline/readline_linux.c.v b/vlib/readline/readline_nix.c.v similarity index 92% rename from vlib/readline/readline_linux.c.v rename to vlib/readline/readline_nix.c.v index e2ab9b8c04..cc837b8369 100644 --- a/vlib/readline/readline_linux.c.v +++ b/vlib/readline/readline_nix.c.v @@ -7,39 +7,10 @@ // module readline +import term.termios import term import os -#include -#include - -const cclen = 10 - -// Termios stores the terminal options on Linux. -pub struct C.termios { -mut: - c_iflag int - c_oflag int - c_cflag int - c_lflag int - c_line u8 - c_cc [cclen]int -} - -struct Termios { -mut: - c_iflag u32 - c_oflag u32 - c_cflag u32 - c_lflag u32 - c_line u8 - c_cc [cclen]int -} - -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.raise(sig int) fn C.getppid() int @@ -70,21 +41,20 @@ 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 unsafe { C.tcgetattr(0, &C.termios(&r.orig_termios)) } != 0 { + if termios.tcgetattr(0, mut r.orig_termios) != 0 { r.is_tty = false r.is_raw = false return } - mut raw := C.termios{} - unsafe { vmemcpy(&raw, &r.orig_termios, int(sizeof(raw))) } + mut raw := r.orig_termios // 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_iflag &= termios.invert(C.BRKINT | C.ICRNL | C.INPCK | C.ISTRIP | C.IXON) + raw.c_cflag |= termios.flag(C.CS8) + raw.c_lflag &= termios.invert(C.ECHO | C.ICANON | C.IEXTEN | C.ISIG) raw.c_cc[C.VMIN] = u8(1) raw.c_cc[C.VTIME] = u8(0) - unsafe { C.tcsetattr(0, C.TCSADRAIN, &raw) } + termios.tcsetattr(0, C.TCSADRAIN, mut raw) // println('> after raw: $raw') r.is_raw = true r.is_tty = true @@ -95,19 +65,18 @@ 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 unsafe { C.tcgetattr(0, &C.termios(&r.orig_termios)) } != 0 { + if termios.tcgetattr(0, mut r.orig_termios) != 0 { r.is_tty = false r.is_raw = false return } - 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) + mut raw := r.orig_termios + raw.c_iflag &= termios.invert(C.BRKINT | C.ICRNL | C.INPCK | C.ISTRIP | C.IXON) + raw.c_cflag |= termios.flag(C.CS8) + raw.c_lflag &= termios.invert(C.ECHO | C.ICANON | C.IEXTEN) raw.c_cc[C.VMIN] = u8(1) raw.c_cc[C.VTIME] = u8(0) - unsafe { C.tcsetattr(0, C.TCSADRAIN, &raw) } + termios.tcsetattr(0, C.TCSADRAIN, mut raw) r.is_raw = true r.is_tty = true } @@ -116,7 +85,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 { - unsafe { C.tcsetattr(0, C.TCSADRAIN, &C.termios(&r.orig_termios)) } + termios.tcsetattr(0, C.TCSADRAIN, mut r.orig_termios) r.is_raw = false } } diff --git a/vlib/term/term_nix.c.v b/vlib/term/term_nix.c.v index 60738625f2..8c89448114 100644 --- a/vlib/term/term_nix.c.v +++ b/vlib/term/term_nix.c.v @@ -1,9 +1,9 @@ module term import os +import term.termios #include -#include // TIOCGWINSZ pub struct C.winsize { pub: @@ -13,10 +13,6 @@ pub: ws_ypixel u16 } -fn C.tcgetattr(fd int, ptr &C.termios) int - -fn C.tcsetattr(fd int, action int, const_ptr &C.termios) int - fn C.ioctl(fd int, request u64, arg voidptr) int // get_terminal_size returns a number of colums and rows of terminal window. @@ -35,21 +31,22 @@ pub fn get_cursor_position() !Coord { return Coord{0, 0} } - old_state := C.termios{} - if unsafe { C.tcgetattr(0, &old_state) } != 0 { + mut old_state := termios.Termios{} + if termios.tcgetattr(0, mut old_state) != 0 { return os.last_error() } defer { // restore the old terminal state: - unsafe { C.tcsetattr(0, C.TCSANOW, &old_state) } + termios.tcsetattr(0, C.TCSANOW, mut old_state) } - mut state := C.termios{} - if unsafe { C.tcgetattr(0, &state) } != 0 { + mut state := termios.Termios{} + if termios.tcgetattr(0, mut state) != 0 { return os.last_error() } - state.c_lflag &= int(~(u32(C.ICANON) | u32(C.ECHO))) - unsafe { C.tcsetattr(0, C.TCSANOW, &state) } + + state.c_lflag &= termios.invert(u32(C.ICANON) | u32(C.ECHO)) + termios.tcsetattr(0, C.TCSANOW, mut state) print('\e[6n') flush_stdout() diff --git a/vlib/term/termios/termios_android.c.v b/vlib/term/termios/termios_android.c.v new file mode 100644 index 0000000000..4f1a5d695f --- /dev/null +++ b/vlib/term/termios/termios_android.c.v @@ -0,0 +1,88 @@ +// Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// +// Serves as more advanced input method +// based on the work of https://github.com/AmokHuginnsson/replxx +// +module termios + +#include +#include + +// https://github.com/lattera/glibc/blob/master/sysdeps/unix/sysv/linux/bits/termios.h + +const cclen = 32 + +type TcFlag = int +type Speed = int +type Cc = u8 + +// Termios stores the terminal options on Linux. +struct C.termios { +mut: + c_iflag TcFlag + c_oflag TcFlag + c_cflag TcFlag + c_lflag TcFlag + c_line Cc + c_cc [cclen]Cc + c_ispeed Speed + c_ospeed Speed +} + +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 + +// flag provides a termios flag of the correct size +// for the underlying C.termios structure +[inline] +pub fn flag(value int) TcFlag { + return int(value) +} + +// invert is a platform dependant way to bitwise NOT (~) TcFlag +// as its length varies across platforms +[inline] +pub fn invert(value TcFlag) TcFlag { + return ~int(value) +} + +pub struct Termios { +pub mut: + c_iflag TcFlag + c_oflag TcFlag + c_cflag TcFlag + c_lflag TcFlag + c_line Cc + c_cc [cclen]Cc + c_ispeed Speed + c_ospeed Speed +} + +// tcgetattr is an unsafe wrapper around C.termios and keeps its semantic +[inline] +pub fn tcgetattr(fd int, mut termios_p Termios) int { + unsafe { + return C.tcgetattr(fd, &C.termios(termios_p)) + } +} + +// tcsetattr is an unsafe wrapper around C.termios and keeps its semantic +[inline] +pub fn tcsetattr(fd int, optional_actions int, mut termios_p Termios) int { + unsafe { + return C.tcsetattr(fd, optional_actions, &C.termios(termios_p)) + } +} + +// ioctl is an unsafe wrapper around C.ioctl and keeps its semantic +[inline] +pub fn ioctl(fd int, request u64, arg voidptr) int { + unsafe { + return C.ioctl(fd, request, arg) + } +} diff --git a/vlib/term/termios/termios_darwin.c.v b/vlib/term/termios/termios_darwin.c.v new file mode 100644 index 0000000000..6c24143f45 --- /dev/null +++ b/vlib/term/termios/termios_darwin.c.v @@ -0,0 +1,86 @@ +// Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// +// Serves as more advanced input method +// based on the work of https://github.com/AmokHuginnsson/replxx +// +module termios + +#include +#include + +// https://github.com/apple/darwin-xnu/blob/main/bsd/sys/termios.h + +const cclen = 20 + +type TcFlag = usize +type Speed = usize +type Cc = u8 + +// Termios stores the terminal options +struct C.termios { +mut: + c_iflag TcFlag + c_oflag TcFlag + c_cflag TcFlag + c_lflag TcFlag + c_cc [cclen]Cc + c_ispeed Speed + c_ospeed Speed +} + +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 + +// flag provides a termios flag of the correct size +// for the underlying C.termios structure +[inline] +pub fn flag(value int) TcFlag { + return usize(value) +} + +// invert is a platform dependant way to bitwise NOT (~) TcFlag +// as its length varies across platforms +[inline] +pub fn invert(value TcFlag) TcFlag { + return ~usize(value) +} + +pub struct Termios { +pub mut: + c_iflag TcFlag + c_oflag TcFlag + c_cflag TcFlag + c_lflag TcFlag + c_cc [cclen]Cc + c_ispeed Speed + c_ospeed Speed +} + +// tcgetattr is an unsafe wrapper around C.termios and keeps its semantic +[inline] +pub fn tcgetattr(fd int, mut termios_p Termios) int { + unsafe { + return C.tcgetattr(fd, &C.termios(termios_p)) + } +} + +// tcsetattr is an unsafe wrapper around C.termios and keeps its semantic +[inline] +pub fn tcsetattr(fd int, optional_actions int, mut termios_p Termios) int { + unsafe { + return C.tcsetattr(fd, optional_actions, &C.termios(termios_p)) + } +} + +// ioctl is an unsafe wrapper around C.ioctl and keeps its semantic +[inline] +pub fn ioctl(fd int, request u64, arg voidptr) int { + unsafe { + return C.ioctl(fd, request, arg) + } +} diff --git a/vlib/term/termios/termios_default.c.v b/vlib/term/termios/termios_default.c.v new file mode 100644 index 0000000000..0e4691b730 --- /dev/null +++ b/vlib/term/termios/termios_default.c.v @@ -0,0 +1,58 @@ +// Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// +// TODO Mac version needs to be implemented +// Will serve as more advanced input method +// based on the work of https://github.com/AmokHuginnsson/replxx +// + +module termios + +// not used but needed for function declarations +type TcFlag = int +type Speed = int +type Cc = u8 + +// flag provides a termios flag of the correct size +// for the underlying C.termios structure +// It is only implemented for Unix like OSes +pub fn flag(value int) TcFlag { + $compile_error('feature not available') +} + +// invert is a platform dependant way to bitwise NOT (~) TcFlag +// as its length varies across platforms +// It is only implemented for Unix like OSes +pub fn invert(value TcFlag) TcFlag { + $compile_error('feature not available') +} + +// termios definitions +// Linux https://github.com/lattera/glibc/blob/master/sysdeps/unix/sysv/linux/bits/termios.h +// OpenBSD https://github.com/openbsd/src/blob/master/sys/sys/termios.h +// FreeBSD https://web.mit.edu/freebsd/head/sys/sys/_termios.h +// Solaris https://github.com/omniti-labs/illumos-omnios/blob/master/usr/src/uts/common/sys/termios.h +// DragonFly https://gitweb.dragonflybsd.org/dragonfly.git/blob_plain/HEAD:/sys/sys/_termios.h +// QNX https://github.com/vocho/openqnx/blob/master/trunk/lib/c/public/termios.h + +pub struct Termios { +} + +// tcgetattr is an unsafe wrapper around C.termios and keeps its semantic +// It is only implemented for Unix like OSes +pub fn tcgetattr(fd int, mut termios_p Termios) int { + $compile_error('feature not available') +} + +// tcsetattr is an unsafe wrapper around C.termios and keeps its semantic +// It is only implemented for Unix like OSes +pub fn tcsetattr(fd int, optional_actions int, mut termios_p Termios) int { + $compile_error('feature not available') +} + +// ioctl is an unsafe wrapper around C.ioctl and keeps its semantic +[inline] +pub fn ioctl(fd int, request u64, arg voidptr) int { + $compile_error('feature not available') +} diff --git a/vlib/term/termios/termios_dragonfly.c.v b/vlib/term/termios/termios_dragonfly.c.v new file mode 100644 index 0000000000..c3ab4392d6 --- /dev/null +++ b/vlib/term/termios/termios_dragonfly.c.v @@ -0,0 +1,86 @@ +// Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// +// Serves as more advanced input method +// based on the work of https://github.com/AmokHuginnsson/replxx +// +module termios + +#include +#include + +// https://gitweb.dragonflybsd.org/dragonfly.git/blob_plain/HEAD:/sys/sys/_termios.h + +const cclen = 20 + +type TcFlag = int +type Speed = int +type Cc = u8 + +// Termios stores the terminal options +struct C.termios { +mut: + c_iflag TcFlag + c_oflag TcFlag + c_cflag TcFlag + c_lflag TcFlag + c_cc [cclen]Cc + c_ispeed Speed + c_ospeed Speed +} + +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 + +// flag provides a termios flag of the correct size +// for the underlying C.termios structure +[inline] +pub fn flag(value int) TcFlag { + return int(value) +} + +// invert is a platform dependant way to bitwise NOT (~) TcFlag +// as its length varies across platforms +[inline] +pub fn invert(value TcFlag) TcFlag { + return ~int(value) +} + +pub struct Termios { +pub mut: + c_iflag TcFlag + c_oflag TcFlag + c_cflag TcFlag + c_lflag TcFlag + c_cc [cclen]Cc + c_ispeed Speed + c_ospeed Speed +} + +// tcgetattr is an unsafe wrapper around C.termios and keeps its semantic +[inline] +pub fn tcgetattr(fd int, mut termios_p Termios) int { + unsafe { + return C.tcgetattr(fd, &C.termios(termios_p)) + } +} + +// tcsetattr is an unsafe wrapper around C.termios and keeps its semantic +[inline] +pub fn tcsetattr(fd int, optional_actions int, mut termios_p Termios) int { + unsafe { + return C.tcsetattr(fd, optional_actions, &C.termios(termios_p)) + } +} + +// ioctl is an unsafe wrapper around C.ioctl and keeps its semantic +[inline] +pub fn ioctl(fd int, request u64, arg voidptr) int { + unsafe { + return C.ioctl(fd, request, arg) + } +} diff --git a/vlib/term/termios/termios_freebsd.c.v b/vlib/term/termios/termios_freebsd.c.v new file mode 100644 index 0000000000..b37202ff04 --- /dev/null +++ b/vlib/term/termios/termios_freebsd.c.v @@ -0,0 +1,86 @@ +// Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// +// Serves as more advanced input method +// based on the work of https://github.com/AmokHuginnsson/replxx +// +module termios + +#include +#include + +// https://web.mit.edu/freebsd/head/sys/sys/_termios.h + +const cclen = 20 + +type TcFlag = int +type Speed = int +type Cc = u8 + +// Termios stores the terminal options +struct C.termios { +mut: + c_iflag TcFlag + c_oflag TcFlag + c_cflag TcFlag + c_lflag TcFlag + c_cc [cclen]Cc + c_ispeed Speed + c_ospeed Speed +} + +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 + +// flag provides a termios flag of the correct size +// for the underlying C.termios structure +[inline] +pub fn flag(value int) TcFlag { + return int(value) +} + +// invert is a platform dependant way to bitwise NOT (~) TcFlag +// as its length varies across platforms +[inline] +pub fn invert(value TcFlag) TcFlag { + return ~int(value) +} + +pub struct Termios { +pub mut: + c_iflag TcFlag + c_oflag TcFlag + c_cflag TcFlag + c_lflag TcFlag + c_cc [cclen]Cc + c_ispeed Speed + c_ospeed Speed +} + +// tcgetattr is an unsafe wrapper around C.termios and keeps its semantic +[inline] +pub fn tcgetattr(fd int, mut termios_p Termios) int { + unsafe { + return C.tcgetattr(fd, &C.termios(termios_p)) + } +} + +// tcsetattr is an unsafe wrapper around C.termios and keeps its semantic +[inline] +pub fn tcsetattr(fd int, optional_actions int, mut termios_p Termios) int { + unsafe { + return C.tcsetattr(fd, optional_actions, &C.termios(termios_p)) + } +} + +// ioctl is an unsafe wrapper around C.ioctl and keeps its semantic +[inline] +pub fn ioctl(fd int, request u64, arg voidptr) int { + unsafe { + return C.ioctl(fd, request, arg) + } +} diff --git a/vlib/term/termios/termios_linux.c.v b/vlib/term/termios/termios_linux.c.v new file mode 100644 index 0000000000..4f1a5d695f --- /dev/null +++ b/vlib/term/termios/termios_linux.c.v @@ -0,0 +1,88 @@ +// Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// +// Serves as more advanced input method +// based on the work of https://github.com/AmokHuginnsson/replxx +// +module termios + +#include +#include + +// https://github.com/lattera/glibc/blob/master/sysdeps/unix/sysv/linux/bits/termios.h + +const cclen = 32 + +type TcFlag = int +type Speed = int +type Cc = u8 + +// Termios stores the terminal options on Linux. +struct C.termios { +mut: + c_iflag TcFlag + c_oflag TcFlag + c_cflag TcFlag + c_lflag TcFlag + c_line Cc + c_cc [cclen]Cc + c_ispeed Speed + c_ospeed Speed +} + +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 + +// flag provides a termios flag of the correct size +// for the underlying C.termios structure +[inline] +pub fn flag(value int) TcFlag { + return int(value) +} + +// invert is a platform dependant way to bitwise NOT (~) TcFlag +// as its length varies across platforms +[inline] +pub fn invert(value TcFlag) TcFlag { + return ~int(value) +} + +pub struct Termios { +pub mut: + c_iflag TcFlag + c_oflag TcFlag + c_cflag TcFlag + c_lflag TcFlag + c_line Cc + c_cc [cclen]Cc + c_ispeed Speed + c_ospeed Speed +} + +// tcgetattr is an unsafe wrapper around C.termios and keeps its semantic +[inline] +pub fn tcgetattr(fd int, mut termios_p Termios) int { + unsafe { + return C.tcgetattr(fd, &C.termios(termios_p)) + } +} + +// tcsetattr is an unsafe wrapper around C.termios and keeps its semantic +[inline] +pub fn tcsetattr(fd int, optional_actions int, mut termios_p Termios) int { + unsafe { + return C.tcsetattr(fd, optional_actions, &C.termios(termios_p)) + } +} + +// ioctl is an unsafe wrapper around C.ioctl and keeps its semantic +[inline] +pub fn ioctl(fd int, request u64, arg voidptr) int { + unsafe { + return C.ioctl(fd, request, arg) + } +} diff --git a/vlib/term/termios/termios_openbsd.c.v b/vlib/term/termios/termios_openbsd.c.v new file mode 100644 index 0000000000..ac8d0c1451 --- /dev/null +++ b/vlib/term/termios/termios_openbsd.c.v @@ -0,0 +1,86 @@ +// Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// +// Serves as more advanced input method +// based on the work of https://github.com/AmokHuginnsson/replxx +// +module termios + +#include +#include + +// https://github.com/openbsd/src/blob/master/sys/sys/termios.h + +const cclen = 20 + +type TcFlag = int +type Speed = int +type Cc = u8 + +// Termios stores the terminal options +struct C.termios { +mut: + c_iflag TcFlag + c_oflag TcFlag + c_cflag TcFlag + c_lflag TcFlag + c_cc [cclen]Cc + c_ispeed Speed + c_ospeed Speed +} + +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 + +// flag provides a termios flag of the correct size +// for the underlying C.termios structure +[inline] +pub fn flag(value int) TcFlag { + return int(value) +} + +// invert is a platform dependant way to bitwise NOT (~) TcFlag +// as its length varies across platforms +[inline] +pub fn invert(value TcFlag) TcFlag { + return ~int(value) +} + +pub struct Termios { +pub mut: + c_iflag TcFlag + c_oflag TcFlag + c_cflag TcFlag + c_lflag TcFlag + c_cc [cclen]Cc + c_ispeed Speed + c_ospeed Speed +} + +// tcgetattr is an unsafe wrapper around C.termios and keeps its semantic +[inline] +pub fn tcgetattr(fd int, mut termios_p Termios) int { + unsafe { + return C.tcgetattr(fd, &C.termios(termios_p)) + } +} + +// tcsetattr is an unsafe wrapper around C.termios and keeps its semantic +[inline] +pub fn tcsetattr(fd int, optional_actions int, mut termios_p Termios) int { + unsafe { + return C.tcsetattr(fd, optional_actions, &C.termios(termios_p)) + } +} + +// ioctl is an unsafe wrapper around C.ioctl and keeps its semantic +[inline] +pub fn ioctl(fd int, request u64, arg voidptr) int { + unsafe { + return C.ioctl(fd, request, arg) + } +} diff --git a/vlib/term/termios/termios_qnx.c.v b/vlib/term/termios/termios_qnx.c.v new file mode 100644 index 0000000000..bbed284e9b --- /dev/null +++ b/vlib/term/termios/termios_qnx.c.v @@ -0,0 +1,88 @@ +// Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// +// Serves as more advanced input method +// based on the work of https://github.com/AmokHuginnsson/replxx +// +module termios + +#include +#include + +// https://github.com/vocho/openqnx/blob/master/trunk/lib/c/public/termios.h + +const cclen = 20 + +type TcFlag = int +type Speed = int +type Cc = u8 + +// Termios stores the terminal options +struct C.termios { +mut: + c_iflag TcFlag + c_oflag TcFlag + c_cflag TcFlag + c_lflag TcFlag + c_cc [cclen]Cc + reserved [3]u32 + c_ispeed Speed + c_ospeed Speed +} + +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 + +// flag provides a termios flag of the correct size +// for the underlying C.termios structure +[inline] +pub fn flag(value int) TcFlag { + return int(value) +} + +// invert is a platform dependant way to bitwise NOT (~) TcFlag +// as its length varies across platforms +[inline] +pub fn invert(value TcFlag) TcFlag { + return ~int(value) +} + +pub struct Termios { +pub mut: + c_iflag TcFlag + c_oflag TcFlag + c_cflag TcFlag + c_lflag TcFlag + c_cc [cclen]Cc + reserved [3]u32 + c_ispeed Speed + c_ospeed Speed +} + +// tcgetattr is an unsafe wrapper around C.termios and keeps its semantic +[inline] +pub fn tcgetattr(fd int, mut termios_p Termios) int { + unsafe { + return C.tcgetattr(fd, &C.termios(termios_p)) + } +} + +// tcsetattr is an unsafe wrapper around C.termios and keeps its semantic +[inline] +pub fn tcsetattr(fd int, optional_actions int, mut termios_p Termios) int { + unsafe { + return C.tcsetattr(fd, optional_actions, &C.termios(termios_p)) + } +} + +// ioctl is an unsafe wrapper around C.ioctl and keeps its semantic +[inline] +pub fn ioctl(fd int, request u64, arg voidptr) int { + unsafe { + return C.ioctl(fd, request, arg) + } +} diff --git a/vlib/term/termios/termios_solaris.c.v b/vlib/term/termios/termios_solaris.c.v new file mode 100644 index 0000000000..bf3d5a235e --- /dev/null +++ b/vlib/term/termios/termios_solaris.c.v @@ -0,0 +1,82 @@ +// Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// +// Serves as more advanced input method +// based on the work of https://github.com/AmokHuginnsson/replxx +// +module termios + +#include +#include + +// https://web.mit.edu/freebsd/head/sys/sys/_termios.h + +const cclen = 20 + +type TcFlag = int +type Speed = int +type Cc = u8 + +// Termios stores the terminal options +struct C.termios { +mut: + c_iflag TcFlag + c_oflag TcFlag + c_cflag TcFlag + c_lflag TcFlag + c_cc [cclen]Cc +} + +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 + +// flag provides a termios flag of the correct size +// for the underlying C.termios structure +[inline] +pub fn flag(value int) TcFlag { + return int(value) +} + +// invert is a platform dependant way to bitwise NOT (~) TcFlag +// as its length varies across platforms +[inline] +pub fn invert(value TcFlag) TcFlag { + return ~int(value) +} + +pub struct Termios { +pub mut: + c_iflag TcFlag + c_oflag TcFlag + c_cflag TcFlag + c_lflag TcFlag + c_cc [cclen]Cc +} + +// tcgetattr is an unsafe wrapper around C.termios and keeps its semantic +[inline] +pub fn tcgetattr(fd int, mut termios_p Termios) int { + unsafe { + return C.tcgetattr(fd, &C.termios(termios_p)) + } +} + +// tcsetattr is an unsafe wrapper around C.termios and keeps its semantic +[inline] +pub fn tcsetattr(fd int, optional_actions int, mut termios_p Termios) int { + unsafe { + return C.tcsetattr(fd, optional_actions, &C.termios(termios_p)) + } +} + +// ioctl is an unsafe wrapper around C.ioctl and keeps its semantic +[inline] +pub fn ioctl(fd int, request u64, arg voidptr) int { + unsafe { + return C.ioctl(fd, request, arg) + } +} diff --git a/vlib/term/termios/termios_test.c.v b/vlib/term/termios/termios_test.c.v new file mode 100644 index 0000000000..d5461cecfe --- /dev/null +++ b/vlib/term/termios/termios_test.c.v @@ -0,0 +1,20 @@ +module termios + +fn test_termios() { + mut original_term := Termios{} + tcgetattr(0, mut original_term) + println(original_term) + + mut silent_term := original_term + silent_term.c_lflag &= invert(C.ECHO) + tcsetattr(0, C.TCSANOW, mut silent_term) + + mut check_term := Termios{} + tcgetattr(0, mut check_term) + assert check_term.c_lflag == silent_term.c_lflag + + tcsetattr(0, C.TCSANOW, mut orginal_term) + + tcgetattr(0, mut check_term) + assert check_term.c_lflag == orginal_term.c_lflag +} diff --git a/vlib/term/termios/termios_windows.c.v b/vlib/term/termios/termios_windows.c.v new file mode 100644 index 0000000000..d5675d2511 --- /dev/null +++ b/vlib/term/termios/termios_windows.c.v @@ -0,0 +1,49 @@ +// Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// +// TODO Windows version needs to be implemented. +// Will serve as more advanced input method +// based on the work of https://github.com/AmokHuginnsson/replxx +// +module termios + +type TcFlag = int +type Speed = int +type Cc = u8 + +// flag provides a termios flag of the correct size +// for the underlying C.termios structure +// It is only implemented for Unix like OSes +pub fn flag(value int) TcFlag { + return int(value) +} + +// invert is a platform dependant way to bitwise NOT (~) TcFlag +// as its length varies across platforms +// It is only implemented for Unix like OSes +pub fn invert(value TcFlag) TcFlag { + return ~int(value) +} + +pub struct Termios { +} + +// tcgetattr is an unsafe wrapper around C.termios and keeps its semantic +// It is only implemented for Unix like OSes +pub fn tcgetattr(fd int, mut termios_p Termios) int { + return -1 +} + +// tcsetattr is an unsafe wrapper around C.termios and keeps its semantic +// It is only implemented for Unix like OSes +pub fn tcsetattr(fd int, optional_actions int, mut termios_p Termios) int { + return -1 +} + +// ioctl is an unsafe wrapper around C.ioctl and keeps its semantic +// It is only implemented for Unix like OSes +[inline] +pub fn ioctl(fd int, request u64, arg voidptr) int { + return -1 +} diff --git a/vlib/term/ui/declarations_default.c.v b/vlib/term/ui/declarations_default.c.v deleted file mode 100644 index 6484e984f9..0000000000 --- a/vlib/term/ui/declarations_default.c.v +++ /dev/null @@ -1,10 +0,0 @@ -module ui - -pub struct C.termios { -mut: - c_iflag int - c_oflag int - c_cflag int - c_lflag int - c_cc [20]u8 -} diff --git a/vlib/term/ui/declarations_linux.c.v b/vlib/term/ui/declarations_linux.c.v deleted file mode 100644 index 97bd730ab7..0000000000 --- a/vlib/term/ui/declarations_linux.c.v +++ /dev/null @@ -1,11 +0,0 @@ -module ui - -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/ui/termios_nix.c.v b/vlib/term/ui/termios_nix.c.v index ee64a79420..75c2126995 100644 --- a/vlib/term/ui/termios_nix.c.v +++ b/vlib/term/ui/termios_nix.c.v @@ -5,9 +5,8 @@ module ui import os import time +import term.termios -#include -#include #include pub struct C.winsize { @@ -15,25 +14,19 @@ pub struct C.winsize { ws_col u16 } -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 { - mut t := C.termios{} - C.tcgetattr(C.STDIN_FILENO, &t) +fn get_termios() termios.Termios { + mut t := termios.Termios{} + termios.tcgetattr(C.STDIN_FILENO, mut t) return t } [inline] fn get_terminal_size() (u16, u16) { winsz := C.winsize{} - C.ioctl(0, C.TIOCGWINSZ, &winsz) + termios.ioctl(0, termios.flag(C.TIOCGWINSZ), voidptr(&winsz)) return winsz.ws_row, winsz.ws_col } @@ -60,16 +53,16 @@ fn (mut ctx Context) termios_setup() ! { return error('not running under a TTY') } - mut termios := get_termios() + mut tios := get_termios() 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 &= ~(C.IGNBRK | C.BRKINT | C.PARMRK | C.IXON) - termios.c_lflag &= ~(C.ICANON | C.ISIG | C.ECHO | C.IEXTEN | C.TOSTOP) + tios.c_iflag &= termios.invert(C.IGNBRK | C.BRKINT | C.PARMRK | C.IXON) + tios.c_lflag &= termios.invert(C.ICANON | C.ISIG | C.ECHO | C.IEXTEN | C.TOSTOP) } else { // Set raw input mode by unsetting ICANON and ECHO - termios.c_lflag &= ~(C.ICANON | C.ECHO) + tios.c_lflag &= termios.invert(C.ICANON | C.ECHO) } if ctx.cfg.hide_cursor { @@ -85,9 +78,9 @@ fn (mut ctx Context) termios_setup() ! { if !ctx.cfg.skip_init_checks { // prevent blocking during the feature detections, but allow enough time for the terminal // to send back the relevant input data - termios.c_cc[C.VTIME] = 1 - termios.c_cc[C.VMIN] = 0 - C.tcsetattr(C.STDIN_FILENO, C.TCSAFLUSH, &termios) + tios.c_cc[C.VTIME] = 1 + tios.c_cc[C.VMIN] = 0 + termios.tcsetattr(C.STDIN_FILENO, C.TCSAFLUSH, mut tios) // feature-test the SU spec sx, sy := get_cursor_position() print('${bsu}${esu}') @@ -105,9 +98,9 @@ fn (mut ctx Context) termios_setup() ! { ctx.enable_rgb = supports_truecolor() } // Prevent stdin from blocking by making its read time 0 - termios.c_cc[C.VTIME] = 0 - termios.c_cc[C.VMIN] = 0 - C.tcsetattr(C.STDIN_FILENO, C.TCSAFLUSH, &termios) + tios.c_cc[C.VTIME] = 0 + tios.c_cc[C.VMIN] = 0 + termios.tcsetattr(C.STDIN_FILENO, C.TCSAFLUSH, mut tios) // enable mouse input print('\x1b[?1003h\x1b[?1006h') flush_stdout() @@ -205,7 +198,8 @@ fn supports_truecolor() bool { fn termios_reset() { // C.TCSANOW ?? - C.tcsetattr(C.STDIN_FILENO, C.TCSAFLUSH, &ui.termios_at_startup) + mut startup := ui.termios_at_startup + termios.tcsetattr(C.STDIN_FILENO, C.TCSAFLUSH, mut startup) print('\x1b[?1003l\x1b[?1006l\x1b[?25h') flush_stdout() c := ctx_ptr diff --git a/vlib/v/builder/builder.v b/vlib/v/builder/builder.v index 1014b485f1..914a3af3b9 100644 --- a/vlib/v/builder/builder.v +++ b/vlib/v/builder/builder.v @@ -304,7 +304,11 @@ pub fn (b &Builder) import_graph() &depgraph.DepGraph { deps << 'builtin' if b.pref.backend == .c { // TODO JavaScript backend doesn't handle os for now - if b.pref.is_vsh && p.mod.name !in ['os', 'dl', 'strings.textscanner'] { + // os import libraries so we exclude anything which could cause a loop + // git grep import vlib/os | cut -f2 -d: | cut -f2 -d" " | sort -u + // dl, os, os.cmdline, os.filelock, os.notify, strings, strings.textscanner, term.termios, time + if b.pref.is_vsh + && p.mod.name !in ['os', 'dl', 'strings.textscanner', 'term.termios'] { deps << 'os' } }