1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

term: add get_cursor_position and set_terminal_title (#6279)

* added functions

added:
  - get_cursor_position()
  - set_terminal_title(title string)

* implement term.get_cursor_position and term.set_terminal_title on unix

* Cleanup

* make x,y fields of term.Coord mutable

* fix vrepl compilation

* use more descriptive var names in term_test.v

* do not change the current terminal title in dumb terminals; do not test term.set_terminal_title outside of CI

* unix: in term.set_terminal_title, return true even for dumb terminals

Co-authored-by: Brent Pryer <brent@pryermachine.com>
Co-authored-by: Delyan Angelov <delian66@gmail.com>
This commit is contained in:
bpryer 2020-09-08 14:00:10 -05:00 committed by GitHub
parent 49c322f120
commit 3f7970db52
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 163 additions and 18 deletions

View File

@ -14,7 +14,7 @@ fn main() {
fn sleeping_line(x,y,size int, ch string) { fn sleeping_line(x,y,size int, ch string) {
mut i := 0 mut i := 0
for i < size { for i < size {
term.set_cursor_position(x+i,y) term.set_cursor_position(x: x+i, y: y)
print(term.bold(term.yellow(ch))) print(term.bold(term.yellow(ch)))
i++ i++
} }
@ -23,7 +23,7 @@ fn sleeping_line(x,y,size int, ch string) {
fn standing_line(x,y,size int, ch string) { fn standing_line(x,y,size int, ch string) {
mut i := 0 mut i := 0
for i < size { for i < size {
term.set_cursor_position(x,y+i) term.set_cursor_position(x: x, y: y+i)
print(term.bold(term.yellow(ch))) print(term.bold(term.yellow(ch)))
i++ i++
} }

View File

@ -451,7 +451,7 @@ fn (mut r Readline) switch_overwrite() {
} }
fn (mut r Readline) clear_screen() { fn (mut r Readline) clear_screen() {
term.set_cursor_position(1, 1) term.set_cursor_position(x:1, y:1)
term.erase_clear() term.erase_clear()
r.refresh_line() r.refresh_line()
} }

View File

@ -15,9 +15,9 @@ import os
fn main() { fn main() {
term.clear() // clears the content in the terminal term.clear() // clears the content in the terminal
width, height := term.get_terminal_size() // get the size of the terminal width, height := term.get_terminal_size() // get the size of the terminal
term.set_cursor_position(width / 2, height / 2) // now we point the cursor to the middle of the terminal term.set_cursor_position(x: width / 2, y: height / 2) // now we point the cursor to the middle of the terminal
println(term.strikethrough(term.bright_green("hello world"))) // Print green text println(term.strikethrough(term.bright_green("hello world"))) // Print green text
term.set_cursor_position(0, height) // Sets the position of the cursor to the bottom of the terminal term.set_cursor_position(x: 0, y: height) // Sets the position of the cursor to the bottom of the terminal
mut var := os.input('press q to quit: ') mut var := os.input('press q to quit: ')
// Keep prompting until the user presses the q key // Keep prompting until the user presses the q key
for { for {
@ -68,7 +68,7 @@ term.underline(string)
term.bg_<color>(string) term.bg_<color>(string)
// sets the position of the cursor at a given place in the terminal // sets the position of the cursor at a given place in the terminal
term.set_cursor_position(x,y) term.set_cursor_position(term.Coord)
// moves the cursor up // moves the cursor up
term.cursor_up() term.cursor_up()

View File

@ -13,8 +13,8 @@ module term
// Setting cursor to the given position // Setting cursor to the given position
// x is the x coordinate // x is the x coordinate
// y is the y coordinate // y is the y coordinate
pub fn set_cursor_position(x int, y int) { pub fn set_cursor_position(c Coord) {
print('\x1b[$y;$x' + 'H') print('\x1b[$c.y;$c.x' + 'H')
} }
// n is number of cells // n is number of cells

View File

@ -6,6 +6,14 @@ const (
default_columns_size = 80 default_columns_size = 80
default_rows_size = 25 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; // can_show_color_on_stdout returns true if colors are allowed in stdout;
// returns false otherwise. // returns false otherwise.
pub fn can_show_color_on_stdout() bool { pub fn can_show_color_on_stdout() bool {

View File

@ -3,9 +3,7 @@ module term
import os import os
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <termios.h> // TIOCGWINSZ #include <termios.h> // TIOCGWINSZ
pub struct C.winsize { pub struct C.winsize {
pub: pub:
ws_row u16 ws_row u16
@ -25,3 +23,71 @@ pub fn get_terminal_size() (int,int) {
C.ioctl(1, C.TIOCGWINSZ, &w) C.ioctl(1, C.TIOCGWINSZ, &w)
return int(w.ws_col), int(w.ws_row) return int(w.ws_col), int(w.ws_row)
} }
// get_cursor_position returns a Coord containing the current cursor position
pub fn get_cursor_position() Coord {
if is_atty(1) <= 0 || os.getenv('TERM') == 'dumb' {
return Coord{
x: 0
y: 0
}
}
// TODO: use termios.h, C.tcgetattr & C.tcsetattr directly,
// instead of using `stty`
oldsettings := os.exec('stty -g') or {
os.Result{}
}
os.system('stty -echo -icanon time 0')
print('\033[6n')
mut ch := int(0)
mut i := 0
// ESC [ YYY `;` XXX `R`
mut reading_x := false
mut reading_y := false
mut x := 0
mut y := 0
for {
ch = C.getchar()
b := byte(ch)
i++
assert i < 15
// state management:
if b == `R` {
break
}
if b == `[` {
reading_y = true
reading_x = false
continue
}
if b == `;` {
reading_y = false
reading_x = true
continue
}
// converting string vals to ints:
if reading_x {
x *= 10
x += (b - byte(`0`))
}
if reading_y {
y *= 10
y += (b - byte(`0`))
}
}
// restore the old terminal settings:
os.system('stty $oldsettings.output')
return Coord{
x: x
y: y
}
}
// set_terminal_title change the terminal title
pub fn set_terminal_title(title string) bool {
if is_atty(1) <= 0 || os.getenv('TERM') == 'dumb' {
return true
}
print('\033]0;${title}\007')
return true
}

View File

@ -1,3 +1,4 @@
import os
import term import term
fn test_get_terminal_size() { fn test_get_terminal_size() {
@ -55,3 +56,47 @@ fn test_header() {
assert term_width == very_long_header_2.len assert term_width == very_long_header_2.len
assert term_width == term.header('1234', '_-/\\/\\').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
}

View File

@ -2,7 +2,8 @@ module term
import os import os
struct Coord { pub struct Coord16 {
pub:
x i16 x i16
y i16 y i16
} }
@ -14,16 +15,22 @@ struct SmallRect {
bottom i16 bottom i16
} }
// win: CONSOLE_SCREEN_BUFFER_INFO
// https://docs.microsoft.com/en-us/windows/console/console-screen-buffer-info-str
struct ConsoleScreenBufferInfo { struct ConsoleScreenBufferInfo {
dw_size Coord dw_size Coord16
dw_cursor_position Coord dw_cursor_position Coord16
w_attributes u16 w_attributes u16
sr_window SmallRect sr_window SmallRect
dw_maximum_window_size Coord dw_maximum_window_size Coord16
} }
// ref - https://docs.microsoft.com/en-us/windows/console/getconsolescreenbufferinfo
fn C.GetConsoleScreenBufferInfo(handle os.HANDLE, info &ConsoleScreenBufferInfo) bool fn C.GetConsoleScreenBufferInfo(handle os.HANDLE, info &ConsoleScreenBufferInfo) bool
// ref - https://docs.microsoft.com/en-us/windows/console/setconsoletitle
fn C.SetConsoleTitle(title &u16) bool
// get_terminal_size returns a number of colums and rows of terminal window. // get_terminal_size returns a number of colums and rows of terminal window.
pub fn get_terminal_size() (int, int) { pub fn get_terminal_size() (int, int) {
if is_atty(1) > 0 && os.getenv('TERM') != 'dumb' { if is_atty(1) > 0 && os.getenv('TERM') != 'dumb' {
@ -36,3 +43,22 @@ pub fn get_terminal_size() (int, int) {
} }
return default_columns_size, default_rows_size return default_columns_size, default_rows_size
} }
// get_cursor_position returns a Coord containing the current cursor position
pub fn get_cursor_position() Coord {
mut res := Coord{}
if is_atty(1) > 0 && os.getenv('TERM') != 'dumb' {
info := ConsoleScreenBufferInfo{}
if C.GetConsoleScreenBufferInfo(C.GetStdHandle(C.STD_OUTPUT_HANDLE), &info) {
res.x = info.dw_cursor_position.x
res.y = info.dw_cursor_position.y
}
}
return res
}
// set_terminal_title change the terminal title
pub fn set_terminal_title(title string) bool {
title_change := C.SetConsoleTitle(title.to_wide())
return title_change
}