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>
pull/6337/head
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) {
mut i := 0
for i < size {
term.set_cursor_position(x+i,y)
term.set_cursor_position(x: x+i, y: y)
print(term.bold(term.yellow(ch)))
i++
}
@ -23,7 +23,7 @@ fn sleeping_line(x,y,size int, ch string) {
fn standing_line(x,y,size int, ch string) {
mut i := 0
for i < size {
term.set_cursor_position(x,y+i)
term.set_cursor_position(x: x, y: y+i)
print(term.bold(term.yellow(ch)))
i++
}

View File

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

View File

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

View File

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

View File

@ -6,6 +6,14 @@ const (
default_columns_size = 80
default_rows_size = 25
)
// Coord - used by term.get_cursor_position and term.set_cursor_position
pub struct Coord {
pub mut:
x int
y int
}
// can_show_color_on_stdout returns true if colors are allowed in stdout;
// returns false otherwise.
pub fn can_show_color_on_stdout() bool {

View File

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

View File

@ -1,7 +1,8 @@
import os
import term
fn test_get_terminal_size() {
cols,_ := term.get_terminal_size()
cols, _ := term.get_terminal_size()
assert cols > 0
}
@ -55,3 +56,47 @@ fn test_header() {
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
}

View File

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