aboutsummaryrefslogtreecommitdiff
path: root/v_windows/v/old/vlib/term
diff options
context:
space:
mode:
authorIndrajith K L2022-12-03 17:00:20 +0530
committerIndrajith K L2022-12-03 17:00:20 +0530
commitf5c4671bfbad96bf346bd7e9a21fc4317b4959df (patch)
tree2764fc62da58f2ba8da7ed341643fc359873142f /v_windows/v/old/vlib/term
downloadcli-tools-windows-master.tar.gz
cli-tools-windows-master.tar.bz2
cli-tools-windows-master.zip
Adds most of the toolsHEADmaster
Diffstat (limited to 'v_windows/v/old/vlib/term')
-rw-r--r--v_windows/v/old/vlib/term/README.md84
-rw-r--r--v_windows/v/old/vlib/term/colors.v199
-rw-r--r--v_windows/v/old/vlib/term/control.v108
-rw-r--r--v_windows/v/old/vlib/term/term.js.v8
-rw-r--r--v_windows/v/old/vlib/term/term.v196
-rw-r--r--v_windows/v/old/vlib/term/term_nix.c.v105
-rw-r--r--v_windows/v/old/vlib/term/term_test.v115
-rw-r--r--v_windows/v/old/vlib/term/term_windows.c.v125
-rw-r--r--v_windows/v/old/vlib/term/ui/README.md99
-rw-r--r--v_windows/v/old/vlib/term/ui/color.v88
-rw-r--r--v_windows/v/old/vlib/term/ui/consoleapi_windows.c.v82
-rw-r--r--v_windows/v/old/vlib/term/ui/input.v241
-rw-r--r--v_windows/v/old/vlib/term/ui/input_nix.c.v70
-rw-r--r--v_windows/v/old/vlib/term/ui/input_windows.c.v326
-rw-r--r--v_windows/v/old/vlib/term/ui/termios_nix.c.v530
-rw-r--r--v_windows/v/old/vlib/term/ui/ui.v256
16 files changed, 2632 insertions, 0 deletions
diff --git a/v_windows/v/old/vlib/term/README.md b/v_windows/v/old/vlib/term/README.md
new file mode 100644
index 0000000..8708226
--- /dev/null
+++ b/v_windows/v/old/vlib/term/README.md
@@ -0,0 +1,84 @@
+# Quickstart
+
+The V `term` module is a module designed to provide the building blocks
+for building very simple TUI apps.
+For more complex apps, you should really look at the `term.input` module,
+as it includes terminal events, is easier to use and is much more performant for large draws.
+
+# Use
+
+You can use the `term` module to either color the output on a terminal
+or to decide on where to put the output in your terminal.
+
+For example let's make a simple program which prints colored text in the middle of the terminal.
+
+```v
+import term
+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(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(x: 0, y: height) // Sets the position of the cursor to the bottom of the terminal
+ // Keep prompting until the user presses the q key
+ for {
+ if var := os.input_opt('press q to quit: ') {
+ if var != 'q' {
+ continue
+ }
+ break
+ }
+ println('')
+ break
+ }
+ println('Goodbye.')
+}
+```
+
+This simple program covers many of the principal aspects of the `term ` module.
+
+# API
+
+Here are some functions you should be aware of in the `term `module:
+
+```v oksyntax
+import term
+
+// returns the height and the width of the terminal
+width, height := term.get_terminal_size()
+// returns the string as green text to be printed on stdout
+term.ok_message('cool')
+// returns the string as red text to be printed on stdout
+term.fail_message('oh, no')
+// returns the string as yellow text to be printed on stdout
+term.warning_message('be warned')
+// clears the entire terminal and leaves a blank one
+term.clear()
+// colors the output of the output, the available colors are: black,blue,yellow,green,cyan,gray,bright_blue,bright_green,bright_red,bright_black,bright_cyan
+term.yellow('submarine')
+// transforms the given string into bold text
+term.bold('and beautiful')
+// puts a strikethrough into the given string
+term.strikethrough('the core of the problem')
+// underlines the given string
+term.underline('important')
+// colors the background of the output following the given color
+// the available colors are: black, blue, yellow, green, cyan, gray
+term.bg_green('field')
+// sets the position of the cursor at a given place in the terminal
+term.set_cursor_position(x: 5, y: 10)
+// moves the cursor up
+term.cursor_up()
+// moves the cursor down
+term.cursor_down()
+// moves the cursor to the right
+term.cursor_forward()
+// moves the cursor to the left
+term.cursor_back()
+// shows the cursor
+term.show_cursor()
+// hides the cursor
+term.hide_cursor()
+```
diff --git a/v_windows/v/old/vlib/term/colors.v b/v_windows/v/old/vlib/term/colors.v
new file mode 100644
index 0000000..f7662ee
--- /dev/null
+++ b/v_windows/v/old/vlib/term/colors.v
@@ -0,0 +1,199 @@
+// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved.
+// Use of this source code is governed by an MIT license
+// that can be found in the LICENSE file.
+module term
+
+pub fn format(msg string, open string, close string) string {
+ return '\x1b[' + open + 'm' + msg + '\x1b[' + close + 'm'
+}
+
+pub fn format_rgb(r int, g int, b int, msg string, open string, close string) string {
+ return '\x1b[' + open + ';2;' + r.str() + ';' + g.str() + ';' + b.str() + 'm' + msg + '\x1b[' +
+ close + 'm'
+}
+
+pub fn rgb(r int, g int, b int, msg string) string {
+ return format_rgb(r, g, b, msg, '38', '39')
+}
+
+pub fn bg_rgb(r int, g int, b int, msg string) string {
+ return format_rgb(r, g, b, msg, '48', '49')
+}
+
+pub fn hex(hex int, msg string) string {
+ return format_rgb(hex >> 16, hex >> 8 & 0xFF, hex & 0xFF, msg, '38', '39')
+}
+
+pub fn bg_hex(hex int, msg string) string {
+ return format_rgb(hex >> 16, hex >> 8 & 0xFF, hex & 0xFF, msg, '48', '49')
+}
+
+pub fn bg_black(msg string) string {
+ return format(msg, '40', '49')
+}
+
+pub fn bright_bg_black(msg string) string {
+ return format(msg, '100', '49')
+}
+
+pub fn bg_blue(msg string) string {
+ return format(msg, '44', '49')
+}
+
+pub fn bright_bg_blue(msg string) string {
+ return format(msg, '104', '49')
+}
+
+pub fn bg_cyan(msg string) string {
+ return format(msg, '46', '49')
+}
+
+pub fn bright_bg_cyan(msg string) string {
+ return format(msg, '106', '49')
+}
+
+pub fn bg_green(msg string) string {
+ return format(msg, '42', '49')
+}
+
+pub fn bright_bg_green(msg string) string {
+ return format(msg, '102', '49')
+}
+
+pub fn bg_magenta(msg string) string {
+ return format(msg, '45', '49')
+}
+
+pub fn bright_bg_magenta(msg string) string {
+ return format(msg, '105', '49')
+}
+
+pub fn bg_red(msg string) string {
+ return format(msg, '41', '49')
+}
+
+pub fn bright_bg_red(msg string) string {
+ return format(msg, '101', '49')
+}
+
+pub fn bg_white(msg string) string {
+ return format(msg, '47', '49')
+}
+
+pub fn bright_bg_white(msg string) string {
+ return format(msg, '107', '49')
+}
+
+pub fn bg_yellow(msg string) string {
+ return format(msg, '43', '49')
+}
+
+pub fn bright_bg_yellow(msg string) string {
+ return format(msg, '103', '49')
+}
+
+pub fn black(msg string) string {
+ return format(msg, '30', '39')
+}
+
+pub fn bright_black(msg string) string {
+ return format(msg, '90', '39')
+}
+
+pub fn blue(msg string) string {
+ return format(msg, '34', '39')
+}
+
+pub fn bright_blue(msg string) string {
+ return format(msg, '94', '39')
+}
+
+pub fn bold(msg string) string {
+ return format(msg, '1', '22')
+}
+
+pub fn cyan(msg string) string {
+ return format(msg, '36', '39')
+}
+
+pub fn bright_cyan(msg string) string {
+ return format(msg, '96', '39')
+}
+
+pub fn dim(msg string) string {
+ return format(msg, '2', '22')
+}
+
+pub fn green(msg string) string {
+ return format(msg, '32', '39')
+}
+
+pub fn bright_green(msg string) string {
+ return format(msg, '92', '39')
+}
+
+pub fn gray(msg string) string {
+ return bright_black(msg)
+}
+
+pub fn hidden(msg string) string {
+ return format(msg, '8', '28')
+}
+
+pub fn italic(msg string) string {
+ return format(msg, '3', '23')
+}
+
+pub fn inverse(msg string) string {
+ return format(msg, '7', '27')
+}
+
+pub fn magenta(msg string) string {
+ return format(msg, '35', '39')
+}
+
+pub fn bright_magenta(msg string) string {
+ return format(msg, '95', '39')
+}
+
+pub fn reset(msg string) string {
+ return format(msg, '0', '0')
+}
+
+pub fn red(msg string) string {
+ return format(msg, '31', '39')
+}
+
+pub fn bright_red(msg string) string {
+ return format(msg, '91', '39')
+}
+
+pub fn strikethrough(msg string) string {
+ return format(msg, '9', '29')
+}
+
+pub fn underline(msg string) string {
+ return format(msg, '4', '24')
+}
+
+pub fn white(msg string) string {
+ return format(msg, '37', '39')
+}
+
+pub fn bright_white(msg string) string {
+ return format(msg, '97', '39')
+}
+
+pub fn yellow(msg string) string {
+ return format(msg, '33', '39')
+}
+
+pub fn bright_yellow(msg string) string {
+ return format(msg, '93', '39')
+}
+
+// highlight_command highlights the command with an on-brand background
+// to make CLI commands immediately recognizable.
+pub fn highlight_command(command string) string {
+ return bright_white(bg_cyan(' $command '))
+}
diff --git a/v_windows/v/old/vlib/term/control.v b/v_windows/v/old/vlib/term/control.v
new file mode 100644
index 0000000..377cc42
--- /dev/null
+++ b/v_windows/v/old/vlib/term/control.v
@@ -0,0 +1,108 @@
+// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved.
+// Use of this source code is governed by an MIT license
+// that can be found in the LICENSE file.
+module term
+
+// Sources for ANSI Control Sequences
+// https://github.com/RajeshPatkarInstitute/Panim
+// https://www.gnu.org/software/screen/manual/html_node/Control-Sequences.html
+// https://en.wikipedia.org/wiki/ANSI_escape_code
+// Support for Windows
+// https://en.wikipedia.org/wiki/ANSI.SYS
+// #include <windows.h>
+// C.SetConsoleMode(C.ENABLE_VIRTUAL_TERMINAL_INPUT)
+// Setting cursor to the given position
+// x is the x coordinate
+// y is the y coordinate
+pub fn set_cursor_position(c Coord) {
+ print('\x1b[$c.y;$c.x' + 'H')
+}
+
+// n is number of cells
+// direction: A is up / North
+// direction: B is down / South
+// direction: C is forward / East
+// direction: D is backward / West
+pub fn move(n int, direction string) {
+ print('\x1b[$n$direction')
+}
+
+pub fn cursor_up(n int) {
+ move(n, 'A')
+}
+
+pub fn cursor_down(n int) {
+ move(n, 'B')
+}
+
+pub fn cursor_forward(n int) {
+ move(n, 'C')
+}
+
+pub fn cursor_back(n int) {
+ move(n, 'D')
+}
+
+// type: 0 -> current cursor position to end of the screen
+// type: 1 -> current cursor position to beginning of the screen
+// type: 2 -> clears entire screen
+// type: 3 -> clears entire screen and also delete scrollback buffer
+
+pub fn erase_display(t string) {
+ print('\x1b[' + t + 'J')
+}
+
+pub fn erase_toend() {
+ erase_display('0')
+}
+
+pub fn erase_tobeg() {
+ erase_display('1')
+}
+
+// clears entire screen and returns cursor to top left-corner
+pub fn erase_clear() {
+ print('\033[H\033[J')
+}
+
+pub fn erase_del_clear() {
+ erase_display('3')
+}
+
+// type: 0 -> current cursor position to end of the line
+// type: 1 -> current cursor position to beginning of the line
+// type: 2 -> clears entire line
+// Note: Cursor position does not change
+pub fn erase_line(t string) {
+ print('\x1b[' + t + 'K')
+}
+
+pub fn erase_line_toend() {
+ erase_line('0')
+}
+
+pub fn erase_line_tobeg() {
+ erase_line('1')
+}
+
+pub fn erase_line_clear() {
+ erase_line('2')
+}
+
+// Will make cursor appear if not visible
+pub fn show_cursor() {
+ print('\x1b[?25h')
+}
+
+// Will make cursor invisible
+pub fn hide_cursor() {
+ print('\x1b[?25l')
+}
+
+// clear_previous_line - useful for progressbars.
+// It moves the cursor to start of line, then 1 line above,
+// then erases the line. In effect the next println will overwrite
+// the previous content.
+pub fn clear_previous_line() {
+ print('\r\x1b[1A\x1b[2K')
+}
diff --git a/v_windows/v/old/vlib/term/term.js.v b/v_windows/v/old/vlib/term/term.js.v
new file mode 100644
index 0000000..be85eca
--- /dev/null
+++ b/v_windows/v/old/vlib/term/term.js.v
@@ -0,0 +1,8 @@
+module term
+
+// get_terminal_size returns a number of colums and rows of terminal window.
+pub fn get_terminal_size() (int, int) {
+ // TODO Find a way to get proper width & height of the terminal
+ // on a Javascript environment
+ return default_columns_size, default_rows_size
+}
diff --git a/v_windows/v/old/vlib/term/term.v b/v_windows/v/old/vlib/term/term.v
new file mode 100644
index 0000000..390b3be
--- /dev/null
+++ b/v_windows/v/old/vlib/term/term.v
@@ -0,0 +1,196 @@
+module term
+
+import os
+import strings.textscanner
+
+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 {
+ return supports_escape_sequences(1)
+}
+
+// can_show_color_on_stderr returns true if colors are allowed in stderr;
+// returns false otherwise.
+pub fn can_show_color_on_stderr() bool {
+ return supports_escape_sequences(2)
+}
+
+// failed returns a bold white on red version of the string `s`
+// If colors are not allowed, returns the string `s`
+pub fn failed(s string) string {
+ if can_show_color_on_stdout() {
+ return bg_red(bold(white(s)))
+ }
+ return s
+}
+
+// ok_message returns a colored string with green color.
+// If colors are not allowed, returns a given string.
+pub fn ok_message(s string) string {
+ if can_show_color_on_stdout() {
+ return green(' $s ')
+ }
+ return s
+}
+
+// fail_message returns a colored string with red color.
+// If colors are not allowed, returns a given string.
+pub fn fail_message(s string) string {
+ return failed(' $s ')
+}
+
+// warn_message returns a colored string with yellow color.
+// If colors are not allowed, returns a given string.
+pub fn warn_message(s string) string {
+ if can_show_color_on_stdout() {
+ return bright_yellow(' $s ')
+ }
+ return s
+}
+
+// colorize returns a colored string by running the specified `cfn` over
+// the message `s`, only if colored output is supported by the terminal.
+// Example: term.colorize(term.yellow, 'the message')
+pub fn colorize(cfn fn (string) string, s string) string {
+ if can_show_color_on_stdout() {
+ return cfn(s)
+ }
+ return s
+}
+
+// strip_ansi removes any ANSI sequences in the `text`
+pub fn strip_ansi(text string) string {
+ // This is a port of https://github.com/kilobyte/colorized-logs/blob/master/ansi2txt.c
+ // \e, [, 1, m, a, b, c, \e, [, 2, 2, m => abc
+ mut input := textscanner.new(text)
+ mut output := []byte{cap: text.len}
+ mut ch := 0
+ for ch != -1 {
+ ch = input.next()
+ if ch == 27 {
+ ch = input.next()
+ if ch == `[` {
+ for {
+ ch = input.next()
+ if ch in [`;`, `?`] || (ch >= `0` && ch <= `9`) {
+ continue
+ }
+ break
+ }
+ } else if ch == `]` {
+ ch = input.next()
+ if ch >= `0` && ch <= `9` {
+ for {
+ ch = input.next()
+ if ch == -1 || ch == 7 {
+ break
+ }
+ if ch == 27 {
+ ch = input.next()
+ break
+ }
+ }
+ }
+ } else if ch == `%` {
+ ch = input.next()
+ }
+ } else if ch != -1 {
+ output << byte(ch)
+ }
+ }
+ return output.bytestr()
+}
+
+// h_divider returns a horizontal divider line with a dynamic width,
+// that depends on the current terminal settings.
+// If an empty string is passed in, print enough spaces to make a new line
+pub fn h_divider(divider string) string {
+ cols, _ := get_terminal_size()
+ mut result := ''
+ if divider.len > 0 {
+ result = divider.repeat(1 + (cols / divider.len))
+ } else {
+ result = ' '.repeat(1 + cols)
+ }
+ return result[0..cols]
+}
+
+// header_left returns a horizontal divider line with a title text on the left.
+// e.g: term.header_left('TITLE', '=')
+// ==== TITLE =========================
+pub fn header_left(text string, divider string) string {
+ plain_text := strip_ansi(text)
+ xcols, _ := get_terminal_size()
+ cols := imax(1, xcols)
+ relement := if divider.len > 0 { divider } else { ' ' }
+ hstart := relement.repeat(4)[0..4]
+ remaining_cols := (cols - (hstart.len + 1 + plain_text.len + 1))
+ hend := relement.repeat((remaining_cols + 1) / relement.len)[0..remaining_cols]
+ return '$hstart $text $hend'
+}
+
+// header returns a horizontal divider line with a centered text in the middle.
+// e.g: term.header('TEXT', '=')
+// =============== TEXT ===============
+pub fn header(text string, divider string) string {
+ if text.len == 0 {
+ return h_divider(divider)
+ }
+ xcols, _ := get_terminal_size()
+ cols := imax(1, xcols)
+ tlimit := imax(1, if cols > text.len + 2 + 2 * divider.len {
+ text.len
+ } else {
+ cols - 3 - 2 * divider.len
+ })
+ tlimit_alligned := if (tlimit % 2) != (cols % 2) { tlimit + 1 } else { tlimit }
+ tstart := imax(0, (cols - tlimit_alligned) / 2)
+ mut ln := ''
+ if divider.len > 0 {
+ ln = divider.repeat(1 + cols / divider.len)[0..cols]
+ } else {
+ ln = ' '.repeat(1 + cols)
+ }
+ if ln.len == 1 {
+ return ln + ' ' + text[0..tlimit] + ' ' + ln
+ }
+ return ln[0..tstart] + ' ' + text[0..tlimit] + ' ' + ln[tstart + tlimit + 2..cols]
+}
+
+fn imax(x int, y int) int {
+ return if x > y { x } else { y }
+}
+
+fn supports_escape_sequences(fd int) bool {
+ vcolors_override := os.getenv('VCOLORS')
+ if vcolors_override == 'always' {
+ return true
+ }
+ if vcolors_override == 'never' {
+ return false
+ }
+ if os.getenv('TERM') == 'dumb' {
+ return false
+ }
+ $if windows {
+ if os.getenv('ConEmuANSI') == 'ON' {
+ return true
+ }
+ // 4 is enable_virtual_terminal_processing
+ return (os.is_atty(fd) & 0x0004) > 0
+ } $else {
+ return os.is_atty(fd) > 0
+ }
+}
diff --git a/v_windows/v/old/vlib/term/term_nix.c.v b/v_windows/v/old/vlib/term/term_nix.c.v
new file mode 100644
index 0000000..45a0a9b
--- /dev/null
+++ b/v_windows/v/old/vlib/term/term_nix.c.v
@@ -0,0 +1,105 @@
+module term
+
+import os
+
+#include <sys/ioctl.h>
+#include <termios.h> // TIOCGWINSZ
+
+pub struct C.winsize {
+pub:
+ ws_row u16
+ ws_col u16
+ ws_xpixel u16
+ ws_ypixel u16
+}
+
+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) {
+ if os.is_atty(1) <= 0 || os.getenv('TERM') == 'dumb' {
+ return default_columns_size, default_rows_size
+ }
+ w := C.winsize{}
+ 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 {
+ if os.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`
+ mut oldsettings := os.execute('stty -g')
+ if oldsettings.exit_code < 0 {
+ oldsettings = 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++
+ if i >= 15 {
+ panic('C.getchar() called too many times')
+ }
+ // 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 os.is_atty(1) <= 0 || os.getenv('TERM') == 'dumb' {
+ return true
+ }
+ print('\033]0')
+ print(title)
+ print('\007')
+ return true
+}
+
+// clear clears current terminal screen.
+pub fn clear() {
+ print('\x1b[2J')
+ print('\x1b[H')
+}
diff --git a/v_windows/v/old/vlib/term/term_test.v b/v_windows/v/old/vlib/term/term_test.v
new file mode 100644
index 0000000..00f9293
--- /dev/null
+++ b/v_windows/v/old/vlib/term/term_test.v
@@ -0,0 +1,115 @@
+import os
+import term
+
+fn test_get_terminal_size() {
+ cols, _ := term.get_terminal_size()
+ assert cols > 0
+}
+
+fn test_h_divider() {
+ divider := term.h_divider('-')
+ assert divider.len > 0
+ assert divider[0] == `-`
+ assert divider[divider.len - 1] == `-`
+}
+
+fn test_h_divider_multiple_characters() {
+ xdivider := term.h_divider('abc')
+ assert xdivider.len > 0
+ assert xdivider.contains('abcabc')
+}
+
+fn test_header() {
+ divider := term.h_divider('-')
+ term_width := divider.len
+ assert term_width > 0
+ empty_header := term.header('', '-')
+ short_header := term.header('reasonable header', '-')
+ very_long_header := term.header(['abc'].repeat(500).join(' '), '-')
+ very_long_header_2 := term.header(['abcd'].repeat(500).join(' '), '-')
+ /*
+ eprintln(divider)
+ eprintln(empty_header)
+ eprintln(short_header)
+ eprintln(term.header('another longer header', '_-/\\'))
+ eprintln(term.header('another longer header', '-'))
+ eprintln(term.header('short', '-'))
+ eprintln(term.header('12345', '-'))
+ eprintln(term.header('1234', '-'))
+ eprintln(term.header('123', '-'))
+ eprintln(term.header('12', '-'))
+ eprintln(term.header('1', '-'))
+ eprintln(very_long_header)
+ eprintln(divider)
+ eprintln(very_long_header_2)
+ eprintln(term.header(['abcd'].repeat(500).join(' '), '_-/\\'))
+ eprintln(term.header(['abcd'].repeat(500).join(' '), '_-//'))
+ eprintln(term.header('1', '_-/\\\/'))
+ eprintln(term.header('12', '_-/\\\/'))
+ 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
+}
+
+fn test_strip_ansi() {
+ strings := [
+ 'abc',
+ term.bold('abc'),
+ term.yellow('abc'),
+ term.bold(term.red('abc')),
+ term.strikethrough(term.inverse(term.dim(term.bold(term.bright_bg_blue('abc'))))),
+ ]
+ for s in strings {
+ assert term.strip_ansi(s) == 'abc'
+ }
+}
diff --git a/v_windows/v/old/vlib/term/term_windows.c.v b/v_windows/v/old/vlib/term/term_windows.c.v
new file mode 100644
index 0000000..1d2f032
--- /dev/null
+++ b/v_windows/v/old/vlib/term/term_windows.c.v
@@ -0,0 +1,125 @@
+module term
+
+import os
+
+[typedef]
+struct C.COORD {
+mut:
+ X i16
+ Y i16
+}
+
+[typedef]
+struct C.SMALL_RECT {
+mut:
+ Left u16
+ Top u16
+ Right u16
+ Bottom u16
+}
+
+// win: CONSOLE_SCREEN_BUFFER_INFO
+// https://docs.microsoft.com/en-us/windows/console/console-screen-buffer-info-str
+[typedef]
+struct C.CONSOLE_SCREEN_BUFFER_INFO {
+mut:
+ dwSize C.COORD
+ dwCursorPosition C.COORD
+ wAttributes u16
+ srWindow C.SMALL_RECT
+ dwMaximumWindowSize C.COORD
+}
+
+union C.uChar {
+mut:
+ UnicodeChar rune
+ AsciiChar byte
+}
+
+[typedef]
+struct C.CHAR_INFO {
+mut:
+ Char C.uChar
+ Attributes u16
+}
+
+// ref - https://docs.microsoft.com/en-us/windows/console/getconsolescreenbufferinfo
+fn C.GetConsoleScreenBufferInfo(handle C.HANDLE, info &C.CONSOLE_SCREEN_BUFFER_INFO) bool
+
+// ref - https://docs.microsoft.com/en-us/windows/console/setconsoletitle
+fn C.SetConsoleTitle(title &u16) bool
+
+// ref - https://docs.microsoft.com/en-us/windows/console/setconsolecursorposition
+fn C.SetConsoleCursorPosition(handle C.HANDLE, coord C.COORD) bool
+
+// ref - https://docs.microsoft.com/en-us/windows/console/scrollconsolescreenbuffer
+fn C.ScrollConsoleScreenBuffer(output C.HANDLE, scroll_rect &C.SMALL_RECT, clip_rect &C.SMALL_RECT, des C.COORD, fill &C.CHAR_INFO) bool
+
+// get_terminal_size returns a number of colums and rows of terminal window.
+pub fn get_terminal_size() (int, int) {
+ 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) {
+ columns := int(info.srWindow.Right - info.srWindow.Left + 1)
+ rows := int(info.srWindow.Bottom - info.srWindow.Top + 1)
+ return columns, rows
+ }
+ }
+ 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 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
+ }
+ }
+ 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
+}
+
+// clear clears current terminal screen.
+// Implementation taken from https://docs.microsoft.com/en-us/windows/console/clearing-the-screen#example-2.
+pub fn clear() {
+ hconsole := C.GetStdHandle(C.STD_OUTPUT_HANDLE)
+ mut csbi := C.CONSOLE_SCREEN_BUFFER_INFO{}
+ mut scrollrect := C.SMALL_RECT{}
+ mut scrolltarget := C.COORD{}
+ mut fill := C.CHAR_INFO{}
+
+ // Get the number of character cells in the current buffer.
+ if !C.GetConsoleScreenBufferInfo(hconsole, &csbi) {
+ return
+ }
+ // Scroll the rectangle of the entire buffer.
+ scrollrect.Left = 0
+ scrollrect.Top = 0
+ scrollrect.Right = u16(csbi.dwSize.X)
+ scrollrect.Bottom = u16(csbi.dwSize.Y)
+
+ // Scroll it upwards off the top of the buffer with a magnitude of the entire height.
+ scrolltarget.X = 0
+ scrolltarget.Y = (0 - csbi.dwSize.Y)
+
+ // Fill with empty spaces with the buffer's default text attribute.
+ fill.Char.UnicodeChar = rune(` `)
+ fill.Attributes = csbi.wAttributes
+
+ // Do the scroll
+ C.ScrollConsoleScreenBuffer(hconsole, &scrollrect, C.NULL, scrolltarget, &fill)
+
+ // Move the cursor to the top left corner too.
+ csbi.dwCursorPosition.X = 0
+ csbi.dwCursorPosition.Y = 0
+
+ C.SetConsoleCursorPosition(hconsole, csbi.dwCursorPosition)
+}
diff --git a/v_windows/v/old/vlib/term/ui/README.md b/v_windows/v/old/vlib/term/ui/README.md
new file mode 100644
index 0000000..6bce054
--- /dev/null
+++ b/v_windows/v/old/vlib/term/ui/README.md
@@ -0,0 +1,99 @@
+## `term.ui`
+
+A V module for designing terminal UI apps
+
+#### Quickstart
+
+```v
+import term.ui as tui
+
+struct App {
+mut:
+ tui &tui.Context = 0
+}
+
+fn event(e &tui.Event, x voidptr) {
+ mut app := &App(x)
+ println(e)
+ if e.typ == .key_down && e.code == .escape {
+ exit(0)
+ }
+}
+
+fn frame(x voidptr) {
+ mut app := &App(x)
+
+ app.tui.clear()
+ app.tui.set_bg_color(r: 63, g: 81, b: 181)
+ app.tui.draw_rect(20, 6, 41, 10)
+ app.tui.draw_text(24, 8, 'Hello from V!')
+ app.tui.set_cursor_position(0, 0)
+
+ app.tui.reset()
+ app.tui.flush()
+}
+
+mut app := &App{}
+app.tui = tui.init(
+ user_data: app
+ event_fn: event
+ frame_fn: frame
+ hide_cursor: true
+)
+app.tui.run() ?
+```
+
+See the `/examples/term.ui/` folder for more usage examples.
+
+#### Configuration
+
+- `user_data voidptr` - a pointer to any `user_data`, it will be passed as the last argument to
+ each callback. Used for accessing your app context from the different callbacks.
+- `init_fn fn(voidptr)` - a callback that will be called after initialization
+ and before the first event / frame. Useful for initializing any user data.
+- `frame_fn fn(voidptr)` - a callback that will be fired on each frame,
+ at a rate of `frame_rate` frames per second.
+`event_fn fn(&Event, voidptr)` - a callback that will be fired for every event received.
+- `cleanup_fn fn(voidptr)` - a callback that will be fired once, before the application exits.
+- `fail_fn fn(string)` - a callback that will be fired
+ if a fatal error occurs during app initialization.
+- `buffer_size int = 256` - the internal size of the read buffer.
+ Increasing it may help in case you're missing events, but you probably shouldn't lower
+ this value unless you make sure you're still receiving all events. In general,
+ higher frame rates work better with lower buffer sizes, and vice versa.
+- `frame_rate int = 30` - the number of times per second that the `frame` callback will be fired.
+ 30fps is a nice balance between smoothness and performance,
+ but you can increase or lower it as you wish.
+- `hide_cursor bool` - whether to hide the mouse cursor. Useful if you want to use your own.
+- `capture_events bool` - sets the terminal into raw mode, which makes it intercept some
+ escape codes such as `ctrl + c` and `ctrl + z`.
+ Useful if you want to use those key combinations in your app.
+- `window_title string` - sets the title of the terminal window.
+ This may be changed later, by calling the `set_window_title()` method.
+- `reset []int = [1, 2, 3, 4, 6, 7, 8, 9, 11, 13, 14, 15, 19]` - a list of reset signals,
+ to setup handlers to cleanup the terminal state when they're received.
+ You should not need to change this, unless you know what you're doing.
+
+All of these fields may be omitted, in which case, the default value will be used.
+In the case of the various callbacks, they will not be fired if a handler has not been specified.
+
+
+#### FAQ
+
+Q: My terminal (doesn't receive events / doesn't print anything / prints gibberish characters),
+what's up with that?
+A: Please check if your terminal. The module has been tested with `xterm`-based terminals on Linux
+(like `gnome-terminal` and `konsole`), and `Terminal.app` and `iterm2` on macOS.
+If your terminal does not work, open an issue with the output of `echo $TERM`.
+
+Q: There are screen tearing issues when doing large prints
+A: This is an issue with how terminals render frames,
+as they may decide to do so in the middle of receiving a frame,
+and cannot be fully fixed unless your console implements the [synchronized updates spec](https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec).
+It can be reduced *drastically*, though, by using the rendering methods built in to the module,
+and by only painting frames when your app's content has actually changed.
+
+Q: Why does the module only emit `keydown` events, and not `keyup` like `sokol`/`gg`?
+A: It's because of the way terminals emit events. Every key event is received as a keypress,
+and there isn't a way of telling terminals to send keyboard events differently,
+nor a reliable way of converting these into `keydown` / `keyup` events.
diff --git a/v_windows/v/old/vlib/term/ui/color.v b/v_windows/v/old/vlib/term/ui/color.v
new file mode 100644
index 0000000..3e0a0bb
--- /dev/null
+++ b/v_windows/v/old/vlib/term/ui/color.v
@@ -0,0 +1,88 @@
+// radare - LGPL - Copyright 2013-2020 - pancake, xarkes
+// ansi 256 color extension for r_cons
+// https://en.wikipedia.org/wiki/ANSI_color
+
+module ui
+
+const (
+ value_range = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]!
+ color_table = init_color_table()
+)
+
+[direct_array_access]
+fn init_color_table() []int {
+ mut color_table_ := []int{len: 256}
+ // ansi colors
+ color_table_[0] = 0x000000
+ color_table_[1] = 0x800000
+ color_table_[2] = 0x008000
+ color_table_[3] = 0x808000
+ color_table_[4] = 0x000080
+ color_table_[5] = 0x800080
+ color_table_[6] = 0x008080
+ color_table_[7] = 0xc0c0c0
+ color_table_[8] = 0x808080
+ color_table_[9] = 0xff0000
+ color_table_[10] = 0x00ff00
+ color_table_[11] = 0xffff00
+ color_table_[12] = 0x0000ff
+ color_table_[13] = 0xff00ff
+ color_table_[14] = 0x00ffff
+ color_table_[15] = 0xffffff
+ // color palette
+ for i in 0 .. 216 {
+ r := ui.value_range[(i / 36) % 6]
+ g := ui.value_range[(i / 6) % 6]
+ b := ui.value_range[i % 6]
+ color_table_[i + 16] = ((r << 16) & 0xffffff) + ((g << 8) & 0xffff) + (b & 0xff)
+ }
+ // grayscale
+ for i in 0 .. 24 {
+ r := 8 + (i * 10)
+ color_table_[i + 232] = ((r << 16) & 0xffffff) + ((r << 8) & 0xffff) + (r & 0xff)
+ }
+ return color_table_
+}
+
+fn clamp(x int, y int, z int) int {
+ if x < y {
+ return y
+ }
+ if x > z {
+ return z
+ }
+ return x
+}
+
+fn approximate_rgb(r int, g int, b int) int {
+ grey := r > 0 && r < 255 && r == g && r == b
+ if grey {
+ return 232 + int(f64(r) / (255 / 24.1))
+ }
+ k := int(256.0 / 6)
+ r2 := clamp(r / k, 0, 5)
+ g2 := clamp(g / k, 0, 5)
+ b2 := clamp(b / k, 0, 5)
+ return 16 + (r2 * 36) + (g2 * 6) + b2
+}
+
+fn lookup_rgb(r int, g int, b int) int {
+ color := (r << 16) + (g << 8) + b
+ // lookup extended colors only, coz non-extended can be changed by users.
+ for i in 16 .. 256 {
+ if ui.color_table[i] == color {
+ return i
+ }
+ }
+ return -1
+}
+
+// converts an RGB color to an ANSI 256-color, approximating it to the nearest available color
+// if an exact match is not found
+fn rgb2ansi(r int, g int, b int) int {
+ c := lookup_rgb(r, g, b)
+ if c == -1 {
+ return approximate_rgb(r, g, b)
+ }
+ return c
+}
diff --git a/v_windows/v/old/vlib/term/ui/consoleapi_windows.c.v b/v_windows/v/old/vlib/term/ui/consoleapi_windows.c.v
new file mode 100644
index 0000000..a6002a6
--- /dev/null
+++ b/v_windows/v/old/vlib/term/ui/consoleapi_windows.c.v
@@ -0,0 +1,82 @@
+module ui
+
+union C.Event {
+ KeyEvent C.KEY_EVENT_RECORD
+ MouseEvent C.MOUSE_EVENT_RECORD
+ WindowBufferSizeEvent C.WINDOW_BUFFER_SIZE_RECORD
+ MenuEvent C.MENU_EVENT_RECORD
+ FocusEvent C.FOCUS_EVENT_RECORD
+}
+
+[typedef]
+struct C.INPUT_RECORD {
+ EventType u16
+ Event C.Event
+}
+
+union C.uChar {
+ UnicodeChar rune
+ AsciiChar byte
+}
+
+[typedef]
+struct C.KEY_EVENT_RECORD {
+ bKeyDown int
+ wRepeatCount u16
+ wVirtualKeyCode u16
+ wVirtualScanCode u16
+ uChar C.uChar
+ dwControlKeyState u32
+}
+
+[typedef]
+struct C.MOUSE_EVENT_RECORD {
+ dwMousePosition C.COORD
+ dwButtonState u32
+ dwControlKeyState u32
+ dwEventFlags u32
+}
+
+[typedef]
+struct C.WINDOW_BUFFER_SIZE_RECORD {
+ dwSize C.COORD
+}
+
+[typedef]
+struct C.MENU_EVENT_RECORD {
+ dwCommandId u32
+}
+
+[typedef]
+struct C.FOCUS_EVENT_RECORD {
+ bSetFocus int
+}
+
+[typedef]
+struct C.COORD {
+ X i16
+ Y i16
+}
+
+[typedef]
+struct C.SMALL_RECT {
+ Left u16
+ Top u16
+ Right u16
+ Bottom u16
+}
+
+[typedef]
+struct C.CONSOLE_SCREEN_BUFFER_INFO {
+ dwSize C.COORD
+ dwCursorPosition C.COORD
+ wAttributes u16
+ srWindow C.SMALL_RECT
+ dwMaximumWindowSize C.COORD
+}
+
+fn C.ReadConsoleInput(hConsoleInput C.HANDLE, lpBuffer &C.INPUT_RECORD, nLength u32, lpNumberOfEventsRead &u32) bool
+
+fn C.GetNumberOfConsoleInputEvents(hConsoleInput C.HANDLE, lpcNumberOfEvents &u32) bool
+
+fn C.GetConsoleScreenBufferInfo(handle C.HANDLE, info &C.CONSOLE_SCREEN_BUFFER_INFO) bool
diff --git a/v_windows/v/old/vlib/term/ui/input.v b/v_windows/v/old/vlib/term/ui/input.v
new file mode 100644
index 0000000..0532b39
--- /dev/null
+++ b/v_windows/v/old/vlib/term/ui/input.v
@@ -0,0 +1,241 @@
+// Copyright (c) 2020-2021 Raúl Hernández. All rights reserved.
+// Use of this source code is governed by an MIT license
+// that can be found in the LICENSE file.
+module ui
+
+import os
+
+pub enum KeyCode {
+ null = 0
+ tab = 9
+ enter = 10
+ escape = 27
+ space = 32
+ backspace = 127
+ exclamation = 33
+ double_quote = 34
+ hashtag = 35
+ dollar = 36
+ percent = 37
+ ampersand = 38
+ single_quote = 39
+ left_paren = 40
+ right_paren = 41
+ asterisk = 42
+ plus = 43
+ comma = 44
+ minus = 45
+ period = 46
+ slash = 47
+ _0 = 48
+ _1 = 49
+ _2 = 50
+ _3 = 51
+ _4 = 52
+ _5 = 53
+ _6 = 54
+ _7 = 55
+ _8 = 56
+ _9 = 57
+ colon = 58
+ semicolon = 59
+ less_than = 60
+ equal = 61
+ greater_than = 62
+ question_mark = 63
+ at = 64
+ a = 97
+ b = 98
+ c = 99
+ d = 100
+ e = 101
+ f = 102
+ g = 103
+ h = 104
+ i = 105
+ j = 106
+ k = 107
+ l = 108
+ m = 109
+ n = 110
+ o = 111
+ p = 112
+ q = 113
+ r = 114
+ s = 115
+ t = 116
+ u = 117
+ v = 118
+ w = 119
+ x = 120
+ y = 121
+ z = 122
+ left_square_bracket = 91
+ backslash = 92
+ right_square_bracket = 93
+ caret = 94
+ underscore = 95
+ backtick = 96
+ left_curly_bracket = 123
+ vertical_bar = 124
+ right_curly_bracket = 125
+ tilde = 126
+ insert = 260
+ delete = 261
+ up = 262
+ down = 263
+ right = 264
+ left = 265
+ page_up = 266
+ page_down = 267
+ home = 268
+ end = 269
+ f1 = 290
+ f2 = 291
+ f3 = 292
+ f4 = 293
+ f5 = 294
+ f6 = 295
+ f7 = 296
+ f8 = 297
+ f9 = 298
+ f10 = 299
+ f11 = 300
+ f12 = 301
+ f13 = 302
+ f14 = 303
+ f15 = 304
+ f16 = 305
+ f17 = 306
+ f18 = 307
+ f19 = 308
+ f20 = 309
+ f21 = 310
+ f22 = 311
+ f23 = 312
+ f24 = 313
+}
+
+pub enum Direction {
+ unknown
+ up
+ down
+ left
+ right
+}
+
+pub enum MouseButton {
+ unknown
+ left
+ middle
+ right
+}
+
+pub enum EventType {
+ unknown
+ mouse_down
+ mouse_up
+ mouse_move
+ mouse_drag
+ mouse_scroll
+ key_down
+ resized
+}
+
+[flag]
+pub enum Modifiers {
+ ctrl
+ shift
+ alt
+}
+
+pub struct Event {
+pub:
+ typ EventType
+ // Mouse event info
+ x int
+ y int
+ button MouseButton
+ direction Direction
+ // Keyboard event info
+ code KeyCode
+ modifiers Modifiers
+ ascii byte
+ utf8 string
+ // Resized event info
+ width int
+ height int
+}
+
+pub struct Context {
+ ExtraContext // contains fields specific to an implementation
+pub:
+ cfg Config // adsasdas
+mut:
+ print_buf []byte
+ paused bool
+ enable_su bool
+ enable_rgb bool
+pub mut:
+ frame_count u64
+ window_width int
+ window_height int
+}
+
+pub struct Config {
+ user_data voidptr
+ init_fn fn (voidptr)
+ frame_fn fn (voidptr)
+ cleanup_fn fn (voidptr)
+ event_fn fn (&Event, voidptr)
+ fail_fn fn (string)
+
+ buffer_size int = 256
+ frame_rate int = 30
+ use_x11 bool
+
+ window_title string
+ hide_cursor bool
+ capture_events bool
+ use_alternate_buffer bool = true
+ skip_init_checks bool
+ // All kill signals to set up exit listeners on:
+ reset []os.Signal = [.hup, .int, .quit, .ill, .abrt, .bus, .fpe, .kill, .segv, .pipe, .alrm, .term,
+ .stop,
+]
+}
+
+[inline]
+fn (ctx &Context) init() {
+ if ctx.cfg.init_fn != voidptr(0) {
+ ctx.cfg.init_fn(ctx.cfg.user_data)
+ }
+}
+
+[inline]
+fn (ctx &Context) frame() {
+ if ctx.cfg.frame_fn != voidptr(0) {
+ ctx.cfg.frame_fn(ctx.cfg.user_data)
+ }
+}
+
+[inline]
+fn (ctx &Context) cleanup() {
+ if ctx.cfg.cleanup_fn != voidptr(0) {
+ ctx.cfg.cleanup_fn(ctx.cfg.user_data)
+ }
+}
+
+[inline]
+fn (ctx &Context) fail(error string) {
+ if ctx.cfg.fail_fn != voidptr(0) {
+ ctx.cfg.fail_fn(error)
+ }
+}
+
+[inline]
+fn (ctx &Context) event(event &Event) {
+ if ctx.cfg.event_fn != voidptr(0) {
+ ctx.cfg.event_fn(event, ctx.cfg.user_data)
+ }
+}
diff --git a/v_windows/v/old/vlib/term/ui/input_nix.c.v b/v_windows/v/old/vlib/term/ui/input_nix.c.v
new file mode 100644
index 0000000..e806fb8
--- /dev/null
+++ b/v_windows/v/old/vlib/term/ui/input_nix.c.v
@@ -0,0 +1,70 @@
+// Copyright (c) 2020-2021 Raúl Hernández. All rights reserved.
+// Use of this source code is governed by an MIT license
+// that can be found in the LICENSE file.
+module ui
+
+struct ExtraContext {
+mut:
+ read_buf []byte
+}
+
+const (
+ ctx_ptr = &Context(0)
+)
+
+pub fn init(cfg Config) &Context {
+ mut ctx := &Context{
+ cfg: cfg
+ }
+ ctx.read_buf = []byte{cap: cfg.buffer_size}
+
+ // lmao
+ unsafe {
+ x := &ui.ctx_ptr
+ *x = ctx
+ _ = x
+ }
+ return ctx
+}
+
+[inline]
+fn save_title() {
+ // restore the previously saved terminal title
+ print('\x1b[22;0t')
+}
+
+[inline]
+fn load_title() {
+ // restore the previously saved terminal title
+ print('\x1b[23;0t')
+}
+
+pub fn (mut ctx Context) run() ? {
+ if ctx.cfg.use_x11 {
+ ctx.fail('error: x11 backend not implemented yet')
+ exit(1)
+ } else {
+ ctx.termios_setup() ?
+ ctx.termios_loop()
+ }
+}
+
+// shifts the array left, to remove any data that was just read, and updates its len
+// TODO: remove
+[inline]
+fn (mut ctx Context) shift(len int) {
+ unsafe {
+ C.memmove(ctx.read_buf.data, &byte(ctx.read_buf.data) + len, ctx.read_buf.cap - len)
+ ctx.resize_arr(ctx.read_buf.len - len)
+ }
+}
+
+// TODO: don't actually do this, lmao
+[inline]
+fn (mut ctx Context) resize_arr(size int) {
+ mut l := unsafe { &ctx.read_buf.len }
+ unsafe {
+ *l = size
+ _ = l
+ }
+}
diff --git a/v_windows/v/old/vlib/term/ui/input_windows.c.v b/v_windows/v/old/vlib/term/ui/input_windows.c.v
new file mode 100644
index 0000000..bd9782d
--- /dev/null
+++ b/v_windows/v/old/vlib/term/ui/input_windows.c.v
@@ -0,0 +1,326 @@
+// Copyright (c) 2020-2021 Raúl Hernández. All rights reserved.
+// Use of this source code is governed by an MIT license
+// that can be found in the LICENSE file.
+module ui
+
+import os
+import time
+
+const (
+ buf_size = 64
+ ctx_ptr = &Context(0)
+ stdin_at_startup = u32(0)
+)
+
+struct ExtraContext {
+mut:
+ stdin_handle C.HANDLE
+ stdout_handle C.HANDLE
+ read_buf [buf_size]C.INPUT_RECORD
+ mouse_down MouseButton
+}
+
+fn restore_terminal_state() {
+ if ui.ctx_ptr != 0 {
+ if ui.ctx_ptr.cfg.use_alternate_buffer {
+ // clear the terminal and set the cursor to the origin
+ print('\x1b[2J\x1b[3J')
+ print('\x1b[?1049l')
+ }
+ C.SetConsoleMode(ui.ctx_ptr.stdin_handle, ui.stdin_at_startup)
+ }
+ load_title()
+ os.flush()
+}
+
+pub fn init(cfg Config) &Context {
+ mut ctx := &Context{
+ cfg: cfg
+ }
+ // get the standard input handle
+ stdin_handle := C.GetStdHandle(C.STD_INPUT_HANDLE)
+ stdout_handle := C.GetStdHandle(C.STD_OUTPUT_HANDLE)
+ if stdin_handle == C.INVALID_HANDLE_VALUE {
+ panic('could not get stdin handle')
+ }
+ // save the current input mode, to be restored on exit
+ if C.GetConsoleMode(stdin_handle, &ui.stdin_at_startup) == 0 {
+ panic('could not get stdin console mode')
+ }
+
+ // enable extended input flags (see https://stackoverflow.com/a/46802726)
+ // 0x80 == C.ENABLE_EXTENDED_FLAGS
+ if C.SetConsoleMode(stdin_handle, 0x80) == 0 {
+ panic('could not set raw input mode')
+ }
+ // enable window and mouse input events.
+ if C.SetConsoleMode(stdin_handle, C.ENABLE_WINDOW_INPUT | C.ENABLE_MOUSE_INPUT) == 0 {
+ panic('could not set raw input mode')
+ }
+ // store the current title, so restore_terminal_state can get it back
+ save_title()
+
+ if ctx.cfg.use_alternate_buffer {
+ // switch to the alternate buffer
+ print('\x1b[?1049h')
+ // clear the terminal and set the cursor to the origin
+ print('\x1b[2J\x1b[3J\x1b[1;1H')
+ }
+
+ if ctx.cfg.hide_cursor {
+ ctx.hide_cursor()
+ ctx.flush()
+ }
+
+ if ctx.cfg.window_title != '' {
+ print('\x1b]0;$ctx.cfg.window_title\x07')
+ }
+
+ unsafe {
+ x := &ui.ctx_ptr
+ *x = ctx
+ }
+ C.atexit(restore_terminal_state)
+ for code in ctx.cfg.reset {
+ os.signal_opt(code, fn (_ os.Signal) {
+ mut c := ui.ctx_ptr
+ if c != 0 {
+ c.cleanup()
+ }
+ exit(0)
+ }) or {}
+ }
+
+ ctx.stdin_handle = stdin_handle
+ ctx.stdout_handle = stdout_handle
+ return ctx
+}
+
+pub fn (mut ctx Context) run() ? {
+ frame_time := 1_000_000 / ctx.cfg.frame_rate
+ mut init_called := false
+ mut sw := time.new_stopwatch(auto_start: false)
+ mut sleep_len := 0
+ for {
+ if !init_called {
+ ctx.init()
+ init_called = true
+ }
+ if sleep_len > 0 {
+ time.sleep(sleep_len * time.microsecond)
+ }
+ if !ctx.paused {
+ sw.restart()
+ if ctx.cfg.event_fn != voidptr(0) {
+ ctx.parse_events()
+ }
+ ctx.frame()
+ sw.pause()
+ e := sw.elapsed().microseconds()
+ sleep_len = frame_time - int(e)
+ ctx.frame_count++
+ }
+ }
+}
+
+fn (mut ctx Context) parse_events() {
+ nr_events := u32(0)
+ if !C.GetNumberOfConsoleInputEvents(ctx.stdin_handle, &nr_events) {
+ panic('could not get number of events in stdin')
+ }
+ if nr_events < 1 {
+ return
+ }
+
+ // print('$nr_events | ')
+ if !C.ReadConsoleInput(ctx.stdin_handle, &ctx.read_buf[0], ui.buf_size, &nr_events) {
+ panic('could not read from stdin')
+ }
+ for i in 0 .. nr_events {
+ // print('E ')
+ match int(ctx.read_buf[i].EventType) {
+ C.KEY_EVENT {
+ e := unsafe { ctx.read_buf[i].Event.KeyEvent }
+ ch := e.wVirtualKeyCode
+ ascii := unsafe { e.uChar.AsciiChar }
+ if e.bKeyDown == 0 {
+ continue
+ }
+ // we don't handle key_up events because they don't exist on linux...
+ // see: https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
+ code := match int(ch) {
+ C.VK_BACK { KeyCode.backspace }
+ C.VK_RETURN { KeyCode.enter }
+ C.VK_PRIOR { KeyCode.page_up }
+ 14...20 { KeyCode.null }
+ C.VK_NEXT { KeyCode.page_down }
+ C.VK_END { KeyCode.end }
+ C.VK_HOME { KeyCode.home }
+ C.VK_LEFT { KeyCode.left }
+ C.VK_UP { KeyCode.up }
+ C.VK_RIGHT { KeyCode.right }
+ C.VK_DOWN { KeyCode.down }
+ C.VK_INSERT { KeyCode.insert }
+ C.VK_DELETE { KeyCode.delete }
+ 65...90 { KeyCode(ch + 32) } // letters
+ 91...93 { KeyCode.null } // special keys
+ 96...105 { KeyCode(ch - 48) } // numpad numbers
+ 112...135 { KeyCode(ch + 178) } // f1 - f24
+ else { KeyCode(ascii) }
+ }
+
+ mut modifiers := Modifiers{}
+ if e.dwControlKeyState & (0x1 | 0x2) != 0 {
+ modifiers.set(.alt)
+ }
+ if e.dwControlKeyState & (0x4 | 0x8) != 0 {
+ modifiers.set(.ctrl)
+ }
+ if e.dwControlKeyState & 0x10 != 0 {
+ modifiers.set(.shift)
+ }
+
+ mut event := &Event{
+ typ: .key_down
+ modifiers: modifiers
+ code: code
+ ascii: ascii
+ width: int(e.dwControlKeyState)
+ height: int(e.wVirtualKeyCode)
+ utf8: unsafe { e.uChar.UnicodeChar.str() }
+ }
+ ctx.event(event)
+ }
+ C.MOUSE_EVENT {
+ e := unsafe { ctx.read_buf[i].Event.MouseEvent }
+ sb_info := C.CONSOLE_SCREEN_BUFFER_INFO{}
+ if !C.GetConsoleScreenBufferInfo(ctx.stdout_handle, &sb_info) {
+ panic('could not get screenbuffer info')
+ }
+ x := e.dwMousePosition.X + 1
+ y := int(e.dwMousePosition.Y) - sb_info.srWindow.Top + 1
+ mut modifiers := Modifiers{}
+ if e.dwControlKeyState & (0x1 | 0x2) != 0 {
+ modifiers.set(.alt)
+ }
+ if e.dwControlKeyState & (0x4 | 0x8) != 0 {
+ modifiers.set(.ctrl)
+ }
+ if e.dwControlKeyState & 0x10 != 0 {
+ modifiers.set(.shift)
+ }
+ // TODO: handle capslock/numlock/etc?? events exist for those keys
+ match int(e.dwEventFlags) {
+ C.MOUSE_MOVED {
+ mut button := match int(e.dwButtonState) {
+ 0 { MouseButton.unknown }
+ 1 { MouseButton.left }
+ 2 { MouseButton.right }
+ else { MouseButton.middle }
+ }
+ typ := if e.dwButtonState == 0 {
+ if ctx.mouse_down != .unknown {
+ button = ctx.mouse_down
+ ctx.mouse_down = .unknown
+ EventType.mouse_up
+ } else {
+ EventType.mouse_move
+ }
+ } else {
+ EventType.mouse_drag
+ }
+ ctx.event(&Event{
+ typ: typ
+ x: x
+ y: y
+ button: button
+ modifiers: modifiers
+ })
+ }
+ C.MOUSE_WHEELED {
+ ctx.event(&Event{
+ typ: .mouse_scroll
+ direction: if i16(e.dwButtonState >> 16) < 0 {
+ Direction.up
+ } else {
+ Direction.down
+ }
+ x: x
+ y: y
+ modifiers: modifiers
+ })
+ }
+ 0x0008 /* C.MOUSE_HWHEELED */ {
+ ctx.event(&Event{
+ typ: .mouse_scroll
+ direction: if i16(e.dwButtonState >> 16) < 0 {
+ Direction.right
+ } else {
+ Direction.left
+ }
+ x: x
+ y: y
+ modifiers: modifiers
+ })
+ }
+ 0 /* CLICK */, C.DOUBLE_CLICK {
+ button := match int(e.dwButtonState) {
+ 0 { ctx.mouse_down }
+ 1 { MouseButton.left }
+ 2 { MouseButton.right }
+ else { MouseButton.middle }
+ }
+ ctx.mouse_down = button
+ ctx.event(&Event{
+ typ: .mouse_down
+ x: x
+ y: y
+ button: button
+ modifiers: modifiers
+ })
+ }
+ else {}
+ }
+ }
+ C.WINDOW_BUFFER_SIZE_EVENT {
+ // e := unsafe { ctx.read_buf[i].Event.WindowBufferSizeEvent }
+ sb := C.CONSOLE_SCREEN_BUFFER_INFO{}
+ if !C.GetConsoleScreenBufferInfo(ctx.stdout_handle, &sb) {
+ panic('could not get screenbuffer info')
+ }
+ w := sb.srWindow.Right - sb.srWindow.Left + 1
+ h := sb.srWindow.Bottom - sb.srWindow.Top + 1
+ utf8 := '($ctx.window_width, $ctx.window_height) -> ($w, $h)'
+ if w != ctx.window_width || h != ctx.window_height {
+ ctx.window_width, ctx.window_height = w, h
+ mut event := &Event{
+ typ: .resized
+ width: ctx.window_width
+ height: ctx.window_height
+ utf8: utf8
+ }
+ ctx.event(event)
+ }
+ }
+ // C.MENU_EVENT {
+ // e := unsafe { ctx.read_buf[i].Event.MenuEvent }
+ // }
+ // C.FOCUS_EVENT {
+ // e := unsafe { ctx.read_buf[i].Event.FocusEvent }
+ // }
+ else {}
+ }
+ }
+}
+
+[inline]
+fn save_title() {
+ // restore the previously saved terminal title
+ print('\x1b[22;0t')
+}
+
+[inline]
+fn load_title() {
+ // restore the previously saved terminal title
+ print('\x1b[23;0t')
+}
diff --git a/v_windows/v/old/vlib/term/ui/termios_nix.c.v b/v_windows/v/old/vlib/term/ui/termios_nix.c.v
new file mode 100644
index 0000000..fb5ff76
--- /dev/null
+++ b/v_windows/v/old/vlib/term/ui/termios_nix.c.v
@@ -0,0 +1,530 @@
+// Copyright (c) 2020-2021 Raúl Hernández. All rights reserved.
+// Use of this source code is governed by an MIT license
+// that can be found in the LICENSE file.
+module ui
+
+import os
+import time
+
+#include <termios.h>
+#include <sys/ioctl.h>
+#include <signal.h>
+
+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()
+)
+
+[inline]
+fn get_termios() C.termios {
+ mut t := C.termios{}
+ C.tcgetattr(C.STDIN_FILENO, &t)
+ return t
+}
+
+[inline]
+fn get_terminal_size() (u16, u16) {
+ winsz := C.winsize{}
+ C.ioctl(0, C.TIOCGWINSZ, &winsz)
+ return winsz.ws_row, winsz.ws_col
+}
+
+fn restore_terminal_state_signal(_ os.Signal) {
+ restore_terminal_state()
+}
+
+fn restore_terminal_state() {
+ termios_reset()
+ mut c := ctx_ptr
+ if c != 0 {
+ c.paused = true
+ load_title()
+ }
+ os.flush()
+}
+
+fn (mut ctx Context) termios_setup() ? {
+ // store the current title, so restore_terminal_state can get it back
+ save_title()
+
+ if !ctx.cfg.skip_init_checks && !(os.is_atty(C.STDIN_FILENO) != 0
+ && os.is_atty(C.STDOUT_FILENO) != 0) {
+ return error('not running under a TTY')
+ }
+
+ mut termios := 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 &= ~u32(C.IGNBRK | C.BRKINT | C.PARMRK | C.IXON)
+ termios.c_lflag &= ~u32(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)
+ }
+
+ if ctx.cfg.hide_cursor {
+ ctx.hide_cursor()
+ ctx.flush()
+ }
+
+ if ctx.cfg.window_title != '' {
+ print('\x1b]0;$ctx.cfg.window_title\x07')
+ }
+
+ 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)
+ // feature-test the SU spec
+ sx, sy := get_cursor_position()
+ print('$bsu$esu')
+ ex, ey := get_cursor_position()
+ if sx == ex && sy == ey {
+ // the terminal either ignored or handled the sequence properly, enable SU
+ ctx.enable_su = true
+ } else {
+ ctx.draw_line(sx, sy, ex, ey)
+ ctx.set_cursor_position(sx, sy)
+ ctx.flush()
+ }
+ // feature-test rgb (truecolor) support
+ 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)
+ // enable mouse input
+ print('\x1b[?1003h\x1b[?1006h')
+ if ctx.cfg.use_alternate_buffer {
+ // switch to the alternate buffer
+ print('\x1b[?1049h')
+ // clear the terminal and set the cursor to the origin
+ print('\x1b[2J\x1b[3J\x1b[1;1H')
+ }
+ ctx.window_height, ctx.window_width = get_terminal_size()
+
+ // Reset console on exit
+ C.atexit(restore_terminal_state)
+ os.signal_opt(.tstp, restore_terminal_state_signal) or {}
+ os.signal_opt(.cont, fn (_ os.Signal) {
+ mut c := ctx_ptr
+ if c != 0 {
+ c.termios_setup() or { panic(err) }
+ c.window_height, c.window_width = get_terminal_size()
+ mut event := &Event{
+ typ: .resized
+ width: c.window_width
+ height: c.window_height
+ }
+ c.paused = false
+ c.event(event)
+ }
+ }) or {}
+ for code in ctx.cfg.reset {
+ os.signal_opt(code, fn (_ os.Signal) {
+ mut c := ctx_ptr
+ if c != 0 {
+ c.cleanup()
+ }
+ exit(0)
+ }) or {}
+ }
+
+ os.signal_opt(.winch, fn (_ os.Signal) {
+ mut c := ctx_ptr
+ if c != 0 {
+ c.window_height, c.window_width = get_terminal_size()
+
+ mut event := &Event{
+ typ: .resized
+ width: c.window_width
+ height: c.window_height
+ }
+ c.event(event)
+ }
+ }) or {}
+
+ os.flush()
+}
+
+fn get_cursor_position() (int, int) {
+ print('\033[6n')
+ mut s := ''
+ unsafe {
+ buf := malloc_noscan(25)
+ len := C.read(C.STDIN_FILENO, buf, 24)
+ buf[len] = 0
+ s = tos(buf, len)
+ }
+ a := s[2..].split(';')
+ if a.len != 2 {
+ return -1, -1
+ }
+ return a[0].int(), a[1].int()
+}
+
+fn supports_truecolor() bool {
+ // faster/simpler, but less reliable, check
+ if os.getenv('COLORTERM') in ['truecolor', '24bit'] {
+ return true
+ }
+ // set the bg color to some arbirtrary value (#010203), assumed not to be the default
+ print('\x1b[48:2:1:2:3m')
+ // andquery the current color
+ print('\x1bP\$qm\x1b\\')
+ mut s := ''
+ unsafe {
+ buf := malloc_noscan(25)
+ len := C.read(C.STDIN_FILENO, buf, 24)
+ buf[len] = 0
+ s = tos(buf, len)
+ }
+ return s.contains('1:2:3')
+}
+
+fn termios_reset() {
+ // C.TCSANOW ??
+ C.tcsetattr(C.STDIN_FILENO, C.TCSAFLUSH, &ui.termios_at_startup)
+ print('\x1b[?1003l\x1b[?1006l\x1b[?25h')
+ c := ctx_ptr
+ if c != 0 && c.cfg.use_alternate_buffer {
+ print('\x1b[?1049l')
+ }
+ os.flush()
+}
+
+///////////////////////////////////////////
+// TODO: do multiple sleep/read cycles, rather than one big one
+fn (mut ctx Context) termios_loop() {
+ frame_time := 1_000_000 / ctx.cfg.frame_rate
+ mut init_called := false
+ mut sw := time.new_stopwatch(auto_start: false)
+ mut sleep_len := 0
+ for {
+ if !init_called {
+ ctx.init()
+ init_called = true
+ }
+ // println('SLEEPING: $sleep_len')
+ if sleep_len > 0 {
+ time.sleep(sleep_len * time.microsecond)
+ }
+ if !ctx.paused {
+ sw.restart()
+ if ctx.cfg.event_fn != voidptr(0) {
+ unsafe {
+ len := C.read(C.STDIN_FILENO, &byte(ctx.read_buf.data) + ctx.read_buf.len,
+ ctx.read_buf.cap - ctx.read_buf.len)
+ ctx.resize_arr(ctx.read_buf.len + len)
+ }
+ if ctx.read_buf.len > 0 {
+ ctx.parse_events()
+ }
+ }
+ ctx.frame()
+ sw.pause()
+ e := sw.elapsed().microseconds()
+ sleep_len = frame_time - int(e)
+
+ ctx.frame_count++
+ }
+ }
+}
+
+fn (mut ctx Context) parse_events() {
+ // Stop this from getting stuck in rare cases where something isn't parsed correctly
+ mut nr_iters := 0
+ for ctx.read_buf.len > 0 {
+ nr_iters++
+ if nr_iters > 100 {
+ ctx.shift(1)
+ }
+ mut event := &Event(0)
+ if ctx.read_buf[0] == 0x1b {
+ e, len := escape_sequence(ctx.read_buf.bytestr())
+ event = e
+ ctx.shift(len)
+ } else {
+ event = single_char(ctx.read_buf.bytestr())
+ ctx.shift(1)
+ }
+ if event != 0 {
+ ctx.event(event)
+ nr_iters = 0
+ }
+ }
+}
+
+fn single_char(buf string) &Event {
+ ch := buf[0]
+
+ mut event := &Event{
+ typ: .key_down
+ ascii: ch
+ code: KeyCode(ch)
+ utf8: buf
+ }
+
+ match ch {
+ // special handling for `ctrl + letter`
+ // TODO: Fix assoc in V and remove this workaround :/
+ // 1 ... 26 { event = Event{ ...event, code: KeyCode(96 | ch), modifiers: .ctrl } }
+ // 65 ... 90 { event = Event{ ...event, code: KeyCode(32 | ch), modifiers: .shift } }
+ // The bit `or`s here are really just `+`'s, just written in this way for a tiny performance improvement
+ // don't treat tab, enter as ctrl+i, ctrl+j
+ 1...8, 11...26 {
+ event = &Event{
+ typ: event.typ
+ ascii: event.ascii
+ utf8: event.utf8
+ code: KeyCode(96 | ch)
+ modifiers: .ctrl
+ }
+ }
+ 65...90 {
+ event = &Event{
+ typ: event.typ
+ ascii: event.ascii
+ utf8: event.utf8
+ code: KeyCode(32 | ch)
+ modifiers: .shift
+ }
+ }
+ else {}
+ }
+
+ return event
+}
+
+// Gets an entire, independent escape sequence from the buffer
+// Normally, this just means reading until the first letter, but there are some exceptions...
+fn escape_end(buf string) int {
+ mut i := 0
+ for {
+ if i + 1 == buf.len {
+ return buf.len
+ }
+
+ if buf[i].is_letter() || buf[i] == `~` {
+ if buf[i] == `O` && i + 2 <= buf.len {
+ n := buf[i + 1]
+ if (n >= `A` && n <= `D`) || (n >= `P` && n <= `S`) || n == `F` || n == `H` {
+ return i + 2
+ }
+ }
+ return i + 1
+ // escape hatch to avoid potential issues/crashes, although ideally this should never eval to true
+ } else if buf[i + 1] == 0x1b {
+ return i + 1
+ }
+ i++
+ }
+ // this point should be unreachable
+ assert false
+ return 0
+}
+
+fn escape_sequence(buf_ string) (&Event, int) {
+ end := escape_end(buf_)
+ single := buf_[..end] // read until the end of the sequence
+ buf := single[1..] // skip the escape character
+
+ if buf.len == 0 {
+ return &Event{
+ typ: .key_down
+ ascii: 27
+ code: .escape
+ utf8: single
+ }, 1
+ }
+
+ if buf.len == 1 {
+ c := single_char(buf)
+ mut modifiers := c.modifiers
+ modifiers.set(.alt)
+ return &Event{
+ typ: c.typ
+ ascii: c.ascii
+ code: c.code
+ utf8: single
+ modifiers: modifiers
+ }, 2
+ }
+ // ----------------
+ // Mouse events
+ // ----------------
+ // Documentation: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
+ if buf.len > 2 && buf[1] == `<` {
+ split := buf[2..].split(';')
+ if split.len < 3 {
+ return &Event(0), 0
+ }
+
+ typ, x, y := split[0].int(), split[1].int(), split[2].int()
+ lo := typ & 0b00011
+ hi := typ & 0b11100
+
+ mut modifiers := Modifiers{}
+ if hi & 4 != 0 {
+ modifiers.set(.shift)
+ }
+ if hi & 8 != 0 {
+ modifiers.set(.alt)
+ }
+ if hi & 16 != 0 {
+ modifiers.set(.ctrl)
+ }
+
+ match typ {
+ 0...31 {
+ last := buf[buf.len - 1]
+ button := if lo < 3 { MouseButton(lo + 1) } else { MouseButton.unknown }
+ event := if last == `m` || lo == 3 {
+ EventType.mouse_up
+ } else {
+ EventType.mouse_down
+ }
+
+ return &Event{
+ typ: event
+ x: x
+ y: y
+ button: button
+ modifiers: modifiers
+ utf8: single
+ }, end
+ }
+ 32...63 {
+ button, event := if lo < 3 {
+ MouseButton(lo + 1), EventType.mouse_drag
+ } else {
+ MouseButton.unknown, EventType.mouse_move
+ }
+
+ return &Event{
+ typ: event
+ x: x
+ y: y
+ button: button
+ modifiers: modifiers
+ utf8: single
+ }, end
+ }
+ 64...95 {
+ direction := if typ & 1 == 0 { Direction.down } else { Direction.up }
+ return &Event{
+ typ: .mouse_scroll
+ x: x
+ y: y
+ direction: direction
+ modifiers: modifiers
+ utf8: single
+ }, end
+ }
+ else {
+ return &Event{
+ typ: .unknown
+ utf8: single
+ }, end
+ }
+ }
+ }
+ // ----------------------------
+ // Special key combinations
+ // ----------------------------
+
+ mut code := KeyCode.null
+ mut modifiers := Modifiers{}
+ match buf {
+ '[A', 'OA' { code = .up }
+ '[B', 'OB' { code = .down }
+ '[C', 'OC' { code = .right }
+ '[D', 'OD' { code = .left }
+ '[5~', '[[5~' { code = .page_up }
+ '[6~', '[[6~' { code = .page_down }
+ '[F', 'OF', '[4~', '[[8~' { code = .end }
+ '[H', 'OH', '[1~', '[[7~' { code = .home }
+ '[2~' { code = .insert }
+ '[3~' { code = .delete }
+ 'OP', '[11~' { code = .f1 }
+ 'OQ', '[12~' { code = .f2 }
+ 'OR', '[13~' { code = .f3 }
+ 'OS', '[14~' { code = .f4 }
+ '[15~' { code = .f5 }
+ '[17~' { code = .f6 }
+ '[18~' { code = .f7 }
+ '[19~' { code = .f8 }
+ '[20~' { code = .f9 }
+ '[21~' { code = .f10 }
+ '[23~' { code = .f11 }
+ '[24~' { code = .f12 }
+ else {}
+ }
+
+ if buf == '[Z' {
+ code = .tab
+ modifiers.set(.shift)
+ }
+
+ if buf.len == 5 && buf[0] == `[` && buf[1].is_digit() && buf[2] == `;` {
+ match buf[3] {
+ `2` { modifiers = .shift }
+ `3` { modifiers = .alt }
+ `4` { modifiers = .shift | .alt }
+ `5` { modifiers = .ctrl }
+ `6` { modifiers = .ctrl | .shift }
+ `7` { modifiers = .ctrl | .alt }
+ `8` { modifiers = .ctrl | .alt | .shift }
+ else {}
+ }
+
+ if buf[1] == `1` {
+ match buf[4] {
+ `A` { code = KeyCode.up }
+ `B` { code = KeyCode.down }
+ `C` { code = KeyCode.right }
+ `D` { code = KeyCode.left }
+ `F` { code = KeyCode.end }
+ `H` { code = KeyCode.home }
+ `P` { code = KeyCode.f1 }
+ `Q` { code = KeyCode.f2 }
+ `R` { code = KeyCode.f3 }
+ `S` { code = KeyCode.f4 }
+ else {}
+ }
+ } else if buf[1] == `5` {
+ code = KeyCode.page_up
+ } else if buf[1] == `6` {
+ code = KeyCode.page_down
+ }
+ }
+
+ return &Event{
+ typ: .key_down
+ code: code
+ utf8: single
+ modifiers: modifiers
+ }, end
+}
diff --git a/v_windows/v/old/vlib/term/ui/ui.v b/v_windows/v/old/vlib/term/ui/ui.v
new file mode 100644
index 0000000..6ba3d7c
--- /dev/null
+++ b/v_windows/v/old/vlib/term/ui/ui.v
@@ -0,0 +1,256 @@
+// Copyright (c) 2020-2021 Raúl Hernández. All rights reserved.
+// Use of this source code is governed by an MIT license
+// that can be found in the LICENSE file.
+module ui
+
+import strings
+
+pub struct Color {
+pub:
+ r byte
+ g byte
+ b byte
+}
+
+pub fn (c Color) hex() string {
+ return '#$c.r.hex()$c.g.hex()$c.b.hex()'
+}
+
+// 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\\'
+)
+
+// write puts the string `s` into the print buffer.
+[inline]
+pub fn (mut ctx Context) write(s string) {
+ if s == '' {
+ return
+ }
+ unsafe { ctx.print_buf.push_many(s.str, s.len) }
+}
+
+// flush displays the accumulated print buffer to the screen.
+[inline]
+pub fn (mut ctx Context) flush() {
+ // TODO: Diff the previous frame against this one, and only render things that changed?
+ if !ctx.enable_su {
+ C.write(1, ctx.print_buf.data, ctx.print_buf.len)
+ } else {
+ C.write(1, ui.bsu.str, ui.bsu.len)
+ C.write(1, ctx.print_buf.data, ctx.print_buf.len)
+ C.write(1, ui.esu.str, ui.esu.len)
+ }
+ ctx.print_buf.clear()
+}
+
+// bold sets the character state to bold.
+[inline]
+pub fn (mut ctx Context) bold() {
+ ctx.write('\x1b[1m')
+}
+
+// set_cursor_position positions the cusor at the given coordinates `x`,`y`.
+[inline]
+pub fn (mut ctx Context) set_cursor_position(x int, y int) {
+ ctx.write('\x1b[$y;${x}H')
+}
+
+// show_cursor will make the cursor appear if it is not already visible
+[inline]
+pub fn (mut ctx Context) show_cursor() {
+ ctx.write('\x1b[?25h')
+}
+
+// hide_cursor will make the cursor invisible
+[inline]
+pub fn (mut ctx Context) hide_cursor() {
+ ctx.write('\x1b[?25l')
+}
+
+// set_color sets the current foreground color used by any succeeding `draw_*` calls.
+[inline]
+pub fn (mut ctx Context) set_color(c Color) {
+ if ctx.enable_rgb {
+ ctx.write('\x1b[38;2;${int(c.r)};${int(c.g)};${int(c.b)}m')
+ } else {
+ ctx.write('\x1b[38;5;${rgb2ansi(c.r, c.g, c.b)}m')
+ }
+}
+
+// set_color sets the current background color used by any succeeding `draw_*` calls.
+[inline]
+pub fn (mut ctx Context) set_bg_color(c Color) {
+ if ctx.enable_rgb {
+ ctx.write('\x1b[48;2;${int(c.r)};${int(c.g)};${int(c.b)}m')
+ } else {
+ ctx.write('\x1b[48;5;${rgb2ansi(c.r, c.g, c.b)}m')
+ }
+}
+
+// reset_color sets the current foreground color back to it's default value.
+[inline]
+pub fn (mut ctx Context) reset_color() {
+ ctx.write('\x1b[39m')
+}
+
+// reset_bg_color sets the current background color back to it's default value.
+[inline]
+pub fn (mut ctx Context) reset_bg_color() {
+ ctx.write('\x1b[49m')
+}
+
+// reset restores the state of all colors and text formats back to their default values.
+[inline]
+pub fn (mut ctx Context) reset() {
+ ctx.write('\x1b[0m')
+}
+
+[inline]
+pub fn (mut ctx Context) clear() {
+ ctx.write('\x1b[2J\x1b[3J')
+}
+
+// set_window_title sets the string `s` as the window title.
+[inline]
+pub fn (mut ctx Context) set_window_title(s string) {
+ print('\x1b]0;$s\x07')
+}
+
+// draw_point draws a point at position `x`,`y`.
+[inline]
+pub fn (mut ctx Context) draw_point(x int, y int) {
+ ctx.set_cursor_position(x, y)
+ ctx.write(' ')
+}
+
+// draw_text draws the string `s`, starting from position `x`,`y`.
+[inline]
+pub fn (mut ctx Context) draw_text(x int, y int, s string) {
+ ctx.set_cursor_position(x, y)
+ ctx.write(s)
+}
+
+// draw_line draws a line segment, starting at point `x`,`y`, and ending at point `x2`,`y2`.
+pub fn (mut ctx Context) draw_line(x int, y int, x2 int, y2 int) {
+ min_x, min_y := if x < x2 { x } else { x2 }, if y < y2 { y } else { y2 }
+ max_x, _ := if x > x2 { x } else { x2 }, if y > y2 { y } else { y2 }
+ if y == y2 {
+ // Horizontal line, performance improvement
+ ctx.set_cursor_position(min_x, min_y)
+ ctx.write(strings.repeat(` `, max_x + 1 - min_x))
+ return
+ }
+ // Draw the various points with Bresenham's line algorithm:
+ mut x0, x1 := x, x2
+ mut y0, y1 := y, y2
+ sx := if x0 < x1 { 1 } else { -1 }
+ sy := if y0 < y1 { 1 } else { -1 }
+ dx := if x0 < x1 { x1 - x0 } else { x0 - x1 }
+ dy := if y0 < y1 { y0 - y1 } else { y1 - y0 } // reversed
+ mut err := dx + dy
+ for {
+ // res << Segment{ x0, y0 }
+ ctx.draw_point(x0, y0)
+ if x0 == x1 && y0 == y1 {
+ break
+ }
+ e2 := 2 * err
+ if e2 >= dy {
+ err += dy
+ x0 += sx
+ }
+ if e2 <= dx {
+ err += dx
+ y0 += sy
+ }
+ }
+}
+
+// draw_dashed_line draws a dashed line segment, starting at point `x`,`y`, and ending at point `x2`,`y2`.
+pub fn (mut ctx Context) draw_dashed_line(x int, y int, x2 int, y2 int) {
+ // Draw the various points with Bresenham's line algorithm:
+ mut x0, x1 := x, x2
+ mut y0, y1 := y, y2
+ sx := if x0 < x1 { 1 } else { -1 }
+ sy := if y0 < y1 { 1 } else { -1 }
+ dx := if x0 < x1 { x1 - x0 } else { x0 - x1 }
+ dy := if y0 < y1 { y0 - y1 } else { y1 - y0 } // reversed
+ mut err := dx + dy
+ mut i := 0
+ for {
+ if i % 2 == 0 {
+ ctx.draw_point(x0, y0)
+ }
+ if x0 == x1 && y0 == y1 {
+ break
+ }
+ e2 := 2 * err
+ if e2 >= dy {
+ err += dy
+ x0 += sx
+ }
+ if e2 <= dx {
+ err += dx
+ y0 += sy
+ }
+ i++
+ }
+}
+
+// draw_rect draws a rectangle, starting at top left `x`,`y`, and ending at bottom right `x2`,`y2`.
+pub fn (mut ctx Context) draw_rect(x int, y int, x2 int, y2 int) {
+ if y == y2 || x == x2 {
+ ctx.draw_line(x, y, x2, y2)
+ return
+ }
+ min_y, max_y := if y < y2 { y, y2 } else { y2, y }
+ for y_pos in min_y .. max_y + 1 {
+ ctx.draw_line(x, y_pos, x2, y_pos)
+ }
+}
+
+// draw_empty_dashed_rect draws a rectangle with dashed lines, starting at top left `x`,`y`, and ending at bottom right `x2`,`y2`.
+pub fn (mut ctx Context) draw_empty_dashed_rect(x int, y int, x2 int, y2 int) {
+ if y == y2 || x == x2 {
+ ctx.draw_dashed_line(x, y, x2, y2)
+ return
+ }
+
+ min_x, max_x := if x < x2 { x, x2 } else { x2, x }
+ min_y, max_y := if y < y2 { y, y2 } else { y2, y }
+
+ ctx.draw_dashed_line(min_x, min_y, max_x, min_y)
+ ctx.draw_dashed_line(min_x, min_y, min_x, max_y)
+ if (max_y - min_y) & 1 == 0 {
+ ctx.draw_dashed_line(min_x, max_y, max_x, max_y)
+ } else {
+ ctx.draw_dashed_line(min_x + 1, max_y, max_x, max_y)
+ }
+ if (max_x - min_x) & 1 == 0 {
+ ctx.draw_dashed_line(max_x, min_y, max_x, max_y)
+ } else {
+ ctx.draw_dashed_line(max_x, min_y + 1, max_x, max_y)
+ }
+}
+
+// draw_empty_rect draws a rectangle with no fill, starting at top left `x`,`y`, and ending at bottom right `x2`,`y2`.
+pub fn (mut ctx Context) draw_empty_rect(x int, y int, x2 int, y2 int) {
+ if y == y2 || x == x2 {
+ ctx.draw_line(x, y, x2, y2)
+ return
+ }
+ ctx.draw_line(x, y, x2, y)
+ ctx.draw_line(x, y2, x2, y2)
+ ctx.draw_line(x, y, x, y2)
+ ctx.draw_line(x2, y, x2, y2)
+}
+
+// horizontal_separator draws a horizontal separator, spanning the width of the screen.
+[inline]
+pub fn (mut ctx Context) horizontal_separator(y int) {
+ ctx.set_cursor_position(0, y)
+ ctx.write(strings.repeat(`-`, ctx.window_width)) // /* `⎽` */
+}