1 # Copyright 2019-2022 Free Software Foundation, Inc.
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 3 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 # An ANSI terminal emulator for expect.
19 # Size of the terminal.
23 # Buffer / contents of the terminal.
26 # Position of the cursor.
34 variable _resize_count
40 # Call BODY, then log WHAT along with the original and new cursor position.
41 proc _log_cur { what body } {
45 set orig_cur_row $_cur_row
46 set orig_cur_col $_cur_col
50 _log "$what, cursor: ($orig_cur_row, $orig_cur_col) -> ($_cur_row, $_cur_col)"
53 # If ARG is empty, return DEF: otherwise ARG. This is useful for
54 # defaulting arguments in CSIs.
55 proc _default {arg def} {
62 # Erase in the line Y from SX to just before EX.
63 proc _clear_in_line {sx ex y} {
66 set lattr [array get _attrs]
68 set _chars($sx,$y) [list " " $lattr]
73 # Erase the lines from SY to just before EY.
74 proc _clear_lines {sy ey} {
77 _clear_in_line 0 $_cols $sy
88 _log_cur "Backspace" {
99 _log_cur "Line feed" {
104 if {$_cur_row >= $_rows} {
112 _log_cur "Carriage return" {
121 # https://vt100.net/docs/vt510-rm/ICH.html
123 set n [_default [lindex $args 0] 1]
125 _log_cur "Insert Character ($n)" {
131 # Move characters right of the cursor right by N positions,
132 # starting with the rightmost one.
133 for {set in_col [expr $_cols - $n - 1]} {$in_col >= $_cur_col} {incr in_col -1} {
134 set out_col [expr $in_col + $n]
135 set _chars($out_col,$_cur_row) $_chars($in_col,$_cur_row)
138 # Write N blank spaces starting from the cursor.
139 _clear_in_line $_cur_col [expr $_cur_col + $n] $_cur_row
145 # https://vt100.net/docs/vt510-rm/CUU.html
147 set arg [_default [lindex $args 0] 1]
149 _log_cur "Cursor Up ($arg)" {
152 set _cur_row [expr {max ($_cur_row - $arg, 0)}]
158 # https://vt100.net/docs/vt510-rm/CUD.html
160 set arg [_default [lindex $args 0] 1]
162 _log_cur "Cursor Down ($arg)" {
166 set _cur_row [expr {min ($_cur_row + $arg, $_rows - 1)}]
172 # https://vt100.net/docs/vt510-rm/CUF.html
174 set arg [_default [lindex $args 0] 1]
176 _log_cur "Cursor Forward ($arg)" {
180 set _cur_col [expr {min ($_cur_col + $arg, $_cols - 1)}]
186 # https://vt100.net/docs/vt510-rm/CUB.html
188 set arg [_default [lindex $args 0] 1]
190 _log_cur "Cursor Backward ($arg)" {
193 set _cur_col [expr {max ($_cur_col - $arg, 0)}]
199 # https://vt100.net/docs/vt510-rm/CNL.html
201 set arg [_default [lindex $args 0] 1]
203 _log_cur "Cursor Next Line ($arg)" {
209 set _cur_row [expr {min ($_cur_row + $arg, $_rows - 1)}]
213 # Cursor Previous Line.
215 # https://vt100.net/docs/vt510-rm/CPL.html
217 set arg [_default [lindex $args 0] 1]
219 _log_cur "Cursor Previous Line ($arg)" {
225 set _cur_row [expr {max ($_cur_row - $arg, 0)}]
229 # Cursor Horizontal Absolute.
231 # https://vt100.net/docs/vt510-rm/CHA.html
233 set arg [_default [lindex $args 0] 1]
235 _log_cur "Cursor Horizontal Absolute ($arg)" {
239 set _cur_col [expr {min ($arg - 1, $_cols)}]
245 # https://vt100.net/docs/vt510-rm/CUP.html
247 set row [_default [lindex $args 0] 1]
248 set col [_default [lindex $args 1] 1]
250 _log_cur "Cursor Position ($row, $col)" {
254 set _cur_row [expr {$row - 1}]
255 set _cur_col [expr {$col - 1}]
259 # Cursor Horizontal Forward Tabulation.
261 # https://vt100.net/docs/vt510-rm/CHT.html
263 set n [_default [lindex $args 0] 1]
265 _log_cur "Cursor Horizontal Forward Tabulation ($n)" {
269 incr _cur_col [expr {$n * 8 - $_cur_col % 8}]
270 if {$_cur_col >= $_cols} {
271 set _cur_col [expr {$_cols - 1}]
278 # https://vt100.net/docs/vt510-rm/ED.html
280 set arg [_default [lindex $args 0] 0]
282 _log_cur "Erase in Display ($arg)" {
289 # Cursor (inclusive) to end of display.
290 _clear_in_line $_cur_col $_cols $_cur_row
291 _clear_lines [expr {$_cur_row + 1}] $_rows
292 } elseif {$arg == 1} {
293 # Beginning of display to cursor (inclusive).
294 _clear_lines 0 $_cur_row
295 _clear_in_line 0 [expr $_cur_col + 1] $_cur_row
296 } elseif {$arg == 2} {
298 _clear_lines 0 $_rows
305 # https://vt100.net/docs/vt510-rm/EL.html
307 set arg [_default [lindex $args 0] 0]
309 _log_cur "Erase in Line ($arg)" {
315 # Cursor (inclusive) to end of line.
316 _clear_in_line $_cur_col $_cols $_cur_row
317 } elseif {$arg == 1} {
318 # Beginning of line to cursor (inclusive).
319 _clear_in_line 0 [expr $_cur_col + 1] $_cur_row
320 } elseif {$arg == 2} {
322 _clear_in_line 0 $_cols $_cur_row
329 # https://vt100.net/docs/vt510-rm/DL.html
331 set count [_default [lindex $args 0] 1]
333 _log_cur "Delete line ($count)" {
340 set next_y [expr {$y + $count}]
341 while {$next_y < $_rows} {
342 for {set x 0} {$x < $_cols} {incr x} {
343 set _chars($x,$y) $_chars($x,$next_y)
348 _clear_lines $y $_rows
354 # https://vt100.net/docs/vt510-rm/ECH.html
356 set n [_default [lindex $args 0] 1]
358 _log_cur "Erase chars ($n)" {
359 # Erase characters but don't move cursor.
365 set lattr [array get _attrs]
367 for {set i 0} {$i < $n} {incr i} {
368 set _chars($x,$_cur_row) [list " " $lattr]
374 # Cursor Backward Tabulation.
376 # https://vt100.net/docs/vt510-rm/CBT.html
378 set n [_default [lindex $args 0] 1]
380 _log_cur "Cursor Backward Tabulation ($n)" {
383 set _cur_col [expr {max (int (($_cur_col - 1) / 8) * 8 - ($n - 1) * 8, 0)}]
389 # https://www.xfree86.org/current/ctlseqs.html (See `(REP)`)
391 set n [_default [lindex $args 0] 1]
393 _log_cur "Repeat ($n)" {
396 _insert [string repeat $_last_char $n]
400 # Vertical Line Position Absolute.
402 # https://vt100.net/docs/vt510-rm/VPA.html
404 set row [_default [lindex $args 0] 1]
406 _log_cur "Vertical Line Position Absolute ($row)" {
410 set _cur_row [expr min ($row - 1, $_rows - 1)]
414 # Select Graphic Rendition.
416 # https://vt100.net/docs/vt510-rm/SGR.html
418 _log_cur "Select Graphic Rendition ([join $args {, }])" {
422 switch -exact -- $item {
424 set _attrs(intensity) normal
425 set _attrs(fg) default
426 set _attrs(bg) default
427 set _attrs(underline) 0
428 set _attrs(reverse) 0
431 set _attrs(intensity) bold
434 set _attrs(intensity) dim
437 set _attrs(underline) 1
440 set _attrs(reverse) 1
443 set _attrs(intensity) normal
446 set _attrs(underline) 0
449 set _attrs(reverse) 1
451 30 - 31 - 32 - 33 - 34 - 35 - 36 - 37 {
455 set _attrs(fg) default
457 40 - 41 - 42 - 43 - 44 - 45 - 46 - 47 {
461 set _attrs(bg) default
468 # Insert string at the cursor location.
470 _log_cur "Inserted string '$str'" {
471 _log "Inserting string '$str'"
479 set lattr [array get _attrs]
480 foreach char [split $str {}] {
481 _log_cur " Inserted char '$char'" {
482 set _chars($_cur_col,$_cur_row) [list $char $lattr]
484 if {$_cur_col >= $_cols} {
487 if {$_cur_row >= $_rows} {
496 # Move the cursor to the (0-based) COL and ROW positions.
497 proc _move_cursor { col row } {
503 if { $col < 0 || $col >= $_cols } {
504 error "_move_cursor: invalid col value: $col"
507 if { $row < 0 || $row >= $_rows } {
508 error "_move_cursor: invalid row value: $row"
517 proc _setup {rows cols} {
519 set stty_init "rows $rows columns $cols"
526 variable _resize_count
541 _clear_lines 0 $_rows
544 # Accept some output from gdb and update the screen. WAIT_FOR is
545 # a regexp matching the line to wait for. Return 0 on timeout, 1
547 proc wait_for {wait_for} {
553 set prompt_wait_for "$gdb_prompt \$"
557 -re "^\[\x07\x08\x0a\x0d\]" {
558 scan $expect_out(0,string) %c val
559 set hexval [format "%02x" $val]
560 _log "wait_for: _ctl_0x${hexval}"
563 -re "^\x1b(\[0-9a-zA-Z\])" {
564 _log "wait_for: unsupported escape"
565 error "unsupported escape"
567 -re "^\x1b\\\[(\[0-9;\]*)(\[a-zA-Z@\])" {
568 set cmd $expect_out(2,string)
569 set params [split $expect_out(1,string) ";"]
570 _log "wait_for: _csi_$cmd <<<$expect_out(1,string)>>>"
571 eval _csi_$cmd $params
573 -re "^\[^\x07\x08\x0a\x0d\x1b\]+" {
574 _insert $expect_out(0,string)
576 set _last_char [string index $expect_out(0,string) end]
580 # Assume a timeout means we somehow missed the
581 # expected result, and carry on.
586 # If the cursor appears just after the prompt, return. It
587 # isn't reliable to check this only after an insertion,
588 # because curses may make "unusual" redrawing decisions.
589 if {$wait_for == "$prompt_wait_for"} {
590 set prev [get_line $_cur_row $_cur_col]
592 set prev [get_line $_cur_row]
594 if {[regexp -- $wait_for $prev]} {
595 if {$wait_for == "$prompt_wait_for"} {
598 set wait_for $prompt_wait_for
605 # Like ::clean_restart, but ensures that gdb starts in an
606 # environment where the TUI can work. ROWS and COLS are the size
607 # of the terminal. EXECUTABLE, if given, is passed to
609 proc clean_restart {rows cols {executable {}}} {
611 save_vars {env(TERM) stty_init} {
614 if {$executable == ""} {
617 ::clean_restart $executable
619 ::gdb_test_no_output "set pagination off"
623 # Setup ready for starting the tui, but don't actually start it.
624 # Returns 1 on success, 0 if TUI tests should be skipped.
625 proc prepare_for_tui {} {
626 if {[skip_tui_tests]} {
630 gdb_test_no_output "set tui border-kind ascii"
631 gdb_test_no_output "maint set tui-resize-message on"
635 # Start the TUI. Returns 1 on success, 0 if TUI tests should be
638 if {![prepare_for_tui]} {
642 command_no_prompt_prefix "tui enable"
646 # Send the command CMD to gdb, then wait for a gdb prompt to be
647 # seen in the TUI. CMD should not end with a newline -- that will
648 # be supplied by this function.
652 set str [string_to_regexp $cmd]
653 set str "^$gdb_prompt $str"
657 # As proc command, but don't wait for a initial prompt. This is used for
658 # inital terminal commands, where there's no prompt yet.
659 proc command_no_prompt_prefix {cmd} {
661 set str [string_to_regexp $cmd]
665 # Return the text of screen line N, without attributes. Lines are
666 # 0-based. If C is given, stop before column C. Columns are also
668 proc get_line {n {c ""}} {
670 # This can happen during resizing, if the cursor seems to
671 # temporarily be off-screen.
679 set c [_default $c $_cols]
682 append result [lindex $_chars($x,$n) 0]
688 # Get just the character at (X, Y).
689 proc get_char {x y} {
691 return [lindex $_chars($x,$y) 0]
694 # Get the entire screen as a string.
695 proc get_all_lines {} {
701 for {set y 0} {$y < $_rows} {incr y} {
702 for {set x 0} {$x < $_cols} {incr x} {
703 append result [lindex $_chars($x,$y) 0]
711 # Get the text just before the cursor.
712 proc get_current_line {} {
715 return [get_line $_cur_row $_cur_col]
718 # Helper function for check_box. Returns empty string if the box
719 # is found, description of why not otherwise.
720 proc _check_box {x y width height} {
721 set x2 [expr {$x + $width - 1}]
722 set y2 [expr {$y + $height - 1}]
724 verbose -log "_check_box x=$x, y=$y, x2=$x2, y2=$y2, width=$width, height=$height"
726 set c [get_char $x $y]
728 return "ul corner is $c, not +"
731 set c [get_char $x $y2]
733 return "ll corner is $c, not +"
736 set c [get_char $x2 $y]
738 return "ur corner is $c, not +"
741 set c [get_char $x2 $y2]
743 return "lr corner is $c, not +"
746 # Note we do not check the full horizonal borders of the box.
747 # The top will contain a title, and the bottom may as well, if
748 # it is overlapped by some other border. However, at most a
749 # title should appear as '+-VERY LONG TITLE-+', so we can
750 # check for the '+-' on the left, and '-+' on the right.
751 set c [get_char [expr {$x + 1}] $y]
753 return "ul title padding is $c, not -"
756 set c [get_char [expr {$x2 - 1}] $y]
758 return "ul title padding is $c, not -"
761 # Now check the vertical borders.
762 for {set i [expr {$y + 1}]} {$i < $y2 - 1} {incr i} {
763 set c [get_char $x $i]
765 return "left side $i is $c, not |"
768 set c [get_char $x2 $i]
770 return "right side $i is $c, not |"
777 # Check for a box at the given coordinates.
778 proc check_box {test_name x y width height} {
779 dump_box $x $y $width $height
780 set why [_check_box $x $y $width $height]
784 fail "$test_name ($why)"
788 # Check whether the text contents of the terminal match the
789 # regular expression. Note that text styling is not considered.
790 proc check_contents {test_name regexp} {
792 set contents [get_all_lines]
793 gdb_assert {[regexp -- $regexp $contents]} $test_name
796 # Get the region of the screen described by X, Y, WIDTH,
797 # and HEIGHT, and separate the lines using SEP.
798 proc get_region { x y width height sep } {
801 # Grab the contents of the box, join each line together
804 for {set yy $y} {$yy < [expr {$y + $height}]} {incr yy} {
806 # Add the end of line sequence only if this isn't the
810 for {set xx $x} {$xx < [expr {$x + $width}]} {incr xx} {
811 append result [lindex $_chars($xx,$yy) 0]
817 # Check that the region of the screen described by X, Y, WIDTH,
818 # and HEIGHT match REGEXP. This is like check_contents except
819 # only part of the screen is checked. This can be used to check
820 # the contents within a box (though check_box_contents is a better
821 # choice for boxes with a border).
822 proc check_region_contents { test_name x y width height regexp } {
824 dump_box $x $y $width $height
826 # Now grab the contents of the box, join each line together
827 # with a '\r\n' sequence and match against REGEXP.
828 set result [get_region $x $y $width $height "\r\n"]
829 gdb_assert {[regexp -- $regexp $result]} $test_name
832 # Check the contents of a box on the screen. This is a little
833 # like check_contents, but doens't check the whole screen
834 # contents, only the contents of a single box. This procedure
835 # includes (effectively) a call to check_box to ensure there is a
836 # box where expected, if there is then the contents of the box are
837 # matched against REGEXP.
838 proc check_box_contents {test_name x y width height regexp} {
841 dump_box $x $y $width $height
842 set why [_check_box $x $y $width $height]
844 fail "$test_name (box check: $why)"
848 check_region_contents $test_name [expr {$x + 1}] [expr {$y + 1}] \
849 [expr {$width - 2}] [expr {$height - 2}] $regexp
852 # A debugging function to dump the current screen, with line
854 proc dump_screen {} {
860 verbose -log "Screen Dump (size $_cols columns x $_rows rows, cursor at column $_cur_col, row $_cur_row):"
862 for {set y 0} {$y < $_rows} {incr y} {
863 set fmt [format %5d $y]
864 verbose -log "$fmt [get_line $y]"
868 # A debugging function to dump a box from the current screen, with line
870 proc dump_box { x y width height } {
871 verbose -log "Box Dump ($width x $height) @ ($x, $y):"
872 set region [get_region $x $y $width $height "\n"]
873 set lines [split $region "\n"]
875 foreach line $lines {
876 set fmt [format %5d $nr]
877 verbose -log "$fmt $line"
882 # Resize the terminal.
883 proc _do_resize {rows cols} {
888 set old_rows [expr {min ($_rows, $rows)}]
889 set old_cols [expr {min ($_cols, $cols)}]
892 array set local_chars [array get _chars]
897 _clear_lines 0 $_rows
899 for {set x 0} {$x < $old_cols} {incr x} {
900 for {set y 0} {$y < $old_rows} {incr y} {
901 set _chars($x,$y) $local_chars($x,$y)
906 proc resize {rows cols} {
909 variable _resize_count
911 # expect handles each argument to stty separately. This means
912 # that gdb will see SIGWINCH twice. Rather than rely on this
913 # behavior (which, after all, could be changed), we make it
914 # explicit here. This also simplifies waiting for the redraw.
915 _do_resize $rows $_cols
916 stty rows $_rows < $::gdb_tty_name
917 # Due to the strange column resizing behavior, and because we
918 # don't care about this intermediate resize, we don't check
920 wait_for "@@ resize done $_resize_count"
922 # Somehow the number of columns transmitted to gdb is one less
923 # than what we request from expect. We hide this weird
924 # details from the caller.
925 _do_resize $_rows $cols
926 stty columns [expr {$_cols + 1}] < $::gdb_tty_name
927 wait_for "@@ resize done $_resize_count, size = ${_cols}x${rows}"