]> git.ipfire.org Git - thirdparty/binutils-gdb.git/blob - gdb/testsuite/lib/tuiterm.exp
gdb/testsuite: add links for handled control sequences in lib/tuiterm.exp
[thirdparty/binutils-gdb.git] / gdb / testsuite / lib / tuiterm.exp
1 # Copyright 2019-2021 Free Software Foundation, Inc.
2
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.
7 #
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.
12 #
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/>.
15
16 # An ANSI terminal emulator for expect.
17
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 proc tuiterm_spawn { args } {
23 set result [uplevel builtin_spawn $args]
24 global gdb_spawn_name
25 upvar spawn_out spawn_out
26 if { [info exists spawn_out] } {
27 set gdb_spawn_name $spawn_out(slave,name)
28 } else {
29 unset gdb_spawn_name
30 }
31 return $result
32 }
33
34 # Initialize tuiterm.exp environment.
35 proc tuiterm_env_init { } {
36 # Override spawn with tui_spawn.
37 rename spawn builtin_spawn
38 rename tuiterm_spawn spawn
39 }
40
41 # Finalize tuiterm.exp environment.
42 proc tuiterm_env_finish { } {
43 # Restore spawn.
44 rename spawn tuiterm_spawn
45 rename builtin_spawn spawn
46 }
47
48 namespace eval Term {
49 variable _rows
50 variable _cols
51 variable _chars
52
53 variable _cur_x
54 variable _cur_y
55
56 variable _attrs
57
58 variable _last_char
59
60 variable _resize_count
61
62 # If ARG is empty, return DEF: otherwise ARG. This is useful for
63 # defaulting arguments in CSIs.
64 proc _default {arg def} {
65 if {$arg == ""} {
66 return $def
67 }
68 return $arg
69 }
70
71 # Erase in the line Y from SX to just before EX.
72 proc _clear_in_line {sx ex y} {
73 variable _attrs
74 variable _chars
75 set lattr [array get _attrs]
76 while {$sx < $ex} {
77 set _chars($sx,$y) [list " " $lattr]
78 incr sx
79 }
80 }
81
82 # Erase the lines from SY to just before EY.
83 proc _clear_lines {sy ey} {
84 variable _cols
85 while {$sy < $ey} {
86 _clear_in_line 0 $_cols $sy
87 incr sy
88 }
89 }
90
91 # Beep.
92 proc _ctl_0x07 {} {
93 }
94
95 # Backspace.
96 proc _ctl_0x08 {} {
97 variable _cur_x
98 incr _cur_x -1
99 if {$_cur_x < 0} {
100 variable _cur_y
101 variable _cols
102 set _cur_x [expr {$_cols - 1}]
103 incr _cur_y -1
104 if {$_cur_y < 0} {
105 set _cur_y 0
106 }
107 }
108 }
109
110 # Linefeed.
111 proc _ctl_0x0a {} {
112 variable _cur_y
113 variable _rows
114 incr _cur_y 1
115 if {$_cur_y >= $_rows} {
116 error "FIXME scroll"
117 }
118 }
119
120 # Carriage return.
121 proc _ctl_0x0d {} {
122 variable _cur_x
123 set _cur_x 0
124 }
125
126 # Insert Character.
127 #
128 # https://vt100.net/docs/vt510-rm/ICH.html
129 proc _csi_@ {args} {
130 set n [_default [lindex $args 0] 1]
131 variable _cur_x
132 variable _cur_y
133 variable _chars
134 set in_x $_cur_x
135 set out_x [expr {$_cur_x + $n}]
136 for {set i 0} {$i < $n} {incr i} {
137 set _chars($out_x,$_cur_y) $_chars($in_x,$_cur_y)
138 incr in_x
139 incr out_x
140 }
141 }
142
143 # Cursor Up.
144 #
145 # https://vt100.net/docs/vt510-rm/CUU.html
146 proc _csi_A {args} {
147 variable _cur_y
148 set arg [_default [lindex $args 0] 1]
149 set _cur_y [expr {max ($_cur_y - $arg, 0)}]
150 }
151
152 # Cursor Down.
153 #
154 # https://vt100.net/docs/vt510-rm/CUD.html
155 proc _csi_B {args} {
156 variable _cur_y
157 variable _rows
158 set arg [_default [lindex $args 0] 1]
159 set _cur_y [expr {min ($_cur_y + $arg, $_rows)}]
160 }
161
162 # Cursor Forward.
163 #
164 # https://vt100.net/docs/vt510-rm/CUF.html
165 proc _csi_C {args} {
166 variable _cur_x
167 variable _cols
168 set arg [_default [lindex $args 0] 1]
169 set _cur_x [expr {min ($_cur_x + $arg, $_cols)}]
170 }
171
172 # Cursor Backward.
173 #
174 # https://vt100.net/docs/vt510-rm/CUB.html
175 proc _csi_D {args} {
176 variable _cur_x
177 set arg [_default [lindex $args 0] 1]
178 set _cur_x [expr {max ($_cur_x - $arg, 0)}]
179 }
180
181 # Cursor Next Line.
182 #
183 # https://vt100.net/docs/vt510-rm/CNL.html
184 proc _csi_E {args} {
185 variable _cur_x
186 variable _cur_y
187 variable _rows
188 set arg [_default [lindex $args 0] 1]
189 set _cur_x 0
190 set _cur_y [expr {min ($_cur_y + $arg, $_rows)}]
191 }
192
193 # Cursor Previous Line.
194 #
195 # https://vt100.net/docs/vt510-rm/CPL.html
196 proc _csi_F {args} {
197 variable _cur_x
198 variable _cur_y
199 variable _rows
200 set arg [_default [lindex $args 0] 1]
201 set _cur_x 0
202 set _cur_y [expr {max ($_cur_y - $arg, 0)}]
203 }
204
205 # Cursor Horizontal Absolute.
206 #
207 # https://vt100.net/docs/vt510-rm/CHA.html
208 proc _csi_G {args} {
209 variable _cur_x
210 variable _cols
211 set arg [_default [lindex $args 0] 1]
212 set _cur_x [expr {min ($arg - 1, $_cols)}]
213 }
214
215 # Cursor Position.
216 #
217 # https://vt100.net/docs/vt510-rm/CUP.html
218 proc _csi_H {args} {
219 variable _cur_x
220 variable _cur_y
221 set _cur_y [expr {[_default [lindex $args 0] 1] - 1}]
222 set _cur_x [expr {[_default [lindex $args 1] 1] - 1}]
223 }
224
225 # Cursor Horizontal Forward Tabulation.
226 #
227 # https://vt100.net/docs/vt510-rm/CHT.html
228 proc _csi_I {args} {
229 set n [_default [lindex $args 0] 1]
230 variable _cur_x
231 variable _cols
232 incr _cur_x [expr {$n * 8 - $_cur_x % 8}]
233 if {$_cur_x >= $_cols} {
234 set _cur_x [expr {$_cols - 1}]
235 }
236 }
237
238 # Erase in Display.
239 #
240 # https://vt100.net/docs/vt510-rm/ED.html
241 proc _csi_J {args} {
242 variable _cur_x
243 variable _cur_y
244 variable _rows
245 variable _cols
246 set arg [_default [lindex $args 0] 0]
247 if {$arg == 0} {
248 _clear_in_line $_cur_x $_cols $_cur_y
249 _clear_lines [expr {$_cur_y + 1}] $_rows
250 } elseif {$arg == 1} {
251 _clear_lines 0 [expr {$_cur_y - 1}]
252 _clear_in_line 0 $_cur_x $_cur_y
253 } elseif {$arg == 2} {
254 _clear_lines 0 $_rows
255 }
256 }
257
258 # Erase in Line.
259 #
260 # https://vt100.net/docs/vt510-rm/EL.html
261 proc _csi_K {args} {
262 variable _cur_x
263 variable _cur_y
264 variable _cols
265 set arg [_default [lindex $args 0] 0]
266 if {$arg == 0} {
267 # From cursor to end.
268 _clear_in_line $_cur_x $_cols $_cur_y
269 } elseif {$arg == 1} {
270 _clear_in_line 0 $_cur_x $_cur_y
271 } elseif {$arg == 2} {
272 _clear_in_line 0 $_cols $_cur_y
273 }
274 }
275
276 # Delete line.
277 #
278 # https://vt100.net/docs/vt510-rm/DL.html
279 proc _csi_M {args} {
280 variable _cur_y
281 variable _rows
282 variable _cols
283 variable _chars
284 set count [_default [lindex $args 0] 1]
285 set y $_cur_y
286 set next_y [expr {$y + 1}]
287 while {$count > 0 && $next_y < $_rows} {
288 for {set x 0} {$x < $_cols} {incr x} {
289 set _chars($x,$y) $_chars($x,$next_y)
290 }
291 incr y
292 incr next_y
293 incr count -1
294 }
295 _clear_lines $next_y $_rows
296 }
297
298 # Erase chars.
299 #
300 # https://vt100.net/docs/vt510-rm/ECH.html
301 proc _csi_X {args} {
302 set n [_default [lindex $args 0] 1]
303 # Erase characters but don't move cursor.
304 variable _cur_x
305 variable _cur_y
306 variable _attrs
307 variable _chars
308 set lattr [array get _attrs]
309 set x $_cur_x
310 for {set i 0} {$i < $n} {incr i} {
311 set _chars($x,$_cur_y) [list " " $lattr]
312 incr x
313 }
314 }
315
316 # Cursor Backward Tabulation.
317 #
318 # https://vt100.net/docs/vt510-rm/CBT.html
319 proc _csi_Z {args} {
320 set n [_default [lindex $args 0] 1]
321 variable _cur_x
322 set _cur_x [expr {max (int (($_cur_x - 1) / 8) * 8 - ($n - 1) * 8, 0)}]
323 }
324
325 # Repeat.
326 #
327 # https://www.xfree86.org/current/ctlseqs.html (See `(REP)`)
328 proc _csi_b {args} {
329 variable _last_char
330 set n [_default [lindex $args 0] 1]
331 _insert [string repeat $_last_char $n]
332 }
333
334 # Vertical Line Position Absolute.
335 #
336 # https://vt100.net/docs/vt510-rm/VPA.html
337 proc _csi_d {args} {
338 variable _cur_y
339 set _cur_y [expr {[_default [lindex $args 0] 1] - 1}]
340 }
341
342 # Select Graphic Rendition.
343 #
344 # https://vt100.net/docs/vt510-rm/SGR.html
345 proc _csi_m {args} {
346 variable _attrs
347 foreach item $args {
348 switch -exact -- $item {
349 "" - 0 {
350 set _attrs(intensity) normal
351 set _attrs(fg) default
352 set _attrs(bg) default
353 set _attrs(underline) 0
354 set _attrs(reverse) 0
355 }
356 1 {
357 set _attrs(intensity) bold
358 }
359 2 {
360 set _attrs(intensity) dim
361 }
362 4 {
363 set _attrs(underline) 1
364 }
365 7 {
366 set _attrs(reverse) 1
367 }
368 22 {
369 set _attrs(intensity) normal
370 }
371 24 {
372 set _attrs(underline) 0
373 }
374 27 {
375 set _attrs(reverse) 1
376 }
377 30 - 31 - 32 - 33 - 34 - 35 - 36 - 37 {
378 set _attrs(fg) $item
379 }
380 39 {
381 set _attrs(fg) default
382 }
383 40 - 41 - 42 - 43 - 44 - 45 - 46 - 47 {
384 set _attrs(bg) $item
385 }
386 49 {
387 set _attrs(bg) default
388 }
389 }
390 }
391 }
392
393 # Insert string at the cursor location.
394 proc _insert {str} {
395 verbose "INSERT <<$str>>"
396 variable _cur_x
397 variable _cur_y
398 variable _rows
399 variable _cols
400 variable _attrs
401 variable _chars
402 set lattr [array get _attrs]
403 foreach char [split $str {}] {
404 set _chars($_cur_x,$_cur_y) [list $char $lattr]
405 incr _cur_x
406 if {$_cur_x >= $_cols} {
407 set _cur_x 0
408 incr _cur_y
409 if {$_cur_y >= $_rows} {
410 error "FIXME scroll"
411 }
412 }
413 }
414 }
415
416 # Initialize.
417 proc _setup {rows cols} {
418 global stty_init
419 set stty_init "rows $rows columns $cols"
420
421 variable _rows
422 variable _cols
423 variable _cur_x
424 variable _cur_y
425 variable _attrs
426 variable _resize_count
427
428 set _rows $rows
429 set _cols $cols
430 set _cur_x 0
431 set _cur_y 0
432 set _resize_count 0
433 array set _attrs {
434 intensity normal
435 fg default
436 bg default
437 underline 0
438 reverse 0
439 }
440
441 _clear_lines 0 $_rows
442 }
443
444 # Accept some output from gdb and update the screen. WAIT_FOR is
445 # a regexp matching the line to wait for. Return 0 on timeout, 1
446 # on success.
447 proc wait_for {wait_for} {
448 global expect_out
449 global gdb_prompt
450 variable _cur_x
451 variable _cur_y
452
453 set prompt_wait_for "$gdb_prompt \$"
454
455 while 1 {
456 gdb_expect {
457 -re "^\[\x07\x08\x0a\x0d\]" {
458 scan $expect_out(0,string) %c val
459 set hexval [format "%02x" $val]
460 verbose "+++ _ctl_0x${hexval}"
461 _ctl_0x${hexval}
462 }
463 -re "^\x1b(\[0-9a-zA-Z\])" {
464 verbose "+++ unsupported escape"
465 error "unsupported escape"
466 }
467 -re "^\x1b\\\[(\[0-9;\]*)(\[a-zA-Z@\])" {
468 set cmd $expect_out(2,string)
469 set params [split $expect_out(1,string) ";"]
470 verbose "+++ _csi_$cmd <<<$expect_out(1,string)>>>"
471 eval _csi_$cmd $params
472 }
473 -re "^\[^\x07\x08\x0a\x0d\x1b\]+" {
474 _insert $expect_out(0,string)
475 variable _last_char
476 set _last_char [string index $expect_out(0,string) end]
477 }
478
479 timeout {
480 # Assume a timeout means we somehow missed the
481 # expected result, and carry on.
482 return 0
483 }
484 }
485
486 # If the cursor appears just after the prompt, return. It
487 # isn't reliable to check this only after an insertion,
488 # because curses may make "unusual" redrawing decisions.
489 if {$wait_for == "$prompt_wait_for"} {
490 set prev [get_line $_cur_y $_cur_x]
491 } else {
492 set prev [get_line $_cur_y]
493 }
494 if {[regexp -- $wait_for $prev]} {
495 if {$wait_for == "$prompt_wait_for"} {
496 break
497 }
498 set wait_for $prompt_wait_for
499 }
500 }
501
502 return 1
503 }
504
505 # Like ::clean_restart, but ensures that gdb starts in an
506 # environment where the TUI can work. ROWS and COLS are the size
507 # of the terminal. EXECUTABLE, if given, is passed to
508 # clean_restart.
509 proc clean_restart {rows cols {executable {}}} {
510 global env stty_init
511 save_vars {env(TERM) stty_init} {
512 setenv TERM ansi
513 _setup $rows $cols
514 if {$executable == ""} {
515 ::clean_restart
516 } else {
517 ::clean_restart $executable
518 }
519 }
520 }
521
522 # Setup ready for starting the tui, but don't actually start it.
523 # Returns 1 on success, 0 if TUI tests should be skipped.
524 proc prepare_for_tui {} {
525 if {[skip_tui_tests]} {
526 return 0
527 }
528
529 gdb_test_no_output "set tui border-kind ascii"
530 gdb_test_no_output "maint set tui-resize-message on"
531 return 1
532 }
533
534 # Start the TUI. Returns 1 on success, 0 if TUI tests should be
535 # skipped.
536 proc enter_tui {} {
537 if {![prepare_for_tui]} {
538 return 0
539 }
540
541 command_no_prompt_prefix "tui enable"
542 return 1
543 }
544
545 # Send the command CMD to gdb, then wait for a gdb prompt to be
546 # seen in the TUI. CMD should not end with a newline -- that will
547 # be supplied by this function.
548 proc command {cmd} {
549 global gdb_prompt
550 send_gdb "$cmd\n"
551 set str [string_to_regexp $cmd]
552 set str "^$gdb_prompt $str"
553 wait_for $str
554 }
555
556 # As proc command, but don't wait for a initial prompt. This is used for
557 # inital terminal commands, where there's no prompt yet.
558 proc command_no_prompt_prefix {cmd} {
559 send_gdb "$cmd\n"
560 set str [string_to_regexp $cmd]
561 wait_for "^$str"
562 }
563
564 # Return the text of screen line N, without attributes. Lines are
565 # 0-based. If C is given, stop before column C. Columns are also
566 # zero-based.
567 proc get_line {n {c ""}} {
568 variable _rows
569 # This can happen during resizing, if the cursor seems to
570 # temporarily be off-screen.
571 if {$n >= $_rows} {
572 return ""
573 }
574
575 set result ""
576 variable _cols
577 variable _chars
578 set c [_default $c $_cols]
579 set x 0
580 while {$x < $c} {
581 append result [lindex $_chars($x,$n) 0]
582 incr x
583 }
584 return $result
585 }
586
587 # Get just the character at (X, Y).
588 proc get_char {x y} {
589 variable _chars
590 return [lindex $_chars($x,$y) 0]
591 }
592
593 # Get the entire screen as a string.
594 proc get_all_lines {} {
595 variable _rows
596 variable _cols
597 variable _chars
598
599 set result ""
600 for {set y 0} {$y < $_rows} {incr y} {
601 for {set x 0} {$x < $_cols} {incr x} {
602 append result [lindex $_chars($x,$y) 0]
603 }
604 append result "\n"
605 }
606
607 return $result
608 }
609
610 # Get the text just before the cursor.
611 proc get_current_line {} {
612 variable _cur_x
613 variable _cur_y
614 return [get_line $_cur_y $_cur_x]
615 }
616
617 # Helper function for check_box. Returns empty string if the box
618 # is found, description of why not otherwise.
619 proc _check_box {x y width height} {
620 set x2 [expr {$x + $width - 1}]
621 set y2 [expr {$y + $height - 1}]
622
623 if {[get_char $x $y] != "+"} {
624 return "ul corner"
625 }
626 if {[get_char $x $y2] != "+"} {
627 return "ll corner"
628 }
629 if {[get_char $x2 $y] != "+"} {
630 return "ur corner"
631 }
632 if {[get_char $x2 $y2] != "+"} {
633 return "lr corner"
634 }
635
636 # Note we do not check the full horizonal borders of the box.
637 # The top will contain a title, and the bottom may as well, if
638 # it is overlapped by some other border. However, at most a
639 # title should appear as '+-VERY LONG TITLE-+', so we can
640 # check for the '+-' on the left, and '-+' on the right.
641 if {[get_char [expr {$x + 1}] $y] != "-"} {
642 return "ul title padding"
643 }
644
645 if {[get_char [expr {$x2 - 1}] $y] != "-"} {
646 return "ul title padding"
647 }
648
649 # Now check the vertical borders.
650 for {set i [expr {$y + 1}]} {$i < $y2 - 1} {incr i} {
651 if {[get_char $x $i] != "|"} {
652 return "left side $i"
653 }
654 if {[get_char $x2 $i] != "|"} {
655 return "right side $i"
656 }
657 }
658
659 return ""
660 }
661
662 # Check for a box at the given coordinates.
663 proc check_box {test_name x y width height} {
664 set why [_check_box $x $y $width $height]
665 if {$why == ""} {
666 pass $test_name
667 } else {
668 dump_screen
669 fail "$test_name ($why)"
670 }
671 }
672
673 # Check whether the text contents of the terminal match the
674 # regular expression. Note that text styling is not considered.
675 proc check_contents {test_name regexp} {
676 set contents [get_all_lines]
677 if {![gdb_assert {[regexp -- $regexp $contents]} $test_name]} {
678 dump_screen
679 }
680 }
681
682 # Check the contents of a box on the screen. This is a little
683 # like check_contents, but doens't check the whole screen
684 # contents, only the contents of a single box. This procedure
685 # includes (effectively) a call to check_box to ensure there is a
686 # box where expected, if there is then the contents of the box are
687 # matched against REGEXP.
688 proc check_box_contents {test_name x y width height regexp} {
689 variable _chars
690
691 set why [_check_box $x $y $width $height]
692 if {$why != ""} {
693 dump_screen
694 fail "$test_name (box check: $why)"
695 return
696 }
697
698 # Now grab the contents of the box, join each line together
699 # with a newline character and match against REGEXP.
700 set result ""
701 for {set yy [expr {$y + 1}]} {$yy < [expr {$y + $height - 1}]} {incr yy} {
702 for {set xx [expr {$x + 1}]} {$xx < [expr {$x + $width - 1}]} {incr xx} {
703 append result [lindex $_chars($xx,$yy) 0]
704 }
705 append result "\n"
706 }
707
708 if {![gdb_assert {[regexp -- $regexp $result]} $test_name]} {
709 dump_screen
710 }
711 }
712
713 # A debugging function to dump the current screen, with line
714 # numbers.
715 proc dump_screen {} {
716 variable _rows
717 variable _cols
718 verbose -log "Screen Dump ($_cols x $_rows):"
719 for {set y 0} {$y < $_rows} {incr y} {
720 set fmt [format %5d $y]
721 verbose -log "$fmt [get_line $y]"
722 }
723 }
724
725 # Resize the terminal.
726 proc _do_resize {rows cols} {
727 variable _chars
728 variable _rows
729 variable _cols
730
731 set old_rows [expr {min ($_rows, $rows)}]
732 set old_cols [expr {min ($_cols, $cols)}]
733
734 # Copy locally.
735 array set local_chars [array get _chars]
736 unset _chars
737
738 set _rows $rows
739 set _cols $cols
740 _clear_lines 0 $_rows
741
742 for {set x 0} {$x < $old_cols} {incr x} {
743 for {set y 0} {$y < $old_rows} {incr y} {
744 set _chars($x,$y) $local_chars($x,$y)
745 }
746 }
747 }
748
749 proc resize {rows cols} {
750 variable _rows
751 variable _cols
752 variable _resize_count
753
754 global gdb_spawn_name
755 # expect handles each argument to stty separately. This means
756 # that gdb will see SIGWINCH twice. Rather than rely on this
757 # behavior (which, after all, could be changed), we make it
758 # explicit here. This also simplifies waiting for the redraw.
759 _do_resize $rows $_cols
760 stty rows $_rows < $gdb_spawn_name
761 # Due to the strange column resizing behavior, and because we
762 # don't care about this intermediate resize, we don't check
763 # the size here.
764 wait_for "@@ resize done $_resize_count"
765 incr _resize_count
766 # Somehow the number of columns transmitted to gdb is one less
767 # than what we request from expect. We hide this weird
768 # details from the caller.
769 _do_resize $_rows $cols
770 stty columns [expr {$_cols + 1}] < $gdb_spawn_name
771 wait_for "@@ resize done $_resize_count, size = ${_cols}x${rows}"
772 incr _resize_count
773 }
774 }