1 # Copyright 2019-2020 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.
18 # The expect "spawn" function puts the tty name into the spawn_out
19 # array; but dejagnu doesn't export this globally. So, we have to
20 # wrap spawn with our own function, so that we can capture this value.
21 # The value is later used in calls to stty.
22 rename spawn builtin_spawn
24 set result [uplevel builtin_spawn $args]
26 upvar spawn_out spawn_out
27 set gdb_spawn_name $spawn_out(slave,name)
43 variable _resize_count
45 # If ARG is empty, return DEF: otherwise ARG. This is useful for
46 # defaulting arguments in CSIs.
47 proc _default {arg def} {
54 # Erase in the line Y from SX to just before EX.
55 proc _clear_in_line {sx ex y} {
58 set lattr [array get _attrs]
60 set _chars($sx,$y) [list " " $lattr]
65 # Erase the lines from SY to just before EY.
66 proc _clear_lines {sy ey} {
69 _clear_in_line 0 $_cols $sy
85 set _cur_x [expr {$_cols - 1}]
98 if {$_cur_y >= $_rows} {
109 # Make room for characters.
111 set n [_default [lindex $args 0] 1]
116 set out_x [expr {$_cur_x + $n}]
117 for {set i 0} {$i < $n} {incr i} {
118 set _chars($out_x,$_cur_y) $_chars($in_x,$_cur_y)
127 set arg [_default [lindex $args 0] 1]
128 set _cur_y [expr {max ($_cur_y - $arg, 0)}]
135 set arg [_default [lindex $args 0] 1]
136 set _cur_y [expr {min ($_cur_y + $arg, $_rows)}]
143 set arg [_default [lindex $args 0] 1]
144 set _cur_x [expr {min ($_cur_x + $arg, $_cols)}]
150 set arg [_default [lindex $args 0] 1]
151 set _cur_x [expr {max ($_cur_x - $arg, 0)}]
159 set arg [_default [lindex $args 0] 1]
161 set _cur_y [expr {min ($_cur_y + $arg, $_rows)}]
164 # Cursor Previous Line.
169 set arg [_default [lindex $args 0] 1]
171 set _cur_y [expr {max ($_cur_y - $arg, 0)}]
174 # Cursor Horizontal Absolute.
178 set arg [_default [lindex $args 0] 1]
179 set _cur_x [expr {min ($arg - 1, $_cols)}]
182 # Move cursor (don't know the official name of this one).
186 set _cur_y [expr {[_default [lindex $args 0] 1] - 1}]
187 set _cur_x [expr {[_default [lindex $args 1] 1] - 1}]
190 # Cursor Forward Tabulation.
192 set n [_default [lindex $args 0] 1]
195 incr _cur_x [expr {$n * 8 - $_cur_x % 8}]
196 if {$_cur_x >= $_cols} {
197 set _cur_x [expr {$_cols - 1}]
207 set arg [_default [lindex $args 0] 0]
209 _clear_in_line $_cur_x $_cols $_cur_y
210 _clear_lines [expr {$_cur_y + 1}] $_rows
211 } elseif {$arg == 1} {
212 _clear_lines 0 [expr {$_cur_y - 1}]
213 _clear_in_line 0 $_cur_x $_cur_y
214 } elseif {$arg == 2} {
215 _clear_lines 0 $_rows
224 set arg [_default [lindex $args 0] 0]
226 # From cursor to end.
227 _clear_in_line $_cur_x $_cols $_cur_y
228 } elseif {$arg == 1} {
229 _clear_in_line 0 $_cur_x $_cur_y
230 } elseif {$arg == 2} {
231 _clear_in_line 0 $_cols $_cur_y
241 set count [_default [lindex $args 0] 1]
243 set next_y [expr {$y + 1}]
244 while {$count > 0 && $next_y < $_rows} {
245 for {set x 0} {$x < $_cols} {incr x} {
246 set _chars($x,$y) $_chars($x,$next_y)
252 _clear_lines $next_y $_rows
257 set n [_default [lindex $args 0] 1]
258 # Erase characters but don't move cursor.
263 set lattr [array get _attrs]
265 for {set i 0} {$i < $n} {incr i} {
266 set _chars($x,$_cur_y) [list " " $lattr]
271 # Backward tab stops.
273 set n [_default [lindex $args 0] 1]
275 set _cur_x [expr {max (int (($_cur_x - 1) / 8) * 8 - ($n - 1) * 8, 0)}]
281 set n [_default [lindex $args 0] 1]
282 _insert [string repeat $_last_char $n]
285 # Line Position Absolute.
288 set _cur_y [expr {[_default [lindex $args 0] 1] - 1}]
291 # Select Graphic Rendition.
295 switch -exact -- $item {
297 set _attrs(intensity) normal
298 set _attrs(fg) default
299 set _attrs(bg) default
300 set _attrs(underline) 0
301 set _attrs(reverse) 0
304 set _attrs(intensity) bold
307 set _attrs(intensity) dim
310 set _attrs(underline) 1
313 set _attrs(reverse) 1
316 set _attrs(intensity) normal
319 set _attrs(underline) 0
322 set _attrs(reverse) 1
324 30 - 31 - 32 - 33 - 34 - 35 - 36 - 37 {
328 set _attrs(fg) default
330 40 - 41 - 42 - 43 - 44 - 45 - 46 - 47 {
334 set _attrs(bg) default
340 # Insert string at the cursor location.
342 verbose "INSERT <<$str>>"
349 set lattr [array get _attrs]
350 foreach char [split $str {}] {
351 set _chars($_cur_x,$_cur_y) [list $char $lattr]
353 if {$_cur_x >= $_cols} {
356 if {$_cur_y >= $_rows} {
364 proc _setup {rows cols} {
366 set stty_init "rows $rows columns $cols"
373 variable _resize_count
388 _clear_lines 0 $_rows
391 # Accept some output from gdb and update the screen. WAIT_FOR is
392 # a regexp matching the line to wait for. Return 0 on timeout, 1
394 proc wait_for {wait_for} {
400 set prompt_wait_for "$gdb_prompt \$"
404 -re "^\[\x07\x08\x0a\x0d\]" {
405 scan $expect_out(0,string) %c val
406 set hexval [format "%02x" $val]
407 verbose "+++ _ctl_0x${hexval}"
410 -re "^\x1b(\[0-9a-zA-Z\])" {
411 verbose "+++ unsupported escape"
412 error "unsupported escape"
414 -re "^\x1b\\\[(\[0-9;\]*)(\[a-zA-Z@\])" {
415 set cmd $expect_out(2,string)
416 set params [split $expect_out(1,string) ";"]
417 verbose "+++ _csi_$cmd <<<$expect_out(1,string)>>>"
418 eval _csi_$cmd $params
420 -re "^\[^\x07\x08\x0a\x0d\x1b\]+" {
421 _insert $expect_out(0,string)
423 set _last_char [string index $expect_out(0,string) end]
427 # Assume a timeout means we somehow missed the
428 # expected result, and carry on.
433 # If the cursor appears just after the prompt, return. It
434 # isn't reliable to check this only after an insertion,
435 # because curses may make "unusual" redrawing decisions.
436 if {$wait_for == "$prompt_wait_for"} {
437 set prev [get_line $_cur_y $_cur_x]
439 set prev [get_line $_cur_y]
441 if {[regexp -- $wait_for $prev]} {
442 if {$wait_for == "$prompt_wait_for"} {
445 set wait_for $prompt_wait_for
452 # Like ::clean_restart, but ensures that gdb starts in an
453 # environment where the TUI can work. ROWS and COLS are the size
454 # of the terminal. EXECUTABLE, if given, is passed to
456 proc clean_restart {rows cols {executable {}}} {
458 save_vars {env(TERM) stty_init} {
461 if {$executable == ""} {
464 ::clean_restart $executable
469 # Setup ready for starting the tui, but don't actually start it.
470 # Returns 1 on success, 0 if TUI tests should be skipped.
471 proc prepare_for_tui {} {
472 if {[skip_tui_tests]} {
476 gdb_test_no_output "set tui border-kind ascii"
477 gdb_test_no_output "maint set tui-resize-message on"
481 # Start the TUI. Returns 1 on success, 0 if TUI tests should be
484 if {![prepare_for_tui]} {
492 # Send the command CMD to gdb, then wait for a gdb prompt to be
493 # seen in the TUI. CMD should not end with a newline -- that will
494 # be supplied by this function.
497 wait_for [string_to_regexp $cmd]
500 # Return the text of screen line N, without attributes. Lines are
501 # 0-based. If C is given, stop before column C. Columns are also
503 proc get_line {n {c ""}} {
505 # This can happen during resizing, if the cursor seems to
506 # temporarily be off-screen.
514 set c [_default $c $_cols]
517 append result [lindex $_chars($x,$n) 0]
523 # Get just the character at (X, Y).
524 proc get_char {x y} {
526 return [lindex $_chars($x,$y) 0]
529 # Get the entire screen as a string.
530 proc get_all_lines {} {
536 for {set y 0} {$y < $_rows} {incr y} {
537 for {set x 0} {$x < $_cols} {incr x} {
538 append result [lindex $_chars($x,$y) 0]
546 # Get the text just before the cursor.
547 proc get_current_line {} {
550 return [get_line $_cur_y $_cur_x]
553 # Helper function for check_box. Returns empty string if the box
554 # is found, description of why not otherwise.
555 proc _check_box {x y width height} {
556 set x2 [expr {$x + $width - 1}]
557 set y2 [expr {$y + $height - 1}]
559 if {[get_char $x $y] != "+"} {
562 if {[get_char $x $y2] != "+"} {
565 if {[get_char $x2 $y] != "+"} {
568 if {[get_char $x2 $y2] != "+"} {
572 # Note we do not check the horizonal borders of the box. The
573 # top will contain a title, and the bottom may as well, if it
574 # is overlapped by some other border.
575 for {set i [expr {$y + 1}]} {$i < $y2 - 1} {incr i} {
576 if {[get_char $x $i] != "|"} {
577 return "left side $i"
579 if {[get_char $x2 $i] != "|"} {
580 return "right side $i"
587 # Check for a box at the given coordinates.
588 proc check_box {test_name x y width height} {
589 set why [_check_box $x $y $width $height]
594 fail "$test_name ($why)"
598 # Check whether the text contents of the terminal match the
599 # regular expression. Note that text styling is not considered.
600 proc check_contents {test_name regexp} {
601 set contents [get_all_lines]
602 if {![gdb_assert {[regexp -- $regexp $contents]} $test_name]} {
607 # Check the contents of a box on the screen. This is a little
608 # like check_contents, but doens't check the whole screen
609 # contents, only the contents of a single box. This procedure
610 # includes (effectively) a call to check_box to ensure there is a
611 # box where expected, if there is then the contents of the box are
612 # matched against REGEXP.
613 proc check_box_contents {test_name x y width height regexp} {
616 set why [_check_box $x $y $width $height]
619 fail "$test_name (box check: $why)"
623 # Now grab the contents of the box, join each line together
624 # with a newline character and match against REGEXP.
626 for {set yy [expr {$y + 1}]} {$yy < [expr {$y + $height - 1}]} {incr yy} {
627 for {set xx [expr {$x + 1}]} {$xx < [expr {$x + $width - 1}]} {incr xx} {
628 append result [lindex $_chars($xx,$yy) 0]
633 if {![gdb_assert {[regexp -- $regexp $result]} $test_name]} {
638 # A debugging function to dump the current screen, with line
640 proc dump_screen {} {
643 verbose -log "Screen Dump ($_cols x $_rows):"
644 for {set y 0} {$y < $_rows} {incr y} {
645 set fmt [format %5d $y]
646 verbose -log "$fmt [get_line $y]"
650 # Resize the terminal.
651 proc _do_resize {rows cols} {
656 set old_rows [expr {min ($_rows, $rows)}]
657 set old_cols [expr {min ($_cols, $cols)}]
660 array set local_chars [array get _chars]
665 _clear_lines 0 $_rows
667 for {set x 0} {$x < $old_cols} {incr x} {
668 for {set y 0} {$y < $old_rows} {incr y} {
669 set _chars($x,$y) $local_chars($x,$y)
674 proc resize {rows cols} {
677 variable _resize_count
679 global gdb_spawn_name
680 # expect handles each argument to stty separately. This means
681 # that gdb will see SIGWINCH twice. Rather than rely on this
682 # behavior (which, after all, could be changed), we make it
683 # explicit here. This also simplifies waiting for the redraw.
684 _do_resize $rows $_cols
685 stty rows $_rows < $gdb_spawn_name
686 # Due to the strange column resizing behavior, and because we
687 # don't care about this intermediate resize, we don't check
689 wait_for "@@ resize done $_resize_count"
691 # Somehow the number of columns transmitted to gdb is one less
692 # than what we request from expect. We hide this weird
693 # details from the caller.
694 _do_resize $_rows $cols
695 stty columns [expr {$_cols + 1}] < $gdb_spawn_name
696 wait_for "@@ resize done $_resize_count, size = ${_cols}x${rows}"