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