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