winio.c (113989B)
1 /************************************************************************** 2 * winio.c -- This file is part of GNU nano. * 3 * * 4 * Copyright (C) 1999-2011, 2013-2025 Free Software Foundation, Inc. * 5 * Copyright (C) 2014-2022 Benno Schulenberg * 6 * * 7 * GNU nano is free software: you can redistribute it and/or modify * 8 * it under the terms of the GNU General Public License as published * 9 * by the Free Software Foundation, either version 3 of the License, * 10 * or (at your option) any later version. * 11 * * 12 * GNU nano is distributed in the hope that it will be useful, * 13 * but WITHOUT ANY WARRANTY; without even the implied warranty * 14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * 15 * See the GNU General Public License for more details. * 16 * * 17 * You should have received a copy of the GNU General Public License * 18 * along with this program. If not, see https://gnu.org/licenses/. * 19 * * 20 **************************************************************************/ 21 22 #include "prototypes.h" 23 #include "revision.h" 24 25 #include <ctype.h> 26 #ifdef __linux__ 27 #include <sys/ioctl.h> 28 #endif 29 #include <string.h> 30 #ifdef ENABLE_UTF8 31 #include <wchar.h> 32 #endif 33 34 #ifdef REVISION 35 #define BRANDING REVISION 36 #else 37 #define BRANDING PACKAGE_STRING 38 #endif 39 40 /* When having an older ncurses, then most likely libvte is older too. */ 41 #if defined(NCURSES_VERSION_PATCH) && (NCURSES_VERSION_PATCH < 20200212) 42 #define USING_OLDER_LIBVTE yes 43 #endif 44 45 static int *key_buffer = NULL; 46 /* A buffer for the keystrokes that haven't been handled yet. */ 47 static int *nextcodes = NULL; 48 /* A pointer pointing at the next keycode in the keystroke buffer. */ 49 static size_t capacity = 32; 50 /* The size of the keystroke buffer; gets doubled whenever needed. */ 51 static size_t waiting_codes = 0; 52 /* The number of key codes waiting in the keystroke buffer. */ 53 #ifdef ENABLE_NANORC 54 static const char *plants_pointer = NULL; 55 /* Points into the expansion string for the current implantation. */ 56 #endif 57 static int digit_count = 0; 58 /* How many digits of a three-digit character code we've eaten. */ 59 static bool reveal_cursor = FALSE; 60 /* Whether the cursor should be shown when waiting for input. */ 61 static bool linger_after_escape = FALSE; 62 /* Whether to give ncurses some time to get the next code. */ 63 static int countdown = 0; 64 /* The number of keystrokes left before we blank the status bar. */ 65 static size_t from_x = 0; 66 /* From where in the relevant line the current row is drawn. */ 67 static size_t till_x = 0; 68 /* Until where in the relevant line the current row is drawn. */ 69 static bool has_more = FALSE; 70 /* Whether the current line has more text after the displayed part. */ 71 static bool is_shorter = TRUE; 72 /* Whether a row's text is narrower than the screen's width. */ 73 #ifndef NANO_TINY 74 static size_t sequel_column = 0; 75 /* The starting column of the next chunk when softwrapping. */ 76 static bool recording = FALSE; 77 /* Whether we are in the process of recording a macro. */ 78 static int *macro_buffer = NULL; 79 /* A buffer where the recorded key codes are stored. */ 80 static size_t macro_length = 0; 81 /* The current length of the macro. */ 82 static size_t milestone = 0; 83 /* Where the last burst of recorded keystrokes started. */ 84 85 /* Add the given code to the macro buffer. */ 86 void add_to_macrobuffer(int code) 87 { 88 macro_length++; 89 macro_buffer = nrealloc(macro_buffer, macro_length * sizeof(int)); 90 macro_buffer[macro_length - 1] = code; 91 } 92 93 /* Start or stop the recording of keystrokes. */ 94 void record_macro(void) 95 { 96 recording = !recording; 97 98 if (recording) { 99 macro_length = 0; 100 statusline(REMARK, _("Recording a macro...")); 101 } else { 102 /* Snip the keystroke that invoked this function. */ 103 macro_length = milestone; 104 statusline(REMARK, _("Stopped recording")); 105 } 106 107 if (ISSET(STATEFLAGS)) 108 titlebar(NULL); 109 } 110 111 /* Copy the stored sequence of codes into the regular key buffer, 112 * so they will be "executed" again. */ 113 void run_macro(void) 114 { 115 if (recording) { 116 statusline(AHEM, _("Cannot run macro while recording")); 117 macro_length = milestone; 118 return; 119 } 120 121 if (macro_length == 0) { 122 statusline(AHEM, _("Macro is empty")); 123 return; 124 } 125 126 for (size_t index = macro_length; index > 0; ) 127 put_back(macro_buffer[--index]); 128 129 mute_modifiers = TRUE; 130 } 131 #endif /* !NANO_TINY */ 132 133 /* Allocate the requested space for the keystroke buffer. */ 134 void reserve_space_for(size_t newsize) 135 { 136 if (newsize < capacity) 137 die(_("Too much input at once\n")); 138 139 key_buffer = nrealloc(key_buffer, newsize * sizeof(int)); 140 nextcodes = key_buffer; 141 capacity = newsize; 142 } 143 144 /* Control character compatibility: 145 * 146 * - Ctrl-H is Backspace under ASCII, ANSI, VT100, and VT220. 147 * - Ctrl-I is Tab under ASCII, ANSI, VT100, VT220, and VT320. 148 * - Ctrl-M is Enter under ASCII, ANSI, VT100, VT220, and VT320. 149 * - Ctrl-Q is XON under ASCII, ANSI, VT100, VT220, and VT320. 150 * - Ctrl-S is XOFF under ASCII, ANSI, VT100, VT220, and VT320. 151 * - Ctrl-? is Delete under ASCII, ANSI, VT100, and VT220, 152 * but is Backspace under VT320. 153 * 154 * Note: the VT220 and VT320 also generate Esc [ 3 ~ for Delete. By default, 155 * xterm assumes it's running on a VT320 and generates Ctrl-? for Backspace 156 * and Esc [ 3 ~ for Delete. This causes problems for VT100-derived terminals 157 * such as the FreeBSD console, which expect Ctrl-H for Backspace and Ctrl-? 158 * for Delete, and on which ncurses translates the VT320 sequences to KEY_DC 159 * and [nothing]. We work around this conflict via the REBIND_DELETE flag: 160 * if it's set, we assume VT100 compatibility, and VT320 otherwise. 161 * 162 * Escape sequence compatibility: 163 * 164 * We support escape sequences for ANSI, VT100, VT220, VT320, the Linux 165 * console, the FreeBSD console, the Mach console, xterm, and Terminal, 166 * and some for Konsole, rxvt, Eterm, and iTerm2. Among these sequences, 167 * there are some conflicts: 168 * 169 * - PageUp on FreeBSD console == Tab on ANSI; the latter is omitted. 170 * (Ctrl-I is also Tab on ANSI, which we already support.) 171 * - PageDown on FreeBSD console == Center (5) on numeric keypad with 172 * NumLock off on Linux console; the latter is useless and omitted. 173 * - F1 on FreeBSD console == the mouse sequence on xterm/rxvt/Eterm; 174 * the latter is omitted. (Mouse input works only when KEY_MOUSE 175 * is generated on mouse events, not with the raw escape sequence.) 176 * - F9 on FreeBSD console == PageDown on Mach console; the former is 177 * omitted. (Moving the cursor is more important than a function key.) 178 * - F10 on FreeBSD console == PageUp on Mach console; the former is 179 * omitted. (Same as above.) */ 180 181 /* Read in at least one keystroke from the given window 182 * and save it (or them) in the keystroke buffer. */ 183 void read_keys_from(WINDOW *frame) 184 { 185 int input = ERR; 186 size_t errcount = 0; 187 #ifndef NANO_TINY 188 bool timed = FALSE; 189 #endif 190 191 /* Before reading the first keycode, display any pending screen updates. */ 192 doupdate(); 193 194 if (reveal_cursor && (!spotlighted || ISSET(SHOW_CURSOR) || currmenu == MSPELL) && 195 (LINES > 1 || lastmessage <= HUSH)) 196 curs_set(1); 197 198 #ifndef NANO_TINY 199 if (currmenu == MMAIN && (((ISSET(MINIBAR) || ISSET(ZERO) || LINES == 1) && 200 lastmessage > HUSH && lastmessage < ALERT && 201 lastmessage != INFO) || spotlighted)) { 202 timed = TRUE; 203 halfdelay(ISSET(QUICK_BLANK) ? 8 : 15); 204 /* Counteract a side effect of half-delay mode. */ 205 disable_kb_interrupt(); 206 } 207 #endif 208 209 /* Read in the first keycode, waiting for it to arrive. */ 210 while (input == ERR) { 211 input = wgetch(frame); 212 213 #ifndef NANO_TINY 214 if (the_window_resized) { 215 regenerate_screen(); 216 input = THE_WINDOW_RESIZED; 217 } 218 219 if (timed) { 220 timed = FALSE; 221 /* Leave half-delay mode. */ 222 raw(); 223 224 if (input == ERR) { 225 if (spotlighted || ISSET(ZERO) || LINES == 1) { 226 if (ISSET(ZERO) && lastmessage > VACUUM) 227 wredrawln(midwin, editwinrows - 1, 1); 228 lastmessage = VACUUM; 229 spotlighted = FALSE; 230 update_line(openfile->current, openfile->current_x); 231 wnoutrefresh(midwin); 232 curs_set(1); 233 } 234 if (ISSET(MINIBAR) && !ISSET(ZERO) && LINES > 1) 235 minibar(); 236 as_an_at = TRUE; 237 place_the_cursor(); 238 doupdate(); 239 continue; 240 } 241 } 242 #endif 243 /* When we've failed to get a keycode millions of times in a row, 244 * assume our input source is gone and die gracefully. We could 245 * check if errno is set to EIO ("Input/output error") and die in 246 * that case, but it's not always set properly. Argh. */ 247 if (input == ERR && ++errcount == 12345678) 248 die(_("Too many errors from stdin\n")); 249 } 250 251 curs_set(0); 252 253 /* When there is no keystroke buffer yet, allocate one. */ 254 if (!key_buffer) 255 reserve_space_for(capacity); 256 257 key_buffer[0] = input; 258 259 nextcodes = key_buffer; 260 waiting_codes = 1; 261 262 #ifndef NANO_TINY 263 /* Cancel the highlighting of a search match, if there still is one. */ 264 if (currmenu == MMAIN) { 265 refresh_needed |= spotlighted; 266 spotlighted = FALSE; 267 } 268 269 /* If we got a SIGWINCH, get out as the frame argument is no longer valid. */ 270 if (input == THE_WINDOW_RESIZED) 271 return; 272 273 /* Remember where the recording of this keystroke (or burst of them) started. */ 274 milestone = macro_length; 275 #endif 276 277 /* Read in any remaining key codes using non-blocking input. */ 278 nodelay(frame, TRUE); 279 280 /* After an ESC, when ncurses does not translate escape sequences, 281 * give the keyboard some time to bring the next code to ncurses. */ 282 if (input == ESC_CODE && (linger_after_escape || ISSET(RAW_SEQUENCES))) 283 napms(20); 284 285 while (TRUE) { 286 #ifndef NANO_TINY 287 if (recording) 288 add_to_macrobuffer(input); 289 #endif 290 input = wgetch(frame); 291 292 /* If there aren't any more characters, stop reading. */ 293 if (input == ERR) 294 break; 295 296 /* When the keystroke buffer is full, extend it. */ 297 if (waiting_codes == capacity) 298 reserve_space_for(2 * capacity); 299 300 key_buffer[waiting_codes++] = input; 301 } 302 303 /* Restore blocking-input mode. */ 304 nodelay(frame, FALSE); 305 306 #ifdef DEBUG 307 fprintf(stderr, "Sequence of hex codes:"); 308 for (size_t i = 0; i < waiting_codes; i++) 309 fprintf(stderr, " %3x", key_buffer[i]); 310 fprintf(stderr, "\n"); 311 #endif 312 } 313 314 /* Return the number of key codes waiting in the keystroke buffer. */ 315 size_t waiting_keycodes(void) 316 { 317 return waiting_codes; 318 } 319 320 /* Add the given keycode to the front of the keystroke buffer. */ 321 void put_back(int keycode) 322 { 323 /* If there is no room at the head of the keystroke buffer, make room. */ 324 if (nextcodes == key_buffer) { 325 if (waiting_codes == capacity) 326 reserve_space_for(2 * capacity); 327 memmove(key_buffer + 1, key_buffer, waiting_codes * sizeof(int)); 328 } else 329 nextcodes--; 330 331 *nextcodes = keycode; 332 waiting_codes++; 333 } 334 335 #ifdef ENABLE_NANORC 336 /* Set up the given expansion string to be ingested by the keyboard routines. */ 337 void implant(const char *string) 338 { 339 plants_pointer = string; 340 put_back(MORE_PLANTS); 341 342 mute_modifiers = TRUE; 343 } 344 345 /* Continue processing an expansion string. Returns either an error code, 346 * a plain character byte, or a placeholder for a command shortcut. */ 347 int get_code_from_plantation(void) 348 { 349 if (*plants_pointer == '{') { 350 char *closing = strchr(plants_pointer + 1, '}'); 351 352 if (!closing) 353 return MISSING_BRACE; 354 355 if (plants_pointer[1] == '{' && plants_pointer[2] == '}') { 356 plants_pointer += 3; 357 if (*plants_pointer != '\0') 358 put_back(MORE_PLANTS); 359 return '{'; 360 } 361 362 free(commandname); 363 free(planted_shortcut); 364 365 commandname = measured_copy(plants_pointer + 1, closing - plants_pointer - 1); 366 planted_shortcut = strtosc(commandname); 367 368 if (!planted_shortcut) 369 return NO_SUCH_FUNCTION; 370 371 plants_pointer = closing + 1; 372 373 if (*plants_pointer != '\0') 374 put_back(MORE_PLANTS); 375 376 return PLANTED_A_COMMAND; 377 } else { 378 char *opening = strchr(plants_pointer, '{'); 379 unsigned char firstbyte = *plants_pointer; 380 int length; 381 382 if (opening) { 383 length = opening - plants_pointer; 384 put_back(MORE_PLANTS); 385 } else 386 length = strlen(plants_pointer); 387 388 for (int index = length - 1; index > 0; index--) 389 put_back((unsigned char)plants_pointer[index]); 390 391 plants_pointer += length; 392 393 return (firstbyte) ? firstbyte : ERR; 394 } 395 } 396 #endif 397 398 /* Return one code from the keystroke buffer. If the buffer is empty 399 * but frame is given, first read more codes from the keyboard. */ 400 int get_input(WINDOW *frame) 401 { 402 if (waiting_codes) 403 spotlighted = FALSE; 404 else if (frame) 405 read_keys_from(frame); 406 407 if (waiting_codes) { 408 waiting_codes--; 409 #ifdef ENABLE_NANORC 410 if (*nextcodes == MORE_PLANTS) { 411 nextcodes++; 412 return get_code_from_plantation(); 413 } else 414 #endif 415 return *(nextcodes++); 416 } else 417 return ERR; 418 } 419 420 /* Return the arrow-key code that corresponds to the given letter. 421 * (This mapping is common to a handful of escape sequences.) */ 422 int arrow_from_ABCD(int letter) 423 { 424 if (letter < 'C') 425 return (letter == 'A' ? KEY_UP : KEY_DOWN); 426 else 427 return (letter == 'D' ? KEY_LEFT : KEY_RIGHT); 428 } 429 430 /* Translate a sequence that began with "Esc O" to its corresponding key code. */ 431 int convert_SS3_sequence(const int *seq, size_t length, int *consumed) 432 { 433 switch (seq[0]) { 434 case '1': 435 if (length > 3 && seq[1] == ';') { 436 *consumed = 4; 437 #ifndef NANO_TINY 438 switch (seq[2]) { 439 case '2': 440 if ('A' <= seq[3] && seq[3] <= 'D') { 441 /* Esc O 1 ; 2 A == Shift-Up on old Terminal. */ 442 /* Esc O 1 ; 2 B == Shift-Down on old Terminal. */ 443 /* Esc O 1 ; 2 C == Shift-Right on old Terminal. */ 444 /* Esc O 1 ; 2 D == Shift-Left on old Terminal. */ 445 shift_held = TRUE; 446 return arrow_from_ABCD(seq[3]); 447 } 448 break; 449 case '5': 450 switch (seq[3]) { 451 case 'A': /* Esc O 1 ; 5 A == Ctrl-Up on old Terminal. */ 452 return CONTROL_UP; 453 case 'B': /* Esc O 1 ; 5 B == Ctrl-Down on old Terminal. */ 454 return CONTROL_DOWN; 455 case 'C': /* Esc O 1 ; 5 C == Ctrl-Right on old Terminal. */ 456 return CONTROL_RIGHT; 457 case 'D': /* Esc O 1 ; 5 D == Ctrl-Left on old Terminal. */ 458 return CONTROL_LEFT; 459 } 460 break; 461 } 462 #endif 463 } 464 break; 465 case '2': /* Shift */ 466 case '3': /* Alt */ 467 case '4': /* Shift+Alt */ 468 case '5': /* Ctrl */ 469 case '6': /* Shift+Ctrl */ 470 case '7': /* Alt+Ctrl */ 471 case '8': /* Shift+Alt+Ctrl */ 472 if (length > 1) { 473 *consumed = 2; 474 /* Do not accept multiple modifiers. */ 475 if (seq[0] == '4' || seq[0] > '5') 476 return FOREIGN_SEQUENCE; 477 #ifndef NANO_TINY 478 switch (seq[1]) { 479 case 'A': /* Esc O 5 A == Ctrl-Up on Haiku. */ 480 return CONTROL_UP; 481 case 'B': /* Esc O 5 B == Ctrl-Down on Haiku. */ 482 return CONTROL_DOWN; 483 case 'C': /* Esc O 5 C == Ctrl-Right on Haiku. */ 484 return CONTROL_RIGHT; 485 case 'D': /* Esc O 5 D == Ctrl-Left on Haiku. */ 486 return CONTROL_LEFT; 487 } 488 #endif 489 /* Translate Shift+digit on the keypad to the digit 490 * (Esc O 2 p == Shift-0, ...), modifier+operator to 491 * the operator, and modifier+Enter to CR. */ 492 return (seq[1] - 0x40); 493 } 494 break; 495 case 'A': /* Esc O A == Up on VT100/VT320. */ 496 case 'B': /* Esc O B == Down on VT100/VT320. */ 497 case 'C': /* Esc O C == Right on VT100/VT320. */ 498 case 'D': /* Esc O D == Left on VT100/VT320. */ 499 return arrow_from_ABCD(seq[0]); 500 #ifndef NANO_TINY 501 case 'F': /* Esc O F == End on old xterm. */ 502 return KEY_END; 503 case 'H': /* Esc O H == Home on old xterm. */ 504 return KEY_HOME; 505 case 'M': /* Esc O M == Enter on numeric keypad 506 * with NumLock off on VT100/VT220/VT320. */ 507 return KEY_ENTER; 508 #endif 509 case 'P': /* Esc O P == F1 on VT100/VT220/VT320/xterm/Mach console. */ 510 case 'Q': /* Esc O Q == F2 on VT100/VT220/VT320/xterm/Mach console. */ 511 case 'R': /* Esc O R == F3 on VT100/VT220/VT320/xterm/Mach console. */ 512 case 'S': /* Esc O S == F4 on VT100/VT220/VT320/xterm/Mach console. */ 513 return KEY_F(seq[0] - 'O'); 514 #ifndef NANO_TINY 515 case 'T': /* Esc O T == F5 on Mach console. */ 516 case 'U': /* Esc O U == F6 on Mach console. */ 517 case 'V': /* Esc O V == F7 on Mach console. */ 518 case 'W': /* Esc O W == F8 on Mach console. */ 519 case 'X': /* Esc O X == F9 on Mach console. */ 520 case 'Y': /* Esc O Y == F10 on Mach console. */ 521 return KEY_F(seq[0] - 'O'); 522 case 'a': /* Esc O a == Ctrl-Up on rxvt/Eterm. */ 523 return CONTROL_UP; 524 case 'b': /* Esc O b == Ctrl-Down on rxvt/Eterm. */ 525 return CONTROL_DOWN; 526 case 'c': /* Esc O c == Ctrl-Right on rxvt/Eterm. */ 527 return CONTROL_RIGHT; 528 case 'd': /* Esc O d == Ctrl-Left on rxvt/Eterm. */ 529 return CONTROL_LEFT; 530 case 'j': /* Esc O j == '*' on numeric keypad with 531 * NumLock off on xterm/rxvt/Eterm. */ 532 return '*'; 533 case 'k': /* Esc O k == '+' on the same. */ 534 return '+'; 535 case 'l': /* Esc O l == ',' on VT100/VT220/VT320. */ 536 return ','; 537 case 'm': /* Esc O m == '-' on numeric keypad with 538 * NumLock off on VTnnn/xterm/rxvt/Eterm. */ 539 return '-'; 540 case 'n': /* Esc O n == Delete (.) on numeric keypad 541 * with NumLock off on rxvt/Eterm. */ 542 return KEY_DC; 543 case 'o': /* Esc O o == '/' on numeric keypad with 544 * NumLock off on VTnnn/xterm/rxvt/Eterm. */ 545 return '/'; 546 case 'p': /* Esc O p == Insert (0) on numeric keypad 547 * with NumLock off on rxvt/Eterm. */ 548 return KEY_IC; 549 case 'q': /* Esc O q == End (1) on the same. */ 550 return KEY_END; 551 case 'r': /* Esc O r == Down (2) on the same. */ 552 return KEY_DOWN; 553 case 's': /* Esc O s == PageDown (3) on the same. */ 554 return KEY_NPAGE; 555 case 't': /* Esc O t == Left (4) on the same. */ 556 return KEY_LEFT; 557 case 'v': /* Esc O v == Right (6) on the same. */ 558 return KEY_RIGHT; 559 case 'w': /* Esc O w == Home (7) on the same. */ 560 return KEY_HOME; 561 case 'x': /* Esc O x == Up (8) on the same. */ 562 return KEY_UP; 563 case 'y': /* Esc O y == PageUp (9) on the same. */ 564 return KEY_PPAGE; 565 #endif 566 } 567 568 return FOREIGN_SEQUENCE; 569 } 570 571 /* Translate a sequence that began with "Esc [" to its corresponding key code. */ 572 int convert_CSI_sequence(const int *seq, size_t length, int *consumed) 573 { 574 if (seq[0] < '9' && length > 1) 575 *consumed = 2; 576 577 switch (seq[0]) { 578 case '1': 579 if (length > 1 && seq[1] == '~') 580 /* Esc [ 1 ~ == Home on VT320/Linux console. */ 581 return KEY_HOME; 582 else if (length > 2 && seq[2] == '~') { 583 *consumed = 3; 584 switch (seq[1]) { 585 #ifndef NANO_TINY 586 case '1': /* Esc [ 1 1 ~ == F1 on rxvt/Eterm. */ 587 case '2': /* Esc [ 1 2 ~ == F2 on rxvt/Eterm. */ 588 case '3': /* Esc [ 1 3 ~ == F3 on rxvt/Eterm. */ 589 case '4': /* Esc [ 1 4 ~ == F4 on rxvt/Eterm. */ 590 #endif 591 case '5': /* Esc [ 1 5 ~ == F5 on xterm/rxvt/Eterm. */ 592 return KEY_F(seq[1] - '0'); 593 case '7': /* Esc [ 1 7 ~ == F6 on VT220/VT320/ 594 * Linux console/xterm/rxvt/Eterm. */ 595 case '8': /* Esc [ 1 8 ~ == F7 on the same. */ 596 case '9': /* Esc [ 1 9 ~ == F8 on the same. */ 597 return KEY_F(seq[1] - '1'); 598 } 599 } else if (length > 3 && seq[1] == ';') { 600 *consumed = 4; 601 #ifndef NANO_TINY 602 switch (seq[2]) { 603 case '2': 604 switch (seq[3]) { 605 case 'A': /* Esc [ 1 ; 2 A == Shift-Up on xterm. */ 606 case 'B': /* Esc [ 1 ; 2 B == Shift-Down on xterm. */ 607 case 'C': /* Esc [ 1 ; 2 C == Shift-Right on xterm. */ 608 case 'D': /* Esc [ 1 ; 2 D == Shift-Left on xterm. */ 609 shift_held = TRUE; 610 return arrow_from_ABCD(seq[3]); 611 case 'F': /* Esc [ 1 ; 2 F == Shift-End on xterm. */ 612 return SHIFT_END; 613 case 'H': /* Esc [ 1 ; 2 H == Shift-Home on xterm. */ 614 return SHIFT_HOME; 615 } 616 break; 617 case '9': /* To accommodate iTerm2 in "xterm mode". */ 618 case '3': 619 switch (seq[3]) { 620 case 'A': /* Esc [ 1 ; 3 A == Alt-Up on xterm. */ 621 return ALT_UP; 622 case 'B': /* Esc [ 1 ; 3 B == Alt-Down on xterm. */ 623 return ALT_DOWN; 624 case 'C': /* Esc [ 1 ; 3 C == Alt-Right on xterm. */ 625 return ALT_RIGHT; 626 case 'D': /* Esc [ 1 ; 3 D == Alt-Left on xterm. */ 627 return ALT_LEFT; 628 case 'F': /* Esc [ 1 ; 3 F == Alt-End on xterm. */ 629 return ALT_END; 630 case 'H': /* Esc [ 1 ; 3 H == Alt-Home on xterm. */ 631 return ALT_HOME; 632 } 633 break; 634 case '4': 635 /* When the arrow keys are held together with Shift+Meta, 636 * act as if they are Home/End/PgUp/PgDown with Shift. */ 637 switch (seq[3]) { 638 case 'A': /* Esc [ 1 ; 4 A == Shift-Alt-Up on xterm. */ 639 return SHIFT_PAGEUP; 640 case 'B': /* Esc [ 1 ; 4 B == Shift-Alt-Down on xterm. */ 641 return SHIFT_PAGEDOWN; 642 case 'C': /* Esc [ 1 ; 4 C == Shift-Alt-Right on xterm. */ 643 return SHIFT_END; 644 case 'D': /* Esc [ 1 ; 4 D == Shift-Alt-Left on xterm. */ 645 return SHIFT_HOME; 646 } 647 break; 648 case '5': 649 switch (seq[3]) { 650 case 'A': /* Esc [ 1 ; 5 A == Ctrl-Up on xterm. */ 651 return CONTROL_UP; 652 case 'B': /* Esc [ 1 ; 5 B == Ctrl-Down on xterm. */ 653 return CONTROL_DOWN; 654 case 'C': /* Esc [ 1 ; 5 C == Ctrl-Right on xterm. */ 655 return CONTROL_RIGHT; 656 case 'D': /* Esc [ 1 ; 5 D == Ctrl-Left on xterm. */ 657 return CONTROL_LEFT; 658 case 'E': /* Esc [ 1 ; 5 E == Ctrl-"Center" on xterm. */ 659 return KEY_CENTER; 660 case 'F': /* Esc [ 1 ; 5 F == Ctrl-End on xterm. */ 661 return CONTROL_END; 662 case 'H': /* Esc [ 1 ; 5 H == Ctrl-Home on xterm. */ 663 return CONTROL_HOME; 664 } 665 break; 666 case '6': 667 switch (seq[3]) { 668 case 'A': /* Esc [ 1 ; 6 A == Shift-Ctrl-Up on xterm. */ 669 return shiftcontrolup; 670 case 'B': /* Esc [ 1 ; 6 B == Shift-Ctrl-Down on xterm. */ 671 return shiftcontroldown; 672 case 'C': /* Esc [ 1 ; 6 C == Shift-Ctrl-Right on xterm. */ 673 return shiftcontrolright; 674 case 'D': /* Esc [ 1 ; 6 D == Shift-Ctrl-Left on xterm. */ 675 return shiftcontrolleft; 676 case 'F': /* Esc [ 1 ; 6 F == Shift-Ctrl-End on xterm. */ 677 return shiftcontrolend; 678 case 'H': /* Esc [ 1 ; 6 H == Shift-Ctrl-Home on xterm. */ 679 return shiftcontrolhome; 680 } 681 break; 682 } 683 #endif /* !NANO-TINY */ 684 } else if (length > 4 && seq[2] == ';' && seq[4] == '~') 685 /* Esc [ 1 n ; 2 ~ == F17...F20 on some terminals. */ 686 *consumed = 5; 687 break; 688 case '2': 689 if (length > 2 && seq[2] == '~') { 690 *consumed = 3; 691 switch (seq[1]) { 692 case '0': /* Esc [ 2 0 ~ == F9 on VT220/VT320/ 693 * Linux console/xterm/rxvt/Eterm. */ 694 return KEY_F(9); 695 case '1': /* Esc [ 2 1 ~ == F10 on the same. */ 696 return KEY_F(10); 697 case '3': /* Esc [ 2 3 ~ == F11 on the same. */ 698 return KEY_F(11); 699 case '4': /* Esc [ 2 4 ~ == F12 on the same. */ 700 return KEY_F(12); 701 #ifdef ENABLE_NANORC 702 case '5': /* Esc [ 2 5 ~ == F13 on the same. */ 703 return KEY_F(13); 704 case '6': /* Esc [ 2 6 ~ == F14 on the same. */ 705 return KEY_F(14); 706 case '8': /* Esc [ 2 8 ~ == F15 on the same. */ 707 return KEY_F(15); 708 case '9': /* Esc [ 2 9 ~ == F16 on the same. */ 709 return KEY_F(16); 710 #endif 711 } 712 } else if (length > 1 && seq[1] == '~') 713 /* Esc [ 2 ~ == Insert on VT220/VT320/ 714 * Linux console/xterm/Terminal. */ 715 return KEY_IC; 716 else if (length > 3 && seq[1] == ';' && seq[3] == '~') { 717 /* Esc [ 2 ; x ~ == modified Insert on xterm. */ 718 *consumed = 4; 719 #ifndef NANO_TINY 720 if (seq[2] == '3') 721 return ALT_INSERT; 722 #endif 723 } else if (length > 4 && seq[2] == ';' && seq[4] == '~') 724 /* Esc [ 2 n ; 2 ~ == F21...F24 on some terminals. */ 725 *consumed = 5; 726 #ifndef NANO_TINY 727 else if (length > 3 && seq[1] == '0' && seq[3] == '~') { 728 /* Esc [ 2 0 0 ~ == start of a bracketed paste, 729 * Esc [ 2 0 1 ~ == end of a bracketed paste. */ 730 *consumed = 4; 731 return (seq[2] == '0') ? START_OF_PASTE : END_OF_PASTE; 732 } else { 733 *consumed = length; 734 return FOREIGN_SEQUENCE; 735 } 736 #endif 737 break; 738 case '3': /* Esc [ 3 ~ == Delete on VT220/VT320/ 739 * Linux console/xterm/Terminal. */ 740 if (length > 1 && seq[1] == '~') 741 return KEY_DC; 742 if (length > 3 && seq[1] == ';' && seq[3] == '~') { 743 *consumed = 4; 744 #ifndef NANO_TINY 745 if (seq[2] == '2') 746 /* Esc [ 3 ; 2 ~ == Shift-Delete on xterm/Terminal. */ 747 return SHIFT_DELETE; 748 if (seq[2] == '3') 749 /* Esc [ 3 ; 3 ~ == Alt-Delete on xterm/rxvt/Eterm/Terminal. */ 750 return ALT_DELETE; 751 if (seq[2] == '5') 752 /* Esc [ 3 ; 5 ~ == Ctrl-Delete on xterm. */ 753 return CONTROL_DELETE; 754 if (seq[2] == '6') 755 /* Esc [ 3 ; 6 ~ == Ctrl-Shift-Delete on xterm. */ 756 return controlshiftdelete; 757 #endif 758 } 759 #ifndef NANO_TINY 760 if (length > 1 && seq[1] == '$') 761 /* Esc [ 3 $ == Shift-Delete on urxvt. */ 762 return SHIFT_DELETE; 763 if (length > 1 && seq[1] == '^') 764 /* Esc [ 3 ^ == Ctrl-Delete on urxvt. */ 765 return CONTROL_DELETE; 766 if (length > 1 && seq[1] == '@') 767 /* Esc [ 3 @ == Ctrl-Shift-Delete on urxvt. */ 768 return controlshiftdelete; 769 if (length > 2 && seq[2] == '~') 770 /* Esc [ 3 n ~ == F17...F20 on some terminals. */ 771 *consumed = 3; 772 #endif 773 break; 774 case '4': /* Esc [ 4 ~ == End on VT220/VT320/ 775 * Linux console/xterm. */ 776 if (length > 1 && seq[1] == '~') 777 return KEY_END; 778 break; 779 case '5': /* Esc [ 5 ~ == PageUp on VT220/VT320/ 780 * Linux console/xterm/Eterm/urxvt/Terminal */ 781 if (length > 1 && seq[1] == '~') 782 return KEY_PPAGE; 783 else if (length > 3 && seq[1] == ';' && seq[3] == '~') { 784 *consumed = 4; 785 #ifndef NANO_TINY 786 if (seq[2] == '2') 787 return shiftaltup; 788 if (seq[2] == '3') 789 return ALT_PAGEUP; 790 #endif 791 } 792 break; 793 case '6': /* Esc [ 6 ~ == PageDown on VT220/VT320/ 794 * Linux console/xterm/Eterm/urxvt/Terminal */ 795 if (length > 1 && seq[1] == '~') 796 return KEY_NPAGE; 797 else if (length > 3 && seq[1] == ';' && seq[3] == '~') { 798 *consumed = 4; 799 #ifndef NANO_TINY 800 if (seq[2] == '2') 801 return shiftaltdown; 802 if (seq[2] == '3') 803 return ALT_PAGEDOWN; 804 #endif 805 } 806 break; 807 case '7': /* Esc [ 7 ~ == Home on Eterm/rxvt; 808 * Esc [ 7 $ == Shift-Home on Eterm/rxvt; 809 * Esc [ 7 ^ == Control-Home on Eterm/rxvt; 810 * Esc [ 7 @ == Shift-Control-Home on same. */ 811 if (length > 1 && seq[1] == '~') 812 return KEY_HOME; 813 else if (length > 1 && seq[1] == '$') 814 return SHIFT_HOME; 815 else if (length > 1 && seq[1] == '^') 816 return CONTROL_HOME; 817 #ifndef NANO_TINY 818 else if (length > 1 && seq[1] == '@') 819 return shiftcontrolhome; 820 #endif 821 break; 822 case '8': /* Esc [ 8 ~ == End on Eterm/rxvt; 823 * Esc [ 8 $ == Shift-End on Eterm/rxvt; 824 * Esc [ 8 ^ == Control-End on Eterm/rxvt; 825 * Esc [ 8 @ == Shift-Control-End on same. */ 826 if (length > 1 && seq[1] == '~') 827 return KEY_END; 828 else if (length > 1 && seq[1] == '$') 829 return SHIFT_END; 830 else if (length > 1 && seq[1] == '^') 831 return CONTROL_END; 832 #ifndef NANO_TINY 833 else if (length > 1 && seq[1] == '@') 834 return shiftcontrolend; 835 #endif 836 break; 837 case '9': /* Esc [ 9 == Delete on Mach console. */ 838 return KEY_DC; 839 case '@': /* Esc [ @ == Insert on Mach console. */ 840 return KEY_IC; 841 case 'A': /* Esc [ A == Up on ANSI/VT220/Linux console/ 842 * FreeBSD console/Mach console/xterm/Eterm/ 843 * urxvt/Gnome and Xfce Terminal. */ 844 case 'B': /* Esc [ B == Down on the same. */ 845 case 'C': /* Esc [ C == Right on the same. */ 846 case 'D': /* Esc [ D == Left on the same. */ 847 return arrow_from_ABCD(seq[0]); 848 case 'F': /* Esc [ F == End on FreeBSD console/Eterm. */ 849 return KEY_END; 850 case 'G': /* Esc [ G == PageDown on FreeBSD console. */ 851 return KEY_NPAGE; 852 case 'H': /* Esc [ H == Home on ANSI/VT220/FreeBSD 853 * console/Mach console/Eterm. */ 854 return KEY_HOME; 855 case 'I': /* Esc [ I == PageUp on FreeBSD console. */ 856 return KEY_PPAGE; 857 case 'L': /* Esc [ L == Insert on ANSI/FreeBSD console. */ 858 return KEY_IC; 859 #ifndef NANO_TINY 860 case 'M': /* Esc [ M == F1 on FreeBSD console. */ 861 case 'N': /* Esc [ N == F2 on FreeBSD console. */ 862 case 'O': /* Esc [ O == F3 on FreeBSD console. */ 863 case 'P': /* Esc [ P == F4 on FreeBSD console. */ 864 case 'Q': /* Esc [ Q == F5 on FreeBSD console. */ 865 case 'R': /* Esc [ R == F6 on FreeBSD console. */ 866 case 'S': /* Esc [ S == F7 on FreeBSD console. */ 867 case 'T': /* Esc [ T == F8 on FreeBSD console. */ 868 return KEY_F(seq[0] - 'L'); 869 #endif 870 case 'U': /* Esc [ U == PageDown on Mach console. */ 871 return KEY_NPAGE; 872 case 'V': /* Esc [ V == PageUp on Mach console. */ 873 return KEY_PPAGE; 874 #ifndef NANO_TINY 875 case 'W': /* Esc [ W == F11 on FreeBSD console. */ 876 return KEY_F(11); 877 case 'X': /* Esc [ X == F12 on FreeBSD console. */ 878 return KEY_F(12); 879 #endif 880 case 'Y': /* Esc [ Y == End on Mach console. */ 881 return KEY_END; 882 case 'Z': /* Esc [ Z == Shift-Tab on ANSI/Linux console/ 883 * FreeBSD console/xterm/rxvt/Terminal. */ 884 return SHIFT_TAB; 885 #ifndef NANO_TINY 886 case 'a': /* Esc [ a == Shift-Up on rxvt/Eterm. */ 887 case 'b': /* Esc [ b == Shift-Down on rxvt/Eterm. */ 888 case 'c': /* Esc [ c == Shift-Right on rxvt/Eterm. */ 889 case 'd': /* Esc [ d == Shift-Left on rxvt/Eterm. */ 890 shift_held = TRUE; 891 return arrow_from_ABCD(seq[0] - 0x20); 892 #endif 893 case '[': 894 if (length > 1) { 895 *consumed = 2; 896 if ('@' < seq[1] && seq[1] < 'F') 897 /* Esc [ [ A == F1 on Linux console. */ 898 /* Esc [ [ B == F2 on Linux console. */ 899 /* Esc [ [ C == F3 on Linux console. */ 900 /* Esc [ [ D == F4 on Linux console. */ 901 /* Esc [ [ E == F5 on Linux console. */ 902 return KEY_F(seq[1] - '@'); 903 } 904 break; 905 } 906 907 return FOREIGN_SEQUENCE; 908 } 909 910 /* Interpret an escape sequence that has the given post-ESC starter byte 911 * and with the rest of the sequence still in the keystroke buffer. */ 912 int parse_escape_sequence(int starter) 913 { 914 int consumed = 1; 915 int keycode = 0; 916 917 if (starter == 'O') 918 keycode = convert_SS3_sequence(nextcodes, waiting_codes, &consumed); 919 else if (starter == '[') 920 keycode = convert_CSI_sequence(nextcodes, waiting_codes, &consumed); 921 922 /* Skip the consumed sequence elements. */ 923 waiting_codes -= consumed; 924 nextcodes += consumed; 925 926 return keycode; 927 } 928 929 #define PROCEED -44 930 931 /* For each consecutive call, gather the given digit into a three-digit 932 * decimal byte code (from 000 to 255). Return the assembled code when 933 * it is complete, but until then return PROCEED when the given digit is 934 * valid, and the given digit itself otherwise. */ 935 int assemble_byte_code(int keycode) 936 { 937 static int byte = 0; 938 939 digit_count++; 940 941 /* The first digit is either 0, 1, or 2 (checked before the call). */ 942 if (digit_count == 1) { 943 byte = (keycode - '0') * 100; 944 return PROCEED; 945 } 946 947 /* The second digit may be at most 5 if the first was 2. */ 948 if (digit_count == 2) { 949 if (byte < 200 || keycode <= '5') { 950 byte += (keycode - '0') * 10; 951 return PROCEED; 952 } else 953 return keycode; 954 } 955 956 /* The third digit may be at most 5 if the first two were 2 and 5. */ 957 if (byte < 250 || keycode <= '5') 958 return (byte + keycode - '0'); 959 else 960 return keycode; 961 } 962 963 /* Translate a normal ASCII character into its corresponding control code. 964 * The following groups of control keystrokes are equivalent: 965 * Ctrl-2 == Ctrl-@ == Ctrl-` == Ctrl-Space 966 * Ctrl-3 == Ctrl-[ == <Esc> 967 * Ctrl-4 == Ctrl-\ == Ctrl-| 968 * Ctrl-5 == Ctrl-] 969 * Ctrl-6 == Ctrl-^ == Ctrl-~ 970 * Ctrl-7 == Ctrl-/ == Ctrl-_ 971 * Ctrl-8 == Ctrl-? */ 972 int convert_to_control(int kbinput) 973 { 974 if ('@' <= kbinput && kbinput <= '_') 975 return kbinput - '@'; 976 if ('`' <= kbinput && kbinput <= '~') 977 return kbinput - '`'; 978 if ('3' <= kbinput && kbinput <= '7') 979 return kbinput - 24; 980 if (kbinput == '?' || kbinput == '8') 981 return DEL_CODE; 982 if (kbinput == ' ' || kbinput == '2') 983 return 0; 984 if (kbinput == '/') 985 return 31; 986 987 return kbinput; 988 } 989 990 /* Extract one keystroke from the input stream. Translate escape sequences 991 * and possibly keypad codes into their corresponding values. Set meta_key 992 * to TRUE when appropriate. Supported keypad keystrokes are: the arrow keys, 993 * Insert, Delete, Home, End, PageUp, PageDown, Enter, and Backspace (many of 994 * them also when modified with Shift, Ctrl, Alt, Shift+Ctrl, or Shift+Alt), 995 * the function keys (F1-F12), and the numeric keypad with NumLock off. */ 996 int parse_kbinput(WINDOW *frame) 997 { 998 static bool first_escape_was_alone = FALSE; 999 static bool last_escape_was_alone = FALSE; 1000 static int escapes = 0; 1001 int keycode; 1002 1003 meta_key = FALSE; 1004 shift_held = FALSE; 1005 1006 /* Get one code from the input stream. */ 1007 keycode = get_input(frame); 1008 1009 /* For an Esc, remember whether the last two arrived by themselves. 1010 * Then increment the counter, rolling around on three escapes. */ 1011 if (keycode == ESC_CODE) { 1012 first_escape_was_alone = last_escape_was_alone; 1013 last_escape_was_alone = (waiting_codes == 0); 1014 if (digit_count > 0) { 1015 digit_count = 0; 1016 escapes = 1; 1017 } else if (++escapes > 2) 1018 escapes = (last_escape_was_alone ? 0 : 1); 1019 return ERR; 1020 } else if (keycode == ERR) 1021 return ERR; 1022 1023 if (escapes == 0) { 1024 /* Most key codes in byte range cannot be special keys. */ 1025 if (keycode < 0xFF && keycode != '\t' && keycode != DEL_CODE) 1026 return keycode; 1027 } else if (escapes == 1) { 1028 escapes = 0; 1029 /* Codes out of ASCII printable range cannot form an escape sequence. */ 1030 if (keycode < 0x20 || 0x7E < keycode) { 1031 if (keycode == '\t') 1032 return SHIFT_TAB; 1033 #ifndef NANO_TINY 1034 else if (keycode == KEY_BACKSPACE || keycode == '\b' || 1035 keycode == DEL_CODE) 1036 return CONTROL_SHIFT_DELETE; 1037 #endif 1038 #ifdef ENABLE_UTF8 1039 else if (0xC0 <= keycode && keycode <= 0xFF && using_utf8) { 1040 while (waiting_codes && 0x80 <= nextcodes[0] && nextcodes[0] <= 0xBF) 1041 get_input(NULL); 1042 return FOREIGN_SEQUENCE; 1043 } 1044 #endif 1045 else if (keycode < 0x20 && !last_escape_was_alone) 1046 meta_key = TRUE; 1047 } else if (waiting_codes == 0 || nextcodes[0] == ESC_CODE || 1048 (keycode != 'O' && keycode != '[')) { 1049 if ('A' <= keycode && keycode <= 'Z' && !shifted_metas) 1050 keycode |= 0x20; 1051 meta_key = TRUE; 1052 } else 1053 keycode = parse_escape_sequence(keycode); 1054 } else { 1055 escapes = 0; 1056 if (keycode == '[' && waiting_codes && 1057 (('A' <= nextcodes[0] && nextcodes[0] <= 'D') || 1058 ('a' <= nextcodes[0] && nextcodes[0] <= 'd'))) { 1059 /* An iTerm2/Eterm/rxvt double-escape sequence: Esc Esc [ X 1060 * for Option+arrow, or Esc Esc [ x for Shift+Alt+arrow. */ 1061 switch (get_input(NULL)) { 1062 case 'A': return KEY_HOME; 1063 case 'B': return KEY_END; 1064 case 'C': return CONTROL_RIGHT; 1065 case 'D': return CONTROL_LEFT; 1066 #ifndef NANO_TINY 1067 case 'a': shift_held = TRUE; return KEY_PPAGE; 1068 case 'b': shift_held = TRUE; return KEY_NPAGE; 1069 case 'c': shift_held = TRUE; return KEY_HOME; 1070 case 'd': shift_held = TRUE; return KEY_END; 1071 #endif 1072 } 1073 } else if (waiting_codes && nextcodes[0] != ESC_CODE && 1074 (keycode == '[' || keycode == 'O')) { 1075 keycode = parse_escape_sequence(keycode); 1076 meta_key = TRUE; 1077 } else if ('0' <= keycode && (keycode <= '2' || 1078 (keycode <= '9' && digit_count > 0))) { 1079 /* Two escapes followed by one digit: byte sequence mode. */ 1080 int byte = assemble_byte_code(keycode); 1081 1082 /* If the decimal byte value is not yet complete, return nothing. */ 1083 if (byte == PROCEED) { 1084 escapes = 2; 1085 return ERR; 1086 } 1087 #ifdef ENABLE_UTF8 1088 else if (byte > 0x7F && using_utf8) { 1089 /* Convert the code to the corresponding Unicode, and 1090 * put the second byte back into the keyboard buffer. */ 1091 if (byte < 0xC0) { 1092 put_back((unsigned char)byte); 1093 return 0xC2; 1094 } else { 1095 put_back((unsigned char)(byte - 0x40)); 1096 return 0xC3; 1097 } 1098 } 1099 #endif 1100 else if (byte == '\t' || byte == DEL_CODE) 1101 keycode = byte; 1102 else 1103 return byte; 1104 } else if (digit_count == 0) { 1105 /* If the first escape arrived alone but not the second, then it 1106 * is a Meta keystroke; otherwise, it is an "Esc Esc control". */ 1107 if (first_escape_was_alone && !last_escape_was_alone) { 1108 if ('A' <= keycode && keycode <= 'Z' && !shifted_metas) 1109 keycode |= 0x20; 1110 meta_key = TRUE; 1111 } else 1112 keycode = convert_to_control(keycode); 1113 } 1114 } 1115 1116 if (keycode == controlleft) 1117 return CONTROL_LEFT; 1118 else if (keycode == controlright) 1119 return CONTROL_RIGHT; 1120 else if (keycode == controlup) 1121 return CONTROL_UP; 1122 else if (keycode == controldown) 1123 return CONTROL_DOWN; 1124 else if (keycode == controlhome) 1125 return CONTROL_HOME; 1126 else if (keycode == controlend) 1127 return CONTROL_END; 1128 #ifndef NANO_TINY 1129 else if (keycode == controldelete) 1130 return CONTROL_DELETE; 1131 else if (keycode == controlshiftdelete) 1132 return CONTROL_SHIFT_DELETE; 1133 else if (keycode == shiftup) { 1134 shift_held = TRUE; 1135 return KEY_UP; 1136 } else if (keycode == shiftdown) { 1137 shift_held = TRUE; 1138 return KEY_DOWN; 1139 } else if (keycode == shiftcontrolleft) { 1140 shift_held = TRUE; 1141 return CONTROL_LEFT; 1142 } else if (keycode == shiftcontrolright) { 1143 shift_held = TRUE; 1144 return CONTROL_RIGHT; 1145 } else if (keycode == shiftcontrolup) { 1146 shift_held = TRUE; 1147 return CONTROL_UP; 1148 } else if (keycode == shiftcontroldown) { 1149 shift_held = TRUE; 1150 return CONTROL_DOWN; 1151 } else if (keycode == shiftcontrolhome) { 1152 shift_held = TRUE; 1153 return CONTROL_HOME; 1154 } else if (keycode == shiftcontrolend) { 1155 shift_held = TRUE; 1156 return CONTROL_END; 1157 } else if (keycode == altleft) 1158 return ALT_LEFT; 1159 else if (keycode == altright) 1160 return ALT_RIGHT; 1161 else if (keycode == altup) 1162 return ALT_UP; 1163 else if (keycode == altdown) 1164 return ALT_DOWN; 1165 else if (keycode == althome) 1166 return ALT_HOME; 1167 else if (keycode == altend) 1168 return ALT_END; 1169 else if (keycode == altpageup) 1170 return ALT_PAGEUP; 1171 else if (keycode == altpagedown) 1172 return ALT_PAGEDOWN; 1173 else if (keycode == altinsert) 1174 return ALT_INSERT; 1175 else if (keycode == altdelete) 1176 return ALT_DELETE; 1177 else if (keycode == shiftaltleft) { 1178 shift_held = TRUE; 1179 return KEY_HOME; 1180 } else if (keycode == shiftaltright) { 1181 shift_held = TRUE; 1182 return KEY_END; 1183 } else if (keycode == shiftaltup) { 1184 shift_held = TRUE; 1185 return KEY_PPAGE; 1186 } else if (keycode == shiftaltdown) { 1187 shift_held = TRUE; 1188 return KEY_NPAGE; 1189 } else if ((KEY_F0 + 24) < keycode && keycode < (KEY_F0 + 64)) 1190 return FOREIGN_SEQUENCE; 1191 #endif 1192 1193 #ifdef __linux__ 1194 /* When not running under X, check for the bare arrow keys whether 1195 * Shift/Ctrl/Alt are being held together with them. */ 1196 unsigned char modifiers = 6; 1197 1198 /* Modifiers are: Alt (8), Ctrl (4), Shift (1). */ 1199 if (on_a_vt && !mute_modifiers && ioctl(0, TIOCLINUX, &modifiers) >= 0) { 1200 #ifndef NANO_TINY 1201 /* Is Shift being held? */ 1202 if (modifiers & 0x01) { 1203 if (keycode == '\t') 1204 return SHIFT_TAB; 1205 if (keycode == KEY_DC && modifiers == 0x01) 1206 return SHIFT_DELETE; 1207 if (keycode == KEY_DC && modifiers == 0x05) 1208 return CONTROL_SHIFT_DELETE; 1209 if (!meta_key) 1210 shift_held = TRUE; 1211 } 1212 /* Is only Alt being held? */ 1213 if (modifiers == 0x08) { 1214 switch (keycode) { 1215 case KEY_UP: return ALT_UP; 1216 case KEY_DOWN: return ALT_DOWN; 1217 case KEY_HOME: return ALT_HOME; 1218 case KEY_END: return ALT_END; 1219 case KEY_PPAGE: return ALT_PAGEUP; 1220 case KEY_NPAGE: return ALT_PAGEDOWN; 1221 case KEY_DC: return ALT_DELETE; 1222 case KEY_IC: return ALT_INSERT; 1223 } 1224 } 1225 #endif 1226 /* Is Ctrl being held? */ 1227 if (modifiers & 0x04) { 1228 switch (keycode) { 1229 case KEY_UP: return CONTROL_UP; 1230 case KEY_DOWN: return CONTROL_DOWN; 1231 case KEY_LEFT: return CONTROL_LEFT; 1232 case KEY_RIGHT: return CONTROL_RIGHT; 1233 case KEY_HOME: return CONTROL_HOME; 1234 case KEY_END: return CONTROL_END; 1235 case KEY_DC: return CONTROL_DELETE; 1236 } 1237 } 1238 #ifndef NANO_TINY 1239 /* Are both Shift and Alt being held? */ 1240 if ((modifiers & 0x09) == 0x09) { 1241 switch (keycode) { 1242 case KEY_UP: return KEY_PPAGE; 1243 case KEY_DOWN: return KEY_NPAGE; 1244 case KEY_LEFT: return KEY_HOME; 1245 case KEY_RIGHT: return KEY_END; 1246 } 1247 } 1248 #endif 1249 } 1250 #endif /* __linux__ */ 1251 1252 /* Spurious codes from VTE -- see https://sv.gnu.org/bugs/?64578. */ 1253 if (keycode == mousefocusin || keycode == mousefocusout) 1254 return ERR; 1255 1256 switch (keycode) { 1257 case KEY_SLEFT: 1258 shift_held = TRUE; 1259 return KEY_LEFT; 1260 case KEY_SRIGHT: 1261 shift_held = TRUE; 1262 return KEY_RIGHT; 1263 #ifdef KEY_SR 1264 #ifdef KEY_SUP /* Ncurses doesn't know Shift+Up. */ 1265 case KEY_SUP: 1266 #endif 1267 case KEY_SR: /* Scroll backward, on Xfce4-terminal. */ 1268 shift_held = TRUE; 1269 return KEY_UP; 1270 #endif 1271 #ifdef KEY_SF 1272 #ifdef KEY_SDOWN /* Ncurses doesn't know Shift+Down. */ 1273 case KEY_SDOWN: 1274 #endif 1275 case KEY_SF: /* Scroll forward, on Xfce4-terminal. */ 1276 shift_held = TRUE; 1277 return KEY_DOWN; 1278 #endif 1279 #ifdef KEY_SHOME /* HP-UX 10-11 doesn't know Shift+Home. */ 1280 case KEY_SHOME: 1281 #endif 1282 case SHIFT_HOME: 1283 shift_held = TRUE; 1284 case KEY_A1: /* Home (7) on keypad with NumLock off. */ 1285 return KEY_HOME; 1286 #ifdef KEY_SEND /* HP-UX 10-11 doesn't know Shift+End. */ 1287 case KEY_SEND: 1288 #endif 1289 case SHIFT_END: 1290 shift_held = TRUE; 1291 case KEY_C1: /* End (1) on keypad with NumLock off. */ 1292 return KEY_END; 1293 #ifdef KEY_EOL 1294 case KEY_EOL: /* Ctrl+End on rxvt-unicode. */ 1295 return CONTROL_END; 1296 #endif 1297 #ifndef NANO_TINY 1298 #ifdef KEY_SPREVIOUS 1299 case KEY_SPREVIOUS: 1300 #endif 1301 case SHIFT_PAGEUP: /* Fake key, from Shift+Alt+Up. */ 1302 shift_held = TRUE; 1303 #endif 1304 case KEY_A3: /* PageUp (9) on keypad with NumLock off. */ 1305 return KEY_PPAGE; 1306 #ifndef NANO_TINY 1307 #ifdef KEY_SNEXT 1308 case KEY_SNEXT: 1309 #endif 1310 case SHIFT_PAGEDOWN: /* Fake key, from Shift+Alt+Down. */ 1311 shift_held = TRUE; 1312 #endif 1313 case KEY_C3: /* PageDown (3) on keypad with NumLock off. */ 1314 return KEY_NPAGE; 1315 /* When requested, swap meanings of keycodes for <Bsp> and <Del>. */ 1316 case DEL_CODE: 1317 case KEY_BACKSPACE: 1318 return (ISSET(REBIND_DELETE) ? KEY_DC : KEY_BACKSPACE); 1319 case KEY_DC: 1320 return (ISSET(REBIND_DELETE) ? KEY_BACKSPACE : KEY_DC); 1321 case KEY_SDC: 1322 return SHIFT_DELETE; 1323 case KEY_SCANCEL: 1324 return KEY_CANCEL; 1325 case KEY_SSUSPEND: 1326 case KEY_SUSPEND: 1327 return 0x1A; /* The ASCII code for Ctrl+Z. */ 1328 case KEY_BTAB: 1329 return SHIFT_TAB; 1330 1331 case KEY_SBEG: 1332 case KEY_BEG: 1333 case KEY_B2: /* Center (5) on keypad with NumLock off. */ 1334 #ifdef PDCURSES 1335 case KEY_SHIFT_L: 1336 case KEY_SHIFT_R: 1337 case KEY_CONTROL_L: 1338 case KEY_CONTROL_R: 1339 case KEY_ALT_L: 1340 case KEY_ALT_R: 1341 #endif 1342 #ifdef KEY_RESIZE /* SunOS 5.7-5.9 doesn't know KEY_RESIZE. */ 1343 case KEY_RESIZE: 1344 #endif 1345 #ifndef NANO_TINY 1346 case KEY_FRESH: 1347 #endif 1348 return ERR; /* Ignore this keystroke. */ 1349 } 1350 1351 return keycode; 1352 } 1353 1354 /* Read in a single keystroke, ignoring any that are invalid. */ 1355 int get_kbinput(WINDOW *frame, bool showcursor) 1356 { 1357 int kbinput = ERR; 1358 1359 reveal_cursor = showcursor; 1360 1361 /* Extract one keystroke from the input stream. */ 1362 while (kbinput == ERR) 1363 kbinput = parse_kbinput(frame); 1364 1365 /* If we read from the edit window, blank the status bar when it's time. */ 1366 if (frame == midwin) 1367 blank_it_when_expired(); 1368 1369 return kbinput; 1370 } 1371 1372 #ifdef ENABLE_UTF8 1373 #define INVALID_DIGIT -77 1374 1375 /* For each consecutive call, gather the given symbol into a Unicode code point. 1376 * When it's complete (with six digits, or when Space or Enter is typed), return 1377 * the assembled code. Until then, return PROCEED when the symbol is valid, or 1378 * an error code for anything other than hexadecimal, Space, and Enter. */ 1379 long assemble_unicode(int symbol) 1380 { 1381 static long unicode = 0; 1382 static int digits = 0; 1383 long outcome = PROCEED; 1384 1385 if ('0' <= symbol && symbol <= '9') 1386 unicode = (unicode << 4) + symbol - '0'; 1387 else if ('a' <= (symbol | 0x20) && (symbol | 0x20) <= 'f') 1388 unicode = (unicode << 4) + (symbol | 0x20) - 'a' + 10; 1389 else if (symbol == '\r' || symbol == ' ') 1390 outcome = unicode; 1391 else 1392 outcome = INVALID_DIGIT; 1393 1394 /* If also the sixth digit was a valid hexadecimal value, then the 1395 * Unicode sequence is complete, so return it (when it's valid). */ 1396 if (++digits == 6 && outcome == PROCEED) 1397 outcome = (unicode < 0x110000) ? unicode : INVALID_DIGIT; 1398 1399 /* Show feedback only when editing, not when at a prompt. */ 1400 if (outcome == PROCEED && currmenu == MMAIN) { 1401 char partial[7] = " "; 1402 1403 sprintf(partial + 6 - digits, "%0*lX", digits, unicode); 1404 1405 /* TRANSLATORS: This is shown while a six-digit hexadecimal 1406 * Unicode character code (%s) is being typed in. */ 1407 statusline(INFO, _("Unicode Input: %s"), partial); 1408 } 1409 1410 /* If we have an end result, reset the value and the counter. */ 1411 if (outcome != PROCEED) { 1412 unicode = 0; 1413 digits = 0; 1414 } 1415 1416 return outcome; 1417 } 1418 #endif /* ENABLE_UTF8 */ 1419 1420 /* Read in one control character (or an iTerm/Eterm/rxvt double Escape), 1421 * or convert a series of six digits into a Unicode codepoint. Return 1422 * in count either 1 (for a control character or the first byte of a 1423 * multibyte sequence), or 2 (for an iTerm/Eterm/rxvt double Escape). */ 1424 int *parse_verbatim_kbinput(WINDOW *frame, size_t *count) 1425 { 1426 int keycode, *yield; 1427 1428 reveal_cursor = TRUE; 1429 1430 keycode = get_input(frame); 1431 1432 #ifndef NANO_TINY 1433 /* When the window was resized, abort and return nothing. */ 1434 if (keycode == THE_WINDOW_RESIZED) { 1435 *count = 999; 1436 return NULL; 1437 } 1438 #endif 1439 1440 /* Reserve ample space for the possible result. */ 1441 yield = nmalloc(6 * sizeof(int)); 1442 1443 #ifdef ENABLE_UTF8 1444 /* If the key code is a hexadecimal digit, commence Unicode input. */ 1445 if (using_utf8 && isxdigit(keycode)) { 1446 long unicode = assemble_unicode(keycode); 1447 char multibyte[MB_CUR_MAX]; 1448 1449 reveal_cursor = FALSE; 1450 1451 /* Gather at most six hexadecimal digits. */ 1452 while (unicode == PROCEED) { 1453 keycode = get_input(frame); 1454 unicode = assemble_unicode(keycode); 1455 } 1456 1457 #ifndef NANO_TINY 1458 if (keycode == THE_WINDOW_RESIZED) { 1459 *count = 999; 1460 free(yield); 1461 return NULL; 1462 } 1463 #endif 1464 /* For an invalid keystroke, discard its possible continuation bytes. */ 1465 if (unicode == INVALID_DIGIT) { 1466 if (keycode == ESC_CODE && waiting_codes) { 1467 get_input(NULL); 1468 while (waiting_codes && 0x1F < nextcodes[0] && nextcodes[0] < 0x40) 1469 get_input(NULL); 1470 if (waiting_codes && 0x3F < nextcodes[0] && nextcodes[0] < 0x7F) 1471 get_input(NULL); 1472 } else if (0xC0 <= keycode && keycode <= 0xFF) 1473 while (waiting_codes && 0x7F < nextcodes[0] && nextcodes[0] < 0xC0) 1474 get_input(NULL); 1475 } 1476 1477 /* Convert the Unicode value to a multibyte sequence. */ 1478 *count = wctomb(multibyte, unicode); 1479 1480 if (*count > MAXCHARLEN) 1481 *count = 0; 1482 1483 /* Change the multibyte character into a series of integers. */ 1484 for (size_t i = 0; i < *count; i++) 1485 yield[i] = (int)multibyte[i]; 1486 1487 return yield; 1488 } 1489 #endif /* ENABLE_UTF8 */ 1490 1491 yield[0] = keycode; 1492 1493 /* In case of an escape, take also a second code, as it might be another 1494 * escape (on iTerm2/rxvt) or a control code (for M-Bsp and M-Enter). */ 1495 if (keycode == ESC_CODE && waiting_codes) { 1496 yield[1] = get_input(NULL); 1497 *count = 2; 1498 } 1499 1500 return yield; 1501 } 1502 1503 /* Read in one control code, one character byte, or the leading escapes of 1504 * an escape sequence, and return the resulting number of bytes in count. */ 1505 char *get_verbatim_kbinput(WINDOW *frame, size_t *count) 1506 { 1507 char *bytes = nmalloc(MAXCHARLEN + 2); 1508 int *input; 1509 1510 /* Turn off flow control characters if necessary so that we can type 1511 * them in verbatim, and turn the keypad off if necessary so that we 1512 * don't get extended keypad values. */ 1513 if (ISSET(PRESERVE)) 1514 disable_flow_control(); 1515 if (!ISSET(RAW_SEQUENCES)) 1516 keypad(frame, FALSE); 1517 1518 #ifndef NANO_TINY 1519 /* Turn bracketed-paste mode off. */ 1520 printf("\x1B[?2004l"); 1521 fflush(stdout); 1522 #endif 1523 1524 linger_after_escape = TRUE; 1525 1526 /* Read in a single byte or two escapes. */ 1527 input = parse_verbatim_kbinput(frame, count); 1528 1529 /* If the byte is invalid in the current mode, discard it; 1530 * if it is an incomplete Unicode sequence, stuff it back. */ 1531 if (input && *count) { 1532 if (*input >= 0x80 && *count == 1) { 1533 put_back(*input); 1534 *count = 999; 1535 } else if ((*input == '\n' && as_an_at) || (*input == '\0' && !as_an_at)) 1536 *count = 0; 1537 } 1538 1539 linger_after_escape = FALSE; 1540 1541 #ifndef NANO_TINY 1542 /* Turn bracketed-paste mode back on. */ 1543 printf("\x1B[?2004h"); 1544 fflush(stdout); 1545 #endif 1546 1547 /* Turn flow control characters back on if necessary and turn the 1548 * keypad back on if necessary now that we're done. */ 1549 if (ISSET(PRESERVE)) 1550 enable_flow_control(); 1551 1552 /* Use the global window pointers, because a resize may have freed 1553 * the data that the frame parameter points to. */ 1554 if (!ISSET(RAW_SEQUENCES)) { 1555 keypad(midwin, TRUE); 1556 keypad(footwin, TRUE); 1557 } 1558 1559 if (*count < 999) { 1560 for (size_t i = 0; i < *count; i++) 1561 bytes[i] = (char)input[i]; 1562 bytes[*count] = '\0'; 1563 } 1564 1565 free(input); 1566 1567 return bytes; 1568 } 1569 1570 #ifdef ENABLE_MOUSE 1571 /* Handle any mouse event that may have occurred. We currently handle 1572 * releases/clicks of the first mouse button. If allow_shortcuts is 1573 * TRUE, releasing/clicking on a visible shortcut will put back the 1574 * keystroke associated with that shortcut. If ncurses supports them, 1575 * we also handle presses of the fourth mouse button (upward rolls of 1576 * the mouse wheel) by putting back keystrokes to scroll up, and presses 1577 * of the fifth mouse button (downward rolls of the mouse wheel) by 1578 * putting back keystrokes to scroll down. We also store the coordinates 1579 * of a mouse event that needs further handling in mouse_x and mouse_y. 1580 * Return -1 on error, 0 if the mouse event needs to be handled, 1 if it's 1581 * been handled by putting back keystrokes, or 2 if it's been ignored. */ 1582 int get_mouseinput(int *mouse_y, int *mouse_x, bool allow_shortcuts) 1583 { 1584 bool in_middle, in_footer; 1585 MEVENT event; 1586 1587 /* First, get the actual mouse event. */ 1588 if (getmouse(&event) == ERR) 1589 return -1; 1590 1591 in_middle = wenclose(midwin, event.y, event.x); 1592 in_footer = wenclose(footwin, event.y, event.x); 1593 1594 /* Copy (and possibly adjust) the coordinates of the mouse event. */ 1595 *mouse_x = event.x - (in_middle ? margin : 0); 1596 *mouse_y = event.y; 1597 1598 /* Handle releases/clicks of the first mouse button. */ 1599 if (event.bstate & (BUTTON1_RELEASED | BUTTON1_CLICKED)) { 1600 /* If we're allowing shortcuts, and the current shortcut list is 1601 * being displayed on the last two lines of the screen, and the 1602 * first mouse button was released on/clicked inside it, we need 1603 * to figure out which shortcut was released on/clicked and put 1604 * back the equivalent keystroke(s) for it. */ 1605 if (allow_shortcuts && !ISSET(NO_HELP) && in_footer) { 1606 int width; 1607 /* The width of each shortcut item, except the last two. */ 1608 int index; 1609 /* The calculated index of the clicked item. */ 1610 size_t number; 1611 /* The number of shortcut items that get displayed. */ 1612 1613 /* Shift the coordinates to be relative to the bottom window. */ 1614 wmouse_trafo(footwin, mouse_y, mouse_x, FALSE); 1615 1616 /* Clicks on the status bar are handled elsewhere, so 1617 * restore the untranslated mouse-event coordinates. */ 1618 if (*mouse_y == 0) { 1619 *mouse_x = event.x; 1620 *mouse_y = event.y; 1621 return 0; 1622 } 1623 1624 /* Determine how many shortcuts are being shown. */ 1625 number = shown_entries_for(currmenu); 1626 1627 /* Calculate the clickable width of each menu item. */ 1628 if (number < 5) 1629 width = COLS / 2; 1630 else 1631 width = COLS / ((number + 1) / 2); 1632 1633 /* Calculate the one-based index in the shortcut list. */ 1634 index = (*mouse_x / width) * 2 + *mouse_y; 1635 1636 /* Adjust the index if we hit the last two wider ones. */ 1637 if ((index > number) && (*mouse_x % width < COLS % width)) 1638 index -= 2; 1639 1640 /* Ignore clicks beyond the last shortcut. */ 1641 if (index > number) 1642 return 2; 1643 1644 /* Search through the list of functions to determine which 1645 * shortcut in the current menu the user clicked on; then 1646 * put the corresponding keystroke into the keyboard buffer. */ 1647 for (funcstruct *f = allfuncs; f != NULL; f = f->next) { 1648 if ((f->menus & currmenu) == 0) 1649 continue; 1650 if (first_sc_for(currmenu, f->func) == NULL) 1651 continue; 1652 if (--index == 0) { 1653 const keystruct *shortcut = first_sc_for(currmenu, f->func); 1654 1655 put_back(shortcut->keycode); 1656 if (0x20 <= shortcut->keycode && shortcut->keycode <= 0x7E) 1657 put_back(ESC_CODE); 1658 break; 1659 } 1660 } 1661 1662 return 1; 1663 } else 1664 /* Clicks outside of the bottom window are handled elsewhere. */ 1665 return 0; 1666 } 1667 #if NCURSES_MOUSE_VERSION >= 2 1668 /* Handle "presses" of the fourth and fifth mouse buttons 1669 * (upward and downward rolls of the mouse wheel). */ 1670 else if (event.bstate & (BUTTON4_PRESSED | BUTTON5_PRESSED)) { 1671 if (in_footer) 1672 /* Shift the coordinates to be relative to the bottom window. */ 1673 wmouse_trafo(footwin, mouse_y, mouse_x, FALSE); 1674 1675 if (in_middle || (in_footer && *mouse_y == 0)) { 1676 int keycode = (event.bstate & BUTTON4_PRESSED) ? ALT_UP : ALT_DOWN; 1677 1678 /* One bump of the mouse wheel should scroll two lines. */ 1679 put_back(keycode); 1680 put_back(keycode); 1681 1682 return 1; 1683 } else 1684 /* Ignore "presses" of the fourth and fifth mouse buttons 1685 * that aren't on the edit window or the status bar. */ 1686 return 2; 1687 } 1688 #endif 1689 /* Ignore all other mouse events. */ 1690 return 2; 1691 } 1692 #endif /* ENABLE_MOUSE */ 1693 1694 /* Move (in the given window) to the given row and wipe it clean. */ 1695 void blank_row(WINDOW *window, int row) 1696 { 1697 wmove(window, row, 0); 1698 wclrtoeol(window); 1699 } 1700 1701 /* Blank the first line of the top portion of the screen. */ 1702 void blank_titlebar(void) 1703 { 1704 mvwprintw(topwin, 0, 0, "%*s", COLS, " "); 1705 } 1706 1707 /* Blank all lines of the middle portion of the screen (the edit window). */ 1708 void blank_edit(void) 1709 { 1710 for (int row = 0; row < editwinrows; row++) 1711 blank_row(midwin, row); 1712 } 1713 1714 /* Blank the first line of the bottom portion of the screen. */ 1715 void blank_statusbar(void) 1716 { 1717 blank_row(footwin, 0); 1718 } 1719 1720 /* Wipe the status bar clean and include this in the next screen update. */ 1721 void wipe_statusbar(void) 1722 { 1723 lastmessage = VACUUM; 1724 1725 if ((ISSET(ZERO) || ISSET(MINIBAR) || LINES == 1) && currmenu == MMAIN) 1726 return; 1727 1728 blank_row(footwin, 0); 1729 wnoutrefresh(footwin); 1730 } 1731 1732 /* Blank out the two help lines (when they are present). */ 1733 void blank_bottombars(void) 1734 { 1735 if (!ISSET(NO_HELP) && LINES > 5) { 1736 blank_row(footwin, 1); 1737 blank_row(footwin, 2); 1738 } 1739 } 1740 1741 /* When some number of keystrokes has been reached, wipe the status bar. */ 1742 void blank_it_when_expired(void) 1743 { 1744 if (countdown == 0) 1745 return; 1746 1747 if (--countdown == 0) 1748 wipe_statusbar(); 1749 1750 /* When windows overlap, make sure to show the edit window now. */ 1751 if (currmenu == MMAIN && (ISSET(ZERO) || LINES == 1)) { 1752 wredrawln(midwin, editwinrows - 1, 1); 1753 wnoutrefresh(midwin); 1754 } 1755 } 1756 1757 /* Ensure that the status bar will be wiped upon the next keystroke. */ 1758 void set_blankdelay_to_one(void) 1759 { 1760 countdown = 1; 1761 } 1762 1763 /* Convert text into a string that can be displayed on screen. The caller 1764 * wants to display text starting with the given column, and extending for 1765 * at most span columns. column is zero-based, and span is one-based, so 1766 * span == 0 means you get "" returned. The returned string is dynamically 1767 * allocated, and should be freed. If isdata is TRUE, the caller might put 1768 * "<" at the beginning or ">" at the end of the line if it's too long. If 1769 * isprompt is TRUE, the caller might put ">" at the end of the line if it's 1770 * too long. */ 1771 char *display_string(const char *text, size_t column, size_t span, 1772 bool isdata, bool isprompt) 1773 { 1774 const char *origin = text; 1775 /* The beginning of the text, to later determine the covered part. */ 1776 size_t start_x = actual_x(text, column); 1777 /* The index of the first character that the caller wishes to show. */ 1778 size_t start_col = wideness(text, start_x); 1779 /* The actual column where that first character starts. */ 1780 size_t stowaways = 20; 1781 /* The number of zero-width characters for which to reserve space. */ 1782 size_t allocsize = (COLS + stowaways) * MAXCHARLEN + 1; 1783 /* The amount of memory to reserve for the displayable string. */ 1784 char *converted = nmalloc(allocsize); 1785 /* The displayable string we will return. */ 1786 size_t index = 0; 1787 /* Current position in converted. */ 1788 size_t beyond = column + span; 1789 /* The column number just beyond the last shown character. */ 1790 1791 text += start_x; 1792 1793 #ifndef NANO_TINY 1794 if (span > HIGHEST_POSITIVE) { 1795 statusline(ALERT, "Span has underflowed -- please report a bug"); 1796 converted[0] = '\0'; 1797 return converted; 1798 } 1799 #endif 1800 /* If the first character starts before the left edge, or would be 1801 * overwritten by a "<" token, then show placeholders instead. */ 1802 if ((start_col < column || (start_col > 0 && isdata && !ISSET(SOFTWRAP))) && 1803 *text != '\0' && *text != '\t') { 1804 if (is_cntrl_char(text)) { 1805 if (start_col < column) { 1806 converted[index++] = control_mbrep(text, isdata); 1807 column++; 1808 text += char_length(text); 1809 } 1810 } 1811 #ifdef ENABLE_UTF8 1812 else if (is_doublewidth(text)) { 1813 if (start_col == column) { 1814 converted[index++] = ' '; 1815 column++; 1816 } 1817 1818 /* Display the right half of a two-column character as ']'. */ 1819 converted[index++] = ']'; 1820 column++; 1821 text += char_length(text); 1822 } 1823 #endif 1824 } 1825 1826 #ifdef ENABLE_UTF8 1827 #define ISO8859_CHAR FALSE 1828 #define ZEROWIDTH_CHAR (is_zerowidth(text)) 1829 #else 1830 #define ISO8859_CHAR ((unsigned char)*text > 0x9F) 1831 #define ZEROWIDTH_CHAR FALSE 1832 #endif 1833 1834 while (*text != '\0' && (column < beyond || ZEROWIDTH_CHAR)) { 1835 /* A plain printable ASCII character is one byte, one column. */ 1836 if (((signed char)*text > 0x20 && *text != DEL_CODE) || ISO8859_CHAR) { 1837 converted[index++] = *(text++); 1838 column++; 1839 continue; 1840 } 1841 1842 /* Show a space as a visible character, or as a space. */ 1843 if (*text == ' ') { 1844 #ifndef NANO_TINY 1845 if (ISSET(WHITESPACE_DISPLAY)) { 1846 for (int i = whitelen[0]; i < whitelen[0] + whitelen[1];) 1847 converted[index++] = whitespace[i++]; 1848 } else 1849 #endif 1850 converted[index++] = ' '; 1851 column++; 1852 text++; 1853 continue; 1854 } 1855 1856 /* Show a tab as a visible character plus spaces, or as just spaces. */ 1857 if (*text == '\t') { 1858 #ifndef NANO_TINY 1859 if (ISSET(WHITESPACE_DISPLAY) && (index > 0 || !isdata || 1860 !ISSET(SOFTWRAP) || column % tabsize == 0 || 1861 column == start_col)) { 1862 for (int i = 0; i < whitelen[0];) 1863 converted[index++] = whitespace[i++]; 1864 } else 1865 #endif 1866 converted[index++] = ' '; 1867 column++; 1868 /* Fill the tab up with the required number of spaces. */ 1869 while (column % tabsize != 0 && column < beyond) { 1870 converted[index++] = ' '; 1871 column++; 1872 } 1873 text++; 1874 continue; 1875 } 1876 1877 /* Represent a control character with a leading caret. */ 1878 if (is_cntrl_char(text)) { 1879 converted[index++] = '^'; 1880 converted[index++] = control_mbrep(text, isdata); 1881 text += char_length(text); 1882 column += 2; 1883 continue; 1884 } 1885 1886 #ifdef ENABLE_UTF8 1887 int charlength, charwidth; 1888 wchar_t wc; 1889 1890 /* Convert a multibyte character to a single code. */ 1891 charlength = mbtowide(&wc, text); 1892 1893 /* Represent an invalid character with the Replacement Character. */ 1894 if (charlength < 0) { 1895 converted[index++] = '\xEF'; 1896 converted[index++] = '\xBF'; 1897 converted[index++] = '\xBD'; 1898 text++; 1899 column++; 1900 continue; 1901 } 1902 1903 /* Determine whether the character takes zero, one, or two columns. */ 1904 charwidth = wcwidth(wc); 1905 1906 /* Watch the number of zero-widths, to keep ample memory reserved. */ 1907 if (charwidth == 0 && --stowaways == 0) { 1908 stowaways = 40; 1909 allocsize += stowaways * MAXCHARLEN; 1910 converted = nrealloc(converted, allocsize); 1911 } 1912 1913 #ifdef __linux__ 1914 /* On a Linux console, skip zero-width characters, as it would show 1915 * them WITH a width, thus messing up the display. See bug #52954. */ 1916 if (on_a_vt && charwidth == 0) { 1917 text += charlength; 1918 continue; 1919 } 1920 #endif 1921 /* For any valid character, just copy its bytes. */ 1922 for (; charlength > 0; charlength--) 1923 converted[index++] = *(text++); 1924 1925 /* If the codepoint is unassigned, assume a width of one. */ 1926 column += (charwidth < 0 ? 1 : charwidth); 1927 #endif /* ENABLE_UTF8 */ 1928 } 1929 1930 /* If there is more text than can be shown, make room for the ">". */ 1931 if (column > beyond || (*text != '\0' && (isprompt || 1932 (isdata && !ISSET(SOFTWRAP))))) { 1933 #ifdef ENABLE_UTF8 1934 do { 1935 index = step_left(converted, index); 1936 } while (is_zerowidth(converted + index)); 1937 1938 /* Display the left half of a two-column character as '['. */ 1939 if (is_doublewidth(converted + index)) 1940 converted[index++] = '['; 1941 #else 1942 index--; 1943 #endif 1944 has_more = TRUE; 1945 } else 1946 has_more = FALSE; 1947 1948 is_shorter = (column < beyond); 1949 1950 /* Null-terminate the converted string. */ 1951 converted[index] = '\0'; 1952 1953 /* Remember what part of the original text is covered by converted. */ 1954 from_x = start_x; 1955 till_x = text - origin; 1956 1957 return converted; 1958 } 1959 1960 #ifdef ENABLE_MULTIBUFFER 1961 /* Determine the sequence number of the given buffer in the circular list. */ 1962 int buffer_number(openfilestruct *buffer) 1963 { 1964 int count = 1; 1965 1966 while (buffer != startfile) { 1967 buffer = buffer->prev; 1968 count++; 1969 } 1970 1971 return count; 1972 } 1973 #endif 1974 1975 #ifndef NANO_TINY 1976 /* Show the state of auto-indenting, the mark, hard-wrapping, macro recording, 1977 * and soft-wrapping by showing corresponding letters in the given window. */ 1978 void show_states_at(WINDOW *window) 1979 { 1980 waddstr(window, ISSET(AUTOINDENT) ? "I" : " "); 1981 waddstr(window, openfile->mark ? "M" : " "); 1982 waddstr(window, ISSET(BREAK_LONG_LINES) ? "L" : " "); 1983 waddstr(window, recording ? "R" : " "); 1984 waddstr(window, ISSET(SOFTWRAP) ? "S" : " "); 1985 } 1986 #endif 1987 1988 /* If path is NULL, we're in normal editing mode, so display the current 1989 * version of nano, the current filename, and whether the current file 1990 * has been modified on the title bar. If path isn't NULL, we're either 1991 * in the file browser or the help viewer, so show either the current 1992 * directory or the title of help text, that is: whatever is in path. */ 1993 void titlebar(const char *path) 1994 { 1995 size_t verlen, prefixlen, pathlen, statelen; 1996 /* The width of the different title-bar elements, in columns. */ 1997 size_t pluglen = 0; 1998 /* The width that "Modified" would take up. */ 1999 size_t offset = 0; 2000 /* The position at which the center part of the title bar starts. */ 2001 const char *upperleft = ""; 2002 /* What is shown in the top left corner. */ 2003 const char *prefix = ""; 2004 /* What is shown before the path -- "DIR:" or nothing. */ 2005 const char *state = ""; 2006 /* The state of the current buffer -- "Modified", "View", or "". */ 2007 char *caption; 2008 /* The presentable form of the pathname. */ 2009 char *ranking = NULL; 2010 /* The buffer sequence number plus the total buffer count. */ 2011 2012 /* If the screen is too small, there is no title bar. */ 2013 if (topwin == NULL) 2014 return; 2015 2016 wattron(topwin, interface_color_pair[TITLE_BAR]); 2017 2018 blank_titlebar(); 2019 as_an_at = FALSE; 2020 2021 /* Do as Pico: if there is not enough width available for all items, 2022 * first sacrifice the version string, then eat up the side spaces, 2023 * then sacrifice the prefix, and only then start dottifying. */ 2024 2025 /* Figure out the path, prefix and state strings. */ 2026 #ifdef ENABLE_COLOR 2027 if (currmenu == MLINTER) { 2028 /* TRANSLATORS: The next five are "labels" in the title bar. */ 2029 prefix = _("Linting --"); 2030 path = openfile->filename; 2031 } else 2032 #endif 2033 #ifdef ENABLE_BROWSER 2034 if (!inhelp && path != NULL) 2035 prefix = _("DIR:"); 2036 else 2037 #endif 2038 if (!inhelp) { 2039 #ifdef ENABLE_MULTIBUFFER 2040 /* If there are/were multiple buffers, show which out of how many. */ 2041 if (more_than_one) { 2042 ranking = nmalloc(24); 2043 sprintf(ranking, "[%i/%i]", buffer_number(openfile), 2044 buffer_number(startfile->prev)); 2045 upperleft = ranking; 2046 } else 2047 #endif 2048 upperleft = BRANDING; 2049 2050 if (openfile->filename[0] == '\0') 2051 path = _("New Buffer"); 2052 else 2053 path = openfile->filename; 2054 2055 if (ISSET(VIEW_MODE)) 2056 state = _("View"); 2057 #ifndef NANO_TINY 2058 else if (ISSET(STATEFLAGS)) 2059 state = "+.xxxxx"; 2060 #endif 2061 else if (openfile->modified) 2062 state = _("Modified"); 2063 else if (ISSET(RESTRICTED)) 2064 state = _("Restricted"); 2065 else 2066 pluglen = breadth(_("Modified")) + 1; 2067 } 2068 2069 /* Determine the widths of the four elements, including their padding. */ 2070 verlen = breadth(upperleft) + 3; 2071 prefixlen = breadth(prefix); 2072 if (prefixlen > 0) 2073 prefixlen++; 2074 pathlen = breadth(path); 2075 statelen = breadth(state) + 2; 2076 if (statelen > 2) 2077 pathlen++; 2078 2079 /* Only print the version message when there is room for it. */ 2080 if (verlen + prefixlen + pathlen + pluglen + statelen <= COLS) 2081 mvwaddstr(topwin, 0, 2, upperleft); 2082 else { 2083 verlen = 2; 2084 /* If things don't fit yet, give up the placeholder. */ 2085 if (verlen + prefixlen + pathlen + pluglen + statelen > COLS) 2086 pluglen = 0; 2087 /* If things still don't fit, give up the side spaces. */ 2088 if (verlen + prefixlen + pathlen + pluglen + statelen > COLS) { 2089 verlen = 0; 2090 statelen -= 2; 2091 } 2092 } 2093 2094 free(ranking); 2095 2096 /* If we have side spaces left, center the path name. */ 2097 if (verlen > 0) 2098 offset = verlen + (COLS - (verlen + pluglen + statelen) - 2099 (prefixlen + pathlen)) / 2; 2100 2101 /* Only print the prefix when there is room for it. */ 2102 if (verlen + prefixlen + pathlen + pluglen + statelen <= COLS) { 2103 mvwaddstr(topwin, 0, offset, prefix); 2104 if (prefixlen > 0) 2105 waddstr(topwin, " "); 2106 } else 2107 wmove(topwin, 0, offset); 2108 2109 /* Print the full path if there's room; otherwise, dottify it. */ 2110 if (pathlen + pluglen + statelen <= COLS) { 2111 caption = display_string(path, 0, pathlen, FALSE, FALSE); 2112 waddstr(topwin, caption); 2113 free(caption); 2114 } else if (5 + statelen <= COLS) { 2115 waddstr(topwin, "..."); 2116 caption = display_string(path, 3 + pathlen - COLS + statelen, 2117 COLS - statelen, FALSE, FALSE); 2118 waddstr(topwin, caption); 2119 free(caption); 2120 } 2121 2122 #ifndef NANO_TINY 2123 /* When requested, show on the title bar the state of three options and 2124 * the state of the mark and whether a macro is being recorded. */ 2125 if (*state && ISSET(STATEFLAGS) && !ISSET(VIEW_MODE)) { 2126 if (openfile->modified && COLS > 1) 2127 waddstr(topwin, " *"); 2128 if (statelen < COLS) { 2129 wmove(topwin, 0, COLS + 2 - statelen); 2130 show_states_at(topwin); 2131 } 2132 } else 2133 #endif 2134 { 2135 /* If there's room, right-align the state word; otherwise, clip it. */ 2136 if (statelen > 0 && statelen <= COLS) 2137 mvwaddstr(topwin, 0, COLS - statelen, state); 2138 else if (statelen > 0) 2139 mvwaddnstr(topwin, 0, 0, state, actual_x(state, COLS)); 2140 } 2141 2142 wattroff(topwin, interface_color_pair[TITLE_BAR]); 2143 2144 wrefresh(topwin); 2145 } 2146 2147 #ifndef NANO_TINY 2148 /* Draw a bar at the bottom with some minimal state information. */ 2149 void minibar(void) 2150 { 2151 char *thename = NULL, *number_of_lines = NULL, *ranking = NULL; 2152 char *location = nmalloc(44); 2153 char *hexadecimal = nmalloc(9); 2154 char *successor = NULL; 2155 size_t namewidth, placewidth; 2156 size_t tallywidth = 0; 2157 size_t padding = 2; 2158 #ifdef ENABLE_UTF8 2159 wchar_t widecode; 2160 #endif 2161 2162 /* Draw a colored bar over the full width of the screen. */ 2163 wattron(footwin, interface_color_pair[MINI_INFOBAR]); 2164 mvwprintw(footwin, 0, 0, "%*s", COLS, " "); 2165 2166 if (openfile->filename[0] != '\0') { 2167 as_an_at = FALSE; 2168 thename = display_string(openfile->filename, 0, COLS, FALSE, FALSE); 2169 } else 2170 thename = copy_of(_("(nameless)")); 2171 2172 sprintf(location, "%zi,%zi", openfile->current->lineno, xplustabs() + 1); 2173 placewidth = strlen(location); 2174 namewidth = breadth(thename); 2175 2176 /* If the file name is relatively long, drop the side spaces. */ 2177 if (namewidth + 19 > COLS) 2178 padding = 0; 2179 2180 /* Display the name of the current file (dottifying it if it doesn't fit), 2181 * plus a star when the file has been modified. */ 2182 if (COLS > 4) { 2183 if (namewidth > COLS - 2) { 2184 char *shortname = display_string(thename, namewidth - COLS + 5, 2185 COLS - 5, FALSE, FALSE); 2186 mvwaddstr(footwin, 0, 0, "..."); 2187 waddstr(footwin, shortname); 2188 free(shortname); 2189 } else 2190 mvwaddstr(footwin, 0, padding, thename); 2191 2192 waddstr(footwin, openfile->modified ? " *" : " "); 2193 } 2194 2195 /* Right after reading or writing a file, display its number of lines; 2196 * otherwise, when there are multiple buffers, display an [x/n] counter. */ 2197 if (report_size && COLS > 35) { 2198 size_t count = openfile->filebot->lineno - (openfile->filebot->data[0] == '\0'); 2199 2200 number_of_lines = nmalloc(49); 2201 if (openfile->fmt == NIX_FILE || openfile->fmt == UNSPECIFIED) 2202 sprintf(number_of_lines, P_(" (%zu line)", " (%zu lines)", count), count); 2203 else 2204 sprintf(number_of_lines, P_(" (%zu line, %s)", " (%zu lines, %s)", count), 2205 count, (openfile->fmt == DOS_FILE) ? "DOS" : "Mac"); 2206 tallywidth = breadth(number_of_lines); 2207 if (namewidth + tallywidth + 11 < COLS) 2208 waddstr(footwin, number_of_lines); 2209 else 2210 tallywidth = 0; 2211 report_size = FALSE; 2212 } 2213 #ifdef ENABLE_MULTIBUFFER 2214 else if (openfile->next != openfile && COLS > 35) { 2215 ranking = nmalloc(24); 2216 sprintf(ranking, " [%i/%i]", buffer_number(openfile), buffer_number(startfile->prev)); 2217 if (namewidth + placewidth + breadth(ranking) + 32 < COLS) 2218 waddstr(footwin, ranking); 2219 } 2220 #endif 2221 2222 /* Display the line/column position of the cursor. */ 2223 if (ISSET(CONSTANT_SHOW) && namewidth + tallywidth + placewidth + 32 < COLS) 2224 mvwaddstr(footwin, 0, COLS - 27 - placewidth, location); 2225 2226 /* Display the hexadecimal code of the character under the cursor, 2227 * plus the codes of up to two succeeding zero-width characters. */ 2228 if (ISSET(CONSTANT_SHOW) && namewidth + tallywidth + 28 < COLS) { 2229 char *this_position = openfile->current->data + openfile->current_x; 2230 2231 if (*this_position == '\0') 2232 sprintf(hexadecimal, openfile->current->next ? 2233 (using_utf8 ? "U+000A" : " 0x0A") : " ----"); 2234 else if (*this_position == '\n') 2235 sprintf(hexadecimal, " 0x00"); 2236 #ifdef ENABLE_UTF8 2237 else if ((unsigned char)*this_position < 0x80 && using_utf8) 2238 sprintf(hexadecimal, "U+%04X", (unsigned char)*this_position); 2239 else if (using_utf8 && mbtowide(&widecode, this_position) > 0) 2240 sprintf(hexadecimal, "U+%04X", (int)widecode); 2241 #endif 2242 else 2243 sprintf(hexadecimal, " 0x%02X", (unsigned char)*this_position); 2244 2245 mvwaddstr(footwin, 0, COLS - 23, hexadecimal); 2246 2247 #ifdef ENABLE_UTF8 2248 successor = this_position + char_length(this_position); 2249 2250 if (*this_position && *successor && is_zerowidth(successor) && 2251 mbtowide(&widecode, successor) > 0) { 2252 sprintf(hexadecimal, "|%04X", (int)widecode); 2253 waddstr(footwin, hexadecimal); 2254 2255 successor += char_length(successor); 2256 2257 if (is_zerowidth(successor) && mbtowide(&widecode, successor) > 0) { 2258 sprintf(hexadecimal, "|%04X", (int)widecode); 2259 waddstr(footwin, hexadecimal); 2260 } 2261 } else 2262 successor = NULL; 2263 #endif 2264 } 2265 2266 /* Display the state of three flags, and the state of macro and mark. */ 2267 if (ISSET(STATEFLAGS) && !successor && namewidth + tallywidth + 14 + 2 * padding < COLS) { 2268 wmove(footwin, 0, COLS - 11 - padding); 2269 show_states_at(footwin); 2270 } 2271 2272 /* Indicate it when the line has an anchor. */ 2273 if (openfile->current->has_anchor && namewidth + 7 < COLS) 2274 mvwaddstr(footwin, 0, COLS - 5 - padding, using_utf8 ? "\xE2\x80\xA0" : "+"); 2275 2276 /* Display how many percent the current line is into the file. */ 2277 if (namewidth + 6 < COLS) { 2278 sprintf(location, "%3zi%%", 100 * openfile->current->lineno / openfile->filebot->lineno); 2279 mvwaddstr(footwin, 0, COLS - 4 - padding, location); 2280 } 2281 2282 wattroff(footwin, interface_color_pair[MINI_INFOBAR]); 2283 wrefresh(footwin); 2284 2285 free(number_of_lines); 2286 free(hexadecimal); 2287 free(location); 2288 free(thename); 2289 free(ranking); 2290 } 2291 #endif /* NANO_TINY */ 2292 2293 /* Display the given message on the status bar, but only if its importance 2294 * is higher than that of a message that is already there. */ 2295 void statusline(message_type importance, const char *msg, ...) 2296 { 2297 bool showed_whitespace = ISSET(WHITESPACE_DISPLAY); 2298 static size_t start_col = 0; 2299 char *compound, *message; 2300 bool bracketed; 2301 int colorpair; 2302 va_list ap; 2303 2304 /* Drop all waiting keystrokes upon any kind of "error". */ 2305 if (importance >= AHEM) 2306 waiting_codes = 0; 2307 2308 /* Ignore a message with an importance that is lower than the last one. */ 2309 if (importance < lastmessage && lastmessage > NOTICE) 2310 return; 2311 2312 /* Construct the message out of all the arguments. */ 2313 compound = nmalloc(MAXCHARLEN * COLS + 1); 2314 va_start(ap, msg); 2315 vsnprintf(compound, MAXCHARLEN * COLS + 1, msg, ap); 2316 va_end(ap); 2317 2318 /* When not in curses mode, write the message to standard error. */ 2319 if (isendwin()) { 2320 fprintf(stderr, "\n%s\n", compound); 2321 free(compound); 2322 return; 2323 } 2324 2325 #if defined(ENABLE_MULTIBUFFER) && !defined(NANO_TINY) 2326 if (!we_are_running && importance == ALERT && openfile && !openfile->fmt && 2327 !openfile->errormessage && openfile->next != openfile) 2328 openfile->errormessage = copy_of(compound); 2329 #endif 2330 2331 /* On a one-row terminal, ensure that any changes in the edit window are 2332 * written out first, to prevent them from overwriting the message. */ 2333 if (LINES == 1 && importance < INFO) 2334 wnoutrefresh(midwin); 2335 2336 /* If there are multiple alert messages, add trailing dots to the first. */ 2337 if (lastmessage == ALERT) { 2338 if (start_col > 4) { 2339 wmove(footwin, 0, COLS + 2 - start_col); 2340 wattron(footwin, interface_color_pair[ERROR_MESSAGE]); 2341 waddstr(footwin, "..."); 2342 wattroff(footwin, interface_color_pair[ERROR_MESSAGE]); 2343 wnoutrefresh(footwin); 2344 start_col = 0; 2345 napms(100); 2346 beep(); 2347 } 2348 free(compound); 2349 return; 2350 } 2351 2352 if (importance > NOTICE) { 2353 if (importance == ALERT) 2354 beep(); 2355 colorpair = interface_color_pair[ERROR_MESSAGE]; 2356 } else if (importance == NOTICE) 2357 colorpair = interface_color_pair[SELECTED_TEXT]; 2358 else 2359 colorpair = interface_color_pair[STATUS_BAR]; 2360 2361 lastmessage = importance; 2362 2363 blank_statusbar(); 2364 2365 UNSET(WHITESPACE_DISPLAY); 2366 2367 message = display_string(compound, 0, COLS, FALSE, FALSE); 2368 2369 if (showed_whitespace) 2370 SET(WHITESPACE_DISPLAY); 2371 2372 start_col = (COLS - breadth(message)) / 2; 2373 bracketed = (start_col > 1); 2374 2375 wmove(footwin, 0, (bracketed ? start_col - 2 : start_col)); 2376 wattron(footwin, colorpair); 2377 if (bracketed) 2378 waddstr(footwin, "[ "); 2379 waddstr(footwin, message); 2380 if (bracketed) 2381 waddstr(footwin, " ]"); 2382 wattroff(footwin, colorpair); 2383 2384 #ifdef USING_OLDER_LIBVTE 2385 /* Defeat a VTE/Konsole bug, where the cursor can go off-limits. */ 2386 if (ISSET(CONSTANT_SHOW) && ISSET(NO_HELP)) 2387 wmove(footwin, 0, 0); 2388 #endif 2389 2390 /* Push the message to the screen straightaway. */ 2391 wrefresh(footwin); 2392 2393 free(compound); 2394 free(message); 2395 2396 /* When requested, wipe the status bar after just one keystroke. */ 2397 countdown = (ISSET(QUICK_BLANK) ? 1 : 20); 2398 } 2399 2400 /* Display a normal message on the status bar, quietly. */ 2401 void statusbar(const char *msg) 2402 { 2403 statusline(HUSH, msg); 2404 } 2405 2406 /* Warn the user on the status bar and pause for a moment, so that the 2407 * message can be noticed and read. */ 2408 void warn_and_briefly_pause(const char *msg) 2409 { 2410 blank_bottombars(); 2411 statusline(ALERT, msg); 2412 lastmessage = VACUUM; 2413 napms(1500); 2414 } 2415 2416 /* Write a key's representation plus a minute description of its function 2417 * to the screen. For example, the key could be "^C" and its tag "Cancel". 2418 * Key plus tag may occupy at most width columns. */ 2419 void post_one_key(const char *keystroke, const char *tag, int width) 2420 { 2421 wattron(footwin, interface_color_pair[KEY_COMBO]); 2422 waddnstr(footwin, keystroke, actual_x(keystroke, width)); 2423 wattroff(footwin, interface_color_pair[KEY_COMBO]); 2424 2425 /* If the remaining space is too small, skip the description. */ 2426 width -= breadth(keystroke); 2427 if (width < 2) 2428 return; 2429 2430 waddch(footwin, ' '); 2431 wattron(footwin, interface_color_pair[FUNCTION_TAG]); 2432 waddnstr(footwin, tag, actual_x(tag, width - 1)); 2433 wattroff(footwin, interface_color_pair[FUNCTION_TAG]); 2434 } 2435 2436 /* Display the shortcut list corresponding to menu on the last two rows 2437 * of the bottom portion of the window. */ 2438 void bottombars(int menu) 2439 { 2440 size_t index, number, itemwidth; 2441 const keystruct *s; 2442 funcstruct *f; 2443 2444 /* Set the global variable to the given menu. */ 2445 currmenu = menu; 2446 2447 if (ISSET(NO_HELP) || LINES < (ISSET(ZERO) ? 3 : ISSET(MINIBAR) ? 4 : 5)) 2448 return; 2449 2450 /* Determine how many shortcuts must be shown. */ 2451 number = shown_entries_for(menu); 2452 2453 /* Compute the width of each keyname-plus-explanation pair. */ 2454 itemwidth = COLS / ((number + 1) / 2); 2455 2456 /* If there is no room, don't print anything. */ 2457 if (itemwidth == 0) 2458 return; 2459 2460 blank_bottombars(); 2461 2462 /* Display the first number of shortcuts in the given menu that 2463 * have a key combination assigned to them. */ 2464 for (f = allfuncs, index = 0; f != NULL && index < number; f = f->next) { 2465 size_t thiswidth = itemwidth; 2466 2467 if ((f->menus & menu) == 0) 2468 continue; 2469 2470 s = first_sc_for(menu, f->func); 2471 2472 if (s == NULL) 2473 continue; 2474 2475 wmove(footwin, 1 + index % 2, (index / 2) * itemwidth); 2476 2477 /* When the number is uneven, the penultimate item can be double wide. */ 2478 if ((number % 2) == 1 && (index + 2 == number)) 2479 thiswidth += itemwidth; 2480 2481 /* For the last two items, use also the remaining slack. */ 2482 if (index + 2 >= number) 2483 thiswidth += COLS % itemwidth; 2484 2485 post_one_key(s->keystr, _(f->tag), thiswidth); 2486 2487 index++; 2488 } 2489 2490 wrefresh(footwin); 2491 } 2492 2493 /* Redetermine `cursor_row` from the position of current relative to edittop, 2494 * and put the cursor in the edit window at (cursor_row, "current_x"). */ 2495 void place_the_cursor(void) 2496 { 2497 ssize_t row = 0; 2498 size_t column = xplustabs(); 2499 2500 #ifndef NANO_TINY 2501 if (ISSET(SOFTWRAP)) { 2502 linestruct *line = openfile->edittop; 2503 size_t leftedge; 2504 2505 row -= chunk_for(openfile->firstcolumn, openfile->edittop); 2506 2507 /* Calculate how many rows the lines from edittop to current use. */ 2508 while (line != NULL && line != openfile->current) { 2509 row += 1 + extra_chunks_in(line); 2510 line = line->next; 2511 } 2512 2513 /* Add the number of wraps in the current line before the cursor. */ 2514 row += get_chunk_and_edge(column, openfile->current, &leftedge); 2515 column -= leftedge; 2516 } else 2517 #endif 2518 { 2519 row = openfile->current->lineno - openfile->edittop->lineno; 2520 column -= get_page_start(column); 2521 } 2522 2523 if (row < editwinrows) 2524 wmove(midwin, row, margin + column); 2525 #ifndef NANO_TINY 2526 else 2527 statusline(ALERT, "Misplaced cursor -- please report a bug"); 2528 #endif 2529 2530 #ifdef _CURSES_H_ 2531 wnoutrefresh(midwin); /* Only needed for NetBSD curses. */ 2532 #endif 2533 2534 openfile->cursor_row = row; 2535 } 2536 2537 /* The number of bytes after which to stop painting, to avoid major slowdowns. */ 2538 #define PAINT_LIMIT 2000 2539 2540 /* Draw the given text on the given row of the edit window. line is the 2541 * line to be drawn, and converted is the actual string to be written with 2542 * tabs and control characters replaced by strings of regular characters. 2543 * from_col is the column number of the first character of this "page". */ 2544 void draw_row(int row, const char *converted, linestruct *line, size_t from_col) 2545 { 2546 #ifdef ENABLE_LINENUMBERS 2547 /* If line numbering is switched on, put a line number in front of 2548 * the text -- but only for the parts that are not softwrapped. */ 2549 if (margin > 0) { 2550 wattron(midwin, interface_color_pair[LINE_NUMBER]); 2551 #ifndef NANO_TINY 2552 if (ISSET(SOFTWRAP) && from_col != 0) 2553 mvwprintw(midwin, row, 0, "%*s", margin - 1, " "); 2554 else 2555 #endif 2556 mvwprintw(midwin, row, 0, "%*zd", margin - 1, line->lineno); 2557 wattroff(midwin, interface_color_pair[LINE_NUMBER]); 2558 #ifndef NANO_TINY 2559 if (line->has_anchor && (from_col == 0 || !ISSET(SOFTWRAP))) 2560 wprintw(midwin, using_utf8 ? "\xE2\x80\xA0" : "+"); 2561 else 2562 #endif 2563 wprintw(midwin, " "); 2564 } 2565 #endif /* ENABLE_LINENUMBERS */ 2566 2567 /* First simply write the converted line -- afterward we'll add colors 2568 * and the marking highlight on just the pieces that need it. */ 2569 mvwaddstr(midwin, row, margin, converted); 2570 2571 /* When needed, clear the remainder of the row. */ 2572 if (is_shorter || ISSET(SOFTWRAP)) 2573 wclrtoeol(midwin); 2574 2575 #ifndef NANO_TINY 2576 if (sidebar) 2577 mvwaddch(midwin, row, COLS - 1, bardata[row]); 2578 #endif 2579 2580 #ifdef ENABLE_COLOR 2581 /* If there are color rules (and coloring is turned on), apply them. */ 2582 if (openfile->syntax && !ISSET(NO_SYNTAX)) { 2583 const colortype *varnish = openfile->syntax->color; 2584 2585 /* If there are multiline regexes, make sure this line has a cache. */ 2586 if (openfile->syntax->multiscore > 0 && line->multidata == NULL) 2587 line->multidata = nmalloc(openfile->syntax->multiscore * sizeof(short)); 2588 2589 /* Iterate through all the coloring regexes. */ 2590 for (; varnish != NULL; varnish = varnish->next) { 2591 size_t index = 0; 2592 /* Where in the line we currently begin looking for a match. */ 2593 int start_col = 0; 2594 /* The starting column of a piece to paint. Zero-based. */ 2595 int paintlen = 0; 2596 /* The number of characters to paint. */ 2597 const char *thetext; 2598 /* The place in converted from where painting starts. */ 2599 regmatch_t match; 2600 /* The match positions of a single-line regex. */ 2601 const linestruct *start_line = line->prev; 2602 /* The first line before line that matches 'start'. */ 2603 regmatch_t startmatch, endmatch; 2604 /* The match positions of the start and end regexes. */ 2605 2606 /* First case: varnish is a single-line expression. */ 2607 if (varnish->end == NULL) { 2608 while (index < PAINT_LIMIT && index < till_x) { 2609 /* If there is no match, go on to the next line. */ 2610 if (regexec(varnish->start, &line->data[index], 1, 2611 &match, (index == 0) ? 0 : REG_NOTBOL) != 0) 2612 break; 2613 2614 /* Translate the match to the beginning of the line. */ 2615 match.rm_so += index; 2616 match.rm_eo += index; 2617 index = match.rm_eo; 2618 2619 /* If the match is offscreen to the right, this rule is done. */ 2620 if (match.rm_so >= till_x) 2621 break; 2622 2623 /* If the match has length zero, advance over it. */ 2624 if (match.rm_so == match.rm_eo) { 2625 if (line->data[index] == '\0') 2626 break; 2627 index = step_right(line->data, index); 2628 continue; 2629 } 2630 2631 /* If the match is offscreen to the left, skip to next. */ 2632 if (match.rm_eo <= from_x) 2633 continue; 2634 2635 if (match.rm_so > from_x) 2636 start_col = wideness(line->data, match.rm_so) - from_col; 2637 2638 thetext = converted + actual_x(converted, start_col); 2639 2640 paintlen = actual_x(thetext, wideness(line->data, 2641 match.rm_eo) - from_col - start_col); 2642 2643 wattron(midwin, varnish->attributes); 2644 mvwaddnstr(midwin, row, margin + start_col, thetext, paintlen); 2645 wattroff(midwin, varnish->attributes); 2646 } 2647 2648 continue; 2649 } 2650 2651 /* Second case: varnish is a multiline expression. */ 2652 2653 /* Assume nothing gets painted until proven otherwise below. */ 2654 line->multidata[varnish->id] = NOTHING; 2655 2656 if (start_line && !start_line->multidata) 2657 statusline(ALERT, "Missing multidata -- please report a bug"); 2658 else 2659 2660 /* If there is an unterminated start match before the current line, 2661 * we need to look for an end match first. */ 2662 if (start_line && (start_line->multidata[varnish->id] == WHOLELINE || 2663 start_line->multidata[varnish->id] == STARTSHERE)) { 2664 /* If there is no end on this line, paint whole line, and be done. */ 2665 if (regexec(varnish->end, line->data, 1, &endmatch, 0) == REG_NOMATCH) { 2666 wattron(midwin, varnish->attributes); 2667 mvwaddnstr(midwin, row, margin, converted, -1); 2668 wattroff(midwin, varnish->attributes); 2669 line->multidata[varnish->id] = WHOLELINE; 2670 continue; 2671 } 2672 2673 /* Only if it is visible, paint the part to be coloured. */ 2674 if (endmatch.rm_eo > from_x) { 2675 paintlen = actual_x(converted, wideness(line->data, 2676 endmatch.rm_eo) - from_col); 2677 wattron(midwin, varnish->attributes); 2678 mvwaddnstr(midwin, row, margin, converted, paintlen); 2679 wattroff(midwin, varnish->attributes); 2680 } 2681 2682 line->multidata[varnish->id] = ENDSHERE; 2683 } 2684 2685 /* Second step: look for starts on this line, but begin 2686 * looking only after an end match, if there is one. */ 2687 index = (paintlen == 0) ? 0 : endmatch.rm_eo; 2688 2689 while (index < PAINT_LIMIT && regexec(varnish->start, line->data + index, 2690 1, &startmatch, (index == 0) ? 0 : REG_NOTBOL) == 0) { 2691 /* Make the match relative to the beginning of the line. */ 2692 startmatch.rm_so += index; 2693 startmatch.rm_eo += index; 2694 2695 if (startmatch.rm_so > from_x) 2696 start_col = wideness(line->data, startmatch.rm_so) - from_col; 2697 2698 thetext = converted + actual_x(converted, start_col); 2699 2700 if (regexec(varnish->end, line->data + startmatch.rm_eo, 1, &endmatch, 2701 (startmatch.rm_eo == 0) ? 0 : REG_NOTBOL) == 0) { 2702 /* Make the match relative to the beginning of the line. */ 2703 endmatch.rm_so += startmatch.rm_eo; 2704 endmatch.rm_eo += startmatch.rm_eo; 2705 /* Only paint the match if it is visible on screen 2706 * and it is more than zero characters long. */ 2707 if (endmatch.rm_eo > from_x && endmatch.rm_eo > startmatch.rm_so) { 2708 paintlen = actual_x(thetext, wideness(line->data, 2709 endmatch.rm_eo) - from_col - start_col); 2710 2711 wattron(midwin, varnish->attributes); 2712 mvwaddnstr(midwin, row, margin + start_col, thetext, paintlen); 2713 wattroff(midwin, varnish->attributes); 2714 2715 line->multidata[varnish->id] = JUSTONTHIS; 2716 } 2717 index = endmatch.rm_eo; 2718 /* If both start and end match are anchors, advance. */ 2719 if (startmatch.rm_so == startmatch.rm_eo && 2720 endmatch.rm_so == endmatch.rm_eo) { 2721 if (line->data[index] == '\0') 2722 break; 2723 index = step_right(line->data, index); 2724 } 2725 continue; 2726 } 2727 2728 /* Paint the rest of the line, and we're done. */ 2729 wattron(midwin, varnish->attributes); 2730 mvwaddnstr(midwin, row, margin + start_col, thetext, -1); 2731 wattroff(midwin, varnish->attributes); 2732 2733 line->multidata[varnish->id] = STARTSHERE; 2734 break; 2735 } 2736 } 2737 } 2738 #endif /* ENABLE_COLOR */ 2739 2740 #ifndef NANO_TINY 2741 if (stripe_column > from_col && !inhelp && 2742 (sequel_column == 0 || stripe_column <= sequel_column) && 2743 stripe_column <= from_col + editwincols) { 2744 ssize_t target_column = stripe_column - from_col - 1; 2745 size_t target_x = actual_x(converted, target_column); 2746 char striped_char[MAXCHARLEN]; 2747 size_t charlen = 1; 2748 2749 if (*(converted + target_x) != '\0') { 2750 charlen = collect_char(converted + target_x, striped_char); 2751 target_column = wideness(converted, target_x); 2752 #ifdef USING_OLDER_LIBVTE 2753 } else if (target_column + 1 == editwincols) { 2754 /* Defeat a VTE bug -- see https://sv.gnu.org/bugs/?55896. */ 2755 #ifdef ENABLE_UTF8 2756 if (using_utf8) { 2757 striped_char[0] = '\xC2'; 2758 striped_char[1] = '\xA0'; 2759 charlen = 2; 2760 } else 2761 #endif 2762 striped_char[0] = '.'; 2763 #endif 2764 } else 2765 striped_char[0] = ' '; 2766 2767 wattron(midwin, interface_color_pair[GUIDE_STRIPE]); 2768 mvwaddnstr(midwin, row, margin + target_column, striped_char, charlen); 2769 wattroff(midwin, interface_color_pair[GUIDE_STRIPE]); 2770 } 2771 2772 /* If the line is at least partially selected, paint the marked part. */ 2773 if (openfile->mark && ((line->lineno >= openfile->mark->lineno && 2774 line->lineno <= openfile->current->lineno) || 2775 (line->lineno <= openfile->mark->lineno && 2776 line->lineno >= openfile->current->lineno))) { 2777 linestruct *top, *bot; 2778 /* The lines where the marked region begins and ends. */ 2779 size_t top_x, bot_x; 2780 /* The x positions where the marked region begins and ends. */ 2781 int start_col; 2782 /* The column where painting starts. Zero-based. */ 2783 const char *thetext; 2784 /* The place in converted from where painting starts. */ 2785 int paintlen = -1; 2786 /* The number of characters to paint. Negative means "all". */ 2787 2788 get_region(&top, &top_x, &bot, &bot_x); 2789 2790 if (top->lineno < line->lineno || top_x < from_x) 2791 top_x = from_x; 2792 if (bot->lineno > line->lineno || bot_x > till_x) 2793 bot_x = till_x; 2794 2795 /* Only paint if the marked part of the line is on this page. */ 2796 if (top_x < till_x && bot_x > from_x) { 2797 /* Compute on which screen column to start painting. */ 2798 start_col = wideness(line->data, top_x) - from_col; 2799 2800 if (start_col < 0) 2801 start_col = 0; 2802 2803 thetext = converted + actual_x(converted, start_col); 2804 2805 /* If the end of the mark is onscreen, compute how many 2806 * characters to paint. Otherwise, just paint all. */ 2807 if (bot_x < till_x) { 2808 size_t end_col = wideness(line->data, bot_x) - from_col; 2809 paintlen = actual_x(thetext, end_col - start_col); 2810 } 2811 2812 wattron(midwin, interface_color_pair[SELECTED_TEXT]); 2813 mvwaddnstr(midwin, row, margin + start_col, thetext, paintlen); 2814 wattroff(midwin, interface_color_pair[SELECTED_TEXT]); 2815 } 2816 } 2817 #endif /* !NANO_TINY */ 2818 } 2819 2820 /* Redraw the given line so that the character at the given index is visible 2821 * -- if necessary, scroll the line horizontally (when not softwrapping). 2822 * Return the number of rows "consumed" (relevant when softwrapping). */ 2823 int update_line(linestruct *line, size_t index) 2824 { 2825 int row; 2826 /* The row in the edit window we will be updating. */ 2827 char *converted; 2828 /* The data of the line with tabs and control characters expanded. */ 2829 size_t from_col; 2830 /* From which column a horizontally scrolled line is displayed. */ 2831 2832 #ifndef NANO_TINY 2833 if (ISSET(SOFTWRAP)) 2834 return update_softwrapped_line(line); 2835 2836 sequel_column = 0; 2837 #endif 2838 2839 row = line->lineno - openfile->edittop->lineno; 2840 from_col = get_page_start(wideness(line->data, index)); 2841 2842 /* Expand the piece to be drawn to its representable form, and draw it. */ 2843 converted = display_string(line->data, from_col, editwincols, TRUE, FALSE); 2844 draw_row(row, converted, line, from_col); 2845 free(converted); 2846 2847 if (from_col > 0) { 2848 wattron(midwin, hilite_attribute); 2849 mvwaddch(midwin, row, margin, '<'); 2850 wattroff(midwin, hilite_attribute); 2851 } 2852 if (has_more) { 2853 wattron(midwin, hilite_attribute); 2854 mvwaddch(midwin, row, COLS - 1 - sidebar, '>'); 2855 wattroff(midwin, hilite_attribute); 2856 } 2857 2858 if (spotlighted && line == openfile->current) 2859 spotlight(light_from_col, light_to_col); 2860 2861 return 1; 2862 } 2863 2864 #ifndef NANO_TINY 2865 /* Redraw all the chunks of the given line (as far as they fit onscreen), 2866 * unless it's edittop, which will be displayed from column firstcolumn. 2867 * Return the number of rows that were "consumed". */ 2868 int update_softwrapped_line(linestruct *line) 2869 { 2870 int row = 0; 2871 /* The row in the edit window we will write to. */ 2872 linestruct *someline = openfile->edittop; 2873 /* An iterator needed to find the relevant row. */ 2874 int starting_row; 2875 /* The first row in the edit window that gets updated. */ 2876 size_t from_col = 0; 2877 /* The starting column of the current chunk. */ 2878 size_t to_col = 0; 2879 /* The end column of the current chunk. */ 2880 char *converted; 2881 /* The data of the chunk with tabs and control characters expanded. */ 2882 bool kickoff = TRUE; 2883 /* This tells the softwrapping routine to start at beginning-of-line. */ 2884 bool end_of_line = FALSE; 2885 /* Becomes TRUE when the last chunk of the line has been reached. */ 2886 2887 if (line == openfile->edittop) 2888 from_col = openfile->firstcolumn; 2889 else 2890 row -= chunk_for(openfile->firstcolumn, openfile->edittop); 2891 2892 /* Find out on which screen row the target line should be shown. */ 2893 while (someline != line && someline != NULL) { 2894 row += 1 + extra_chunks_in(someline); 2895 someline = someline->next; 2896 } 2897 2898 /* If the first chunk is offscreen, don't even try to display it. */ 2899 if (row < 0 || row >= editwinrows) 2900 return 0; 2901 2902 starting_row = row; 2903 2904 while (!end_of_line && row < editwinrows) { 2905 to_col = get_softwrap_breakpoint(line->data, from_col, &kickoff, &end_of_line); 2906 2907 sequel_column = (end_of_line) ? 0 : to_col; 2908 2909 /* Convert the chunk to its displayable form and draw it. */ 2910 converted = display_string(line->data, from_col, to_col - from_col, 2911 TRUE, FALSE); 2912 draw_row(row++, converted, line, from_col); 2913 free(converted); 2914 2915 from_col = to_col; 2916 } 2917 2918 if (spotlighted && line == openfile->current) 2919 spotlight_softwrapped(light_from_col, light_to_col); 2920 2921 return (row - starting_row); 2922 } 2923 #endif 2924 2925 /* Check whether the mark is on, or whether old_column and new_column are on 2926 * different "pages" (in softwrap mode, only the former applies), which means 2927 * that the relevant line needs to be redrawn. */ 2928 bool line_needs_update(const size_t old_column, const size_t new_column) 2929 { 2930 #ifndef NANO_TINY 2931 if (openfile->mark) 2932 return TRUE; 2933 else 2934 #endif 2935 return (get_page_start(old_column) != get_page_start(new_column)); 2936 } 2937 2938 /* Try to move up nrows softwrapped chunks from the given line and the 2939 * given column (leftedge). After moving, leftedge will be set to the 2940 * starting column of the current chunk. Return the number of chunks we 2941 * couldn't move up, which will be zero if we completely succeeded. */ 2942 int go_back_chunks(int nrows, linestruct **line, size_t *leftedge) 2943 { 2944 int i; 2945 2946 #ifndef NANO_TINY 2947 if (ISSET(SOFTWRAP)) { 2948 /* Recede through the requested number of chunks. */ 2949 for (i = nrows; i > 0; i--) { 2950 size_t chunk = chunk_for(*leftedge, *line); 2951 2952 *leftedge = 0; 2953 2954 if (chunk >= i) 2955 return go_forward_chunks(chunk - i, line, leftedge); 2956 2957 if (*line == openfile->filetop) 2958 break; 2959 2960 i -= chunk; 2961 *line = (*line)->prev; 2962 *leftedge = HIGHEST_POSITIVE; 2963 } 2964 2965 if (*leftedge == HIGHEST_POSITIVE) 2966 *leftedge = leftedge_for(*leftedge, *line); 2967 } else 2968 #endif 2969 for (i = nrows; i > 0 && (*line)->prev != NULL; i--) 2970 *line = (*line)->prev; 2971 2972 return i; 2973 } 2974 2975 /* Try to move down nrows softwrapped chunks from the given line and the 2976 * given column (leftedge). After moving, leftedge will be set to the 2977 * starting column of the current chunk. Return the number of chunks we 2978 * couldn't move down, which will be zero if we completely succeeded. */ 2979 int go_forward_chunks(int nrows, linestruct **line, size_t *leftedge) 2980 { 2981 int i; 2982 2983 #ifndef NANO_TINY 2984 if (ISSET(SOFTWRAP)) { 2985 size_t current_leftedge = *leftedge; 2986 bool kickoff = TRUE; 2987 2988 /* Advance through the requested number of chunks. */ 2989 for (i = nrows; i > 0; i--) { 2990 bool end_of_line = FALSE; 2991 2992 current_leftedge = get_softwrap_breakpoint((*line)->data, 2993 current_leftedge, &kickoff, &end_of_line); 2994 2995 if (!end_of_line) 2996 continue; 2997 2998 if (*line == openfile->filebot) 2999 break; 3000 3001 *line = (*line)->next; 3002 current_leftedge = 0; 3003 kickoff = TRUE; 3004 } 3005 3006 /* Only change leftedge when we actually could move. */ 3007 if (i < nrows) 3008 *leftedge = current_leftedge; 3009 } else 3010 #endif 3011 for (i = nrows; i > 0 && (*line)->next != NULL; i--) 3012 *line = (*line)->next; 3013 3014 return i; 3015 } 3016 3017 /* Return TRUE if there are fewer than a screen's worth of lines between 3018 * the line at line number was_lineno (and column was_leftedge, if we're 3019 * in softwrap mode) and the line at current[current_x]. */ 3020 bool less_than_a_screenful(size_t was_lineno, size_t was_leftedge) 3021 { 3022 #ifndef NANO_TINY 3023 if (ISSET(SOFTWRAP)) { 3024 linestruct *line = openfile->current; 3025 size_t leftedge = leftedge_for(xplustabs(), openfile->current); 3026 int rows_left = go_back_chunks(editwinrows - 1, &line, &leftedge); 3027 3028 return (rows_left > 0 || line->lineno < was_lineno || 3029 (line->lineno == was_lineno && leftedge <= was_leftedge)); 3030 } else 3031 #endif 3032 return (openfile->current->lineno - was_lineno < editwinrows); 3033 } 3034 3035 #ifndef NANO_TINY 3036 /* Draw a "scroll bar" on the righthand side of the edit window. */ 3037 void draw_scrollbar(void) 3038 { 3039 int fromline = openfile->edittop->lineno - 1; 3040 int totallines = openfile->filebot->lineno; 3041 int coveredlines = editwinrows; 3042 3043 if (ISSET(SOFTWRAP)) { 3044 linestruct *line = openfile->edittop; 3045 int extras = extra_chunks_in(line) - chunk_for(openfile->firstcolumn, line); 3046 3047 while (line->lineno + extras < fromline + editwinrows && line->next) { 3048 line = line->next; 3049 extras += extra_chunks_in(line); 3050 } 3051 3052 coveredlines = line->lineno - fromline; 3053 } 3054 3055 int lowest = (fromline * editwinrows) / totallines; 3056 int highest = lowest + (editwinrows * coveredlines) / totallines; 3057 3058 if (editwinrows > totallines && !ISSET(SOFTWRAP)) 3059 highest = editwinrows; 3060 3061 for (int row = 0; row < editwinrows; row++) { 3062 bardata[row] = ' '|interface_color_pair[SCROLL_BAR]| 3063 ((row < lowest || row > highest) ? A_NORMAL : A_REVERSE); 3064 mvwaddch(midwin, row, COLS - 1, bardata[row]); 3065 } 3066 } 3067 #endif 3068 3069 /* Scroll the edit window one row in the given direction, and 3070 * draw the relevant content on the resultant blank row. */ 3071 void edit_scroll(bool direction) 3072 { 3073 linestruct *line; 3074 size_t leftedge; 3075 int nrows = 1; 3076 3077 /* Move the top line of the edit window one row up or down. */ 3078 if (direction == BACKWARD) 3079 go_back_chunks(1, &openfile->edittop, &openfile->firstcolumn); 3080 else 3081 go_forward_chunks(1, &openfile->edittop, &openfile->firstcolumn); 3082 3083 /* Actually scroll the text of the edit window one row up or down. */ 3084 scrollok(midwin, TRUE); 3085 wscrl(midwin, (direction == BACKWARD) ? -1 : 1); 3086 scrollok(midwin, FALSE); 3087 3088 /* If we're not on the first "page" (when not softwrapping), or the mark 3089 * is on, the row next to the scrolled region needs to be redrawn too. */ 3090 if (line_needs_update(openfile->placewewant, 0) && nrows < editwinrows) 3091 nrows++; 3092 3093 /* If we scrolled backward, the top row needs to be redrawn. */ 3094 line = openfile->edittop; 3095 leftedge = openfile->firstcolumn; 3096 3097 /* If we scrolled forward, the bottom row needs to be redrawn. */ 3098 if (direction == FORWARD) 3099 go_forward_chunks(editwinrows - nrows, &line, &leftedge); 3100 3101 #ifndef NANO_TINY 3102 if (sidebar) 3103 draw_scrollbar(); 3104 3105 if (ISSET(SOFTWRAP)) { 3106 /* Compensate for the earlier chunks of a softwrapped line. */ 3107 nrows += chunk_for(leftedge, line); 3108 3109 /* Don't compensate for the chunks that are offscreen. */ 3110 if (line == openfile->edittop) 3111 nrows -= chunk_for(openfile->firstcolumn, line); 3112 } 3113 #endif 3114 3115 /* Draw new content on the blank row (and on the bordering row too 3116 * when it was deemed necessary). */ 3117 while (nrows > 0 && line != NULL) { 3118 nrows -= update_line(line, (line == openfile->current) ? 3119 openfile->current_x : 0); 3120 line = line->next; 3121 } 3122 } 3123 3124 #ifndef NANO_TINY 3125 /* Get the column number after leftedge where we can break the given linedata, 3126 * and return it. (This will always be at most editwincols after leftedge.) 3127 * When kickoff is TRUE, start at the beginning of the linedata; otherwise, 3128 * continue from where the previous call left off. Set end_of_line to TRUE 3129 * when end-of-line is reached while searching for a possible breakpoint. */ 3130 size_t get_softwrap_breakpoint(const char *linedata, size_t leftedge, 3131 bool *kickoff, bool *end_of_line) 3132 { 3133 static const char *text; 3134 /* Pointer at the current character in this line's data. */ 3135 static size_t column; 3136 /* Column position that corresponds to the above pointer. */ 3137 size_t rightside = leftedge + editwincols; 3138 /* The place at or before which text must be broken. */ 3139 size_t breaking_col = rightside; 3140 /* The column where text can be broken, when there's no better. */ 3141 size_t last_blank_col = 0; 3142 /* The column position of the last seen whitespace character. */ 3143 const char *farthest_blank = NULL; 3144 /* A pointer to the last seen whitespace character in text. */ 3145 3146 /* Initialize the static variables when it's another line. */ 3147 if (*kickoff) { 3148 text = linedata; 3149 column = 0; 3150 *kickoff = FALSE; 3151 } 3152 3153 /* First find the place in text where the current chunk starts. */ 3154 while (*text != '\0' && column < leftedge) 3155 text += advance_over(text, &column); 3156 3157 /* Now find the place in text where this chunk should end. */ 3158 while (*text != '\0' && column <= rightside) { 3159 /* When breaking at blanks, do it *before* the target column. */ 3160 if (ISSET(AT_BLANKS) && is_blank_char(text) && column < rightside) { 3161 farthest_blank = text; 3162 last_blank_col = column; 3163 } 3164 3165 breaking_col = (*text == '\t' ? rightside : column); 3166 text += advance_over(text, &column); 3167 } 3168 3169 /* If we didn't overshoot the limit, we've found a breaking point; 3170 * and we've reached EOL if we didn't even *reach* the limit. */ 3171 if (column <= rightside) { 3172 *end_of_line = (column < rightside); 3173 return column; 3174 } 3175 3176 /* If we're softwrapping at blanks and we found at least one blank, break 3177 * after that blank -- if it doesn't overshoot the screen's edge. */ 3178 if (farthest_blank != NULL) { 3179 size_t aftertheblank = last_blank_col; 3180 size_t onestep = advance_over(farthest_blank, &aftertheblank); 3181 3182 if (aftertheblank <= rightside) { 3183 text = farthest_blank + onestep; 3184 column = aftertheblank; 3185 return aftertheblank; 3186 } 3187 3188 /* If it's a tab that overshoots, break at the screen's edge. */ 3189 if (*farthest_blank == '\t') 3190 breaking_col = rightside; 3191 } 3192 3193 /* Otherwise, break at the last character that doesn't overshoot. */ 3194 return (editwincols > 1) ? breaking_col : column - 1; 3195 } 3196 3197 /* Return the row number of the softwrapped chunk in the given line that the 3198 * given column is on, relative to the first row (zero-based). If leftedge 3199 * isn't NULL, return in it the leftmost column of the chunk. */ 3200 size_t get_chunk_and_edge(size_t column, linestruct *line, size_t *leftedge) 3201 { 3202 size_t current_chunk = 0; 3203 bool end_of_line = FALSE; 3204 bool kickoff = TRUE; 3205 size_t start_col = 0; 3206 size_t end_col; 3207 3208 while (TRUE) { 3209 end_col = get_softwrap_breakpoint(line->data, start_col, &kickoff, &end_of_line); 3210 3211 /* When the column is in range or we reached end-of-line, we're done. */ 3212 if (end_of_line || (start_col <= column && column < end_col)) { 3213 if (leftedge != NULL) 3214 *leftedge = start_col; 3215 return current_chunk; 3216 } 3217 3218 start_col = end_col; 3219 current_chunk++; 3220 } 3221 } 3222 3223 /* Return how many extra rows the given line needs when softwrapping. */ 3224 size_t extra_chunks_in(linestruct *line) 3225 { 3226 return get_chunk_and_edge((size_t)-1, line, NULL); 3227 } 3228 3229 /* Return the row of the softwrapped chunk of the given line that column is on, 3230 * relative to the first row (zero-based). */ 3231 size_t chunk_for(size_t column, linestruct *line) 3232 { 3233 return get_chunk_and_edge(column, line, NULL); 3234 } 3235 3236 /* Return the leftmost column of the softwrapped chunk of the given line that 3237 * the given column is on. */ 3238 size_t leftedge_for(size_t column, linestruct *line) 3239 { 3240 size_t leftedge; 3241 3242 get_chunk_and_edge(column, line, &leftedge); 3243 3244 return leftedge; 3245 } 3246 3247 /* Ensure that firstcolumn is at the starting column of the softwrapped chunk 3248 * it's on. We need to do this when the number of columns of the edit window 3249 * has changed, because then the width of softwrapped chunks has changed. */ 3250 void ensure_firstcolumn_is_aligned(void) 3251 { 3252 if (ISSET(SOFTWRAP)) 3253 openfile->firstcolumn = leftedge_for(openfile->firstcolumn, 3254 openfile->edittop); 3255 else 3256 openfile->firstcolumn = 0; 3257 3258 /* If smooth scrolling is on, make sure the viewport doesn't center. */ 3259 focusing = FALSE; 3260 } 3261 #endif /* !NANO_TINY */ 3262 3263 /* When in softwrap mode, and the given column is on or after the breakpoint of 3264 * a softwrapped chunk, shift it back to the last column before the breakpoint. 3265 * The given column is relative to the given leftedge in current. The returned 3266 * column is relative to the start of the text. */ 3267 size_t actual_last_column(size_t leftedge, size_t column) 3268 { 3269 #ifndef NANO_TINY 3270 if (ISSET(SOFTWRAP)) { 3271 bool kickoff = TRUE; 3272 bool last_chunk = FALSE; 3273 size_t end_col = get_softwrap_breakpoint(openfile->current->data, 3274 leftedge, &kickoff, &last_chunk) - leftedge; 3275 3276 /* If we're not on the last chunk, we're one column past the end of 3277 * the row. Shifting back one column might put us in the middle of 3278 * a multi-column character, but actual_x() will fix that later. */ 3279 if (!last_chunk) 3280 end_col--; 3281 3282 if (column > end_col) 3283 column = end_col; 3284 } 3285 #endif 3286 3287 return leftedge + column; 3288 } 3289 3290 /* Return TRUE if current[current_x] is before the viewport. */ 3291 bool current_is_above_screen(void) 3292 { 3293 #ifndef NANO_TINY 3294 if (ISSET(SOFTWRAP)) 3295 return (openfile->current->lineno < openfile->edittop->lineno || 3296 (openfile->current->lineno == openfile->edittop->lineno && 3297 xplustabs() < openfile->firstcolumn)); 3298 else 3299 #endif 3300 return (openfile->current->lineno < openfile->edittop->lineno); 3301 } 3302 3303 #define SHIM (ISSET(ZERO) && (currmenu == MREPLACEWITH || currmenu == MYESNO) ? 1 : 0) 3304 3305 /* Return TRUE if current[current_x] is beyond the viewport. */ 3306 bool current_is_below_screen(void) 3307 { 3308 #ifndef NANO_TINY 3309 if (ISSET(SOFTWRAP)) { 3310 linestruct *line = openfile->edittop; 3311 size_t leftedge = openfile->firstcolumn; 3312 3313 /* If current[current_x] is more than a screen's worth of lines after 3314 * edittop at column firstcolumn, it's below the screen. */ 3315 return (go_forward_chunks(editwinrows - 1 - SHIM, &line, &leftedge) == 0 && 3316 (line->lineno < openfile->current->lineno || 3317 (line->lineno == openfile->current->lineno && 3318 leftedge < leftedge_for(xplustabs(), openfile->current)))); 3319 } else 3320 #endif 3321 return (openfile->current->lineno >= 3322 openfile->edittop->lineno + editwinrows - SHIM); 3323 } 3324 3325 /* Return TRUE if current[current_x] is outside the viewport. */ 3326 bool current_is_offscreen(void) 3327 { 3328 return (current_is_above_screen() || current_is_below_screen()); 3329 } 3330 3331 /* Update any lines between old_current and current that need to be 3332 * updated. Use this if we've moved without changing any text. */ 3333 void edit_redraw(linestruct *old_current, update_type manner) 3334 { 3335 size_t was_pww = openfile->placewewant; 3336 3337 openfile->placewewant = xplustabs(); 3338 3339 /* If the current line is offscreen, scroll until it's onscreen. */ 3340 if (current_is_offscreen()) { 3341 adjust_viewport(ISSET(JUMPY_SCROLLING) ? CENTERING : manner); 3342 refresh_needed = TRUE; 3343 return; 3344 } 3345 3346 #ifndef NANO_TINY 3347 /* If the mark is on, update all lines between old_current and current. */ 3348 if (openfile->mark) { 3349 linestruct *line = old_current; 3350 3351 while (line != openfile->current) { 3352 update_line(line, 0); 3353 3354 line = (line->lineno > openfile->current->lineno) ? 3355 line->prev : line->next; 3356 } 3357 } else 3358 #endif 3359 /* Otherwise, update old_current only if it differs from current 3360 * and was horizontally scrolled. */ 3361 if (old_current != openfile->current && get_page_start(was_pww) > 0) 3362 update_line(old_current, 0); 3363 3364 /* Update current if the mark is on or it has changed "page", or if it 3365 * differs from old_current and needs to be horizontally scrolled. */ 3366 if (line_needs_update(was_pww, openfile->placewewant) || 3367 (old_current != openfile->current && 3368 get_page_start(openfile->placewewant) > 0)) 3369 update_line(openfile->current, openfile->current_x); 3370 } 3371 3372 /* Refresh the screen without changing the position of lines. Use this 3373 * if we've moved and changed text. */ 3374 void edit_refresh(void) 3375 { 3376 linestruct *line; 3377 int row = 0; 3378 3379 /* If the current line is out of view, get it back on screen. */ 3380 if (current_is_offscreen()) 3381 adjust_viewport((focusing || ISSET(JUMPY_SCROLLING)) ? CENTERING : FLOWING); 3382 3383 #ifdef ENABLE_COLOR 3384 /* When needed and useful, initialize the colors for the current syntax. */ 3385 if (openfile->syntax && !have_palette && !ISSET(NO_SYNTAX) && has_colors()) 3386 prepare_palette(); 3387 3388 /* When the line above the viewport does not have multidata, recalculate all. */ 3389 recook |= ISSET(SOFTWRAP) && openfile->edittop->prev && !openfile->edittop->prev->multidata; 3390 3391 if (recook) { 3392 precalc_multicolorinfo(); 3393 perturbed = FALSE; 3394 recook = FALSE; 3395 } 3396 #endif 3397 3398 #ifndef NANO_TINY 3399 if (sidebar) 3400 draw_scrollbar(); 3401 #endif 3402 3403 //#define TIMEREFRESH 123 3404 #ifdef TIMEREFRESH 3405 #include <time.h> 3406 clock_t start = clock(); 3407 #endif 3408 3409 line = openfile->edittop; 3410 3411 while (row < editwinrows && line != NULL) { 3412 row += update_line(line, (line == openfile->current) ? openfile->current_x : 0); 3413 line = line->next; 3414 } 3415 3416 while (row < editwinrows) { 3417 blank_row(midwin, row); 3418 #ifndef NANO_TINY 3419 if (sidebar) 3420 mvwaddch(midwin, row, COLS - 1, bardata[row]); 3421 #endif 3422 row++; 3423 } 3424 3425 #ifdef TIMEREFRESH 3426 statusline(INFO, "Refresh: %.1f ms", 1000 * (double)(clock() - start) / CLOCKS_PER_SEC); 3427 #endif 3428 3429 place_the_cursor(); 3430 3431 wnoutrefresh(midwin); 3432 3433 refresh_needed = FALSE; 3434 } 3435 3436 /* Move edittop so that current is on the screen. manner says how: 3437 * STATIONARY means that the cursor should stay on the same screen row, 3438 * CENTERING means that current should end up in the middle of the screen, 3439 * and FLOWING means that it should scroll no more than needed to bring 3440 * current into view. */ 3441 void adjust_viewport(update_type manner) 3442 { 3443 int goal = 0; 3444 3445 if (manner == STATIONARY) 3446 goal = openfile->cursor_row; 3447 else if (manner == CENTERING) 3448 goal = editwinrows / 2; 3449 else if (!current_is_above_screen()) 3450 goal = editwinrows - 1 - SHIM; 3451 3452 openfile->edittop = openfile->current; 3453 #ifndef NANO_TINY 3454 if (ISSET(SOFTWRAP)) 3455 openfile->firstcolumn = leftedge_for(xplustabs(), openfile->current); 3456 #endif 3457 3458 /* Move edittop back goal rows, starting at current[current_x]. */ 3459 go_back_chunks(goal, &openfile->edittop, &openfile->firstcolumn); 3460 } 3461 3462 /* Tell curses to unconditionally redraw whatever was on the screen. */ 3463 void full_refresh(void) 3464 { 3465 wrefresh(curscr); 3466 } 3467 3468 /* Draw the three elements of the screen: the title bar, 3469 * the contents of the edit window, and the bottom bars. */ 3470 void draw_all_subwindows(void) 3471 { 3472 titlebar(title); 3473 #ifdef ENABLE_HELP 3474 if (inhelp) { 3475 close_buffer(); 3476 wrap_help_text_into_buffer(); 3477 } else 3478 #endif 3479 edit_refresh(); 3480 bottombars(currmenu); 3481 } 3482 3483 /* Display on the status bar details about the current cursor position. */ 3484 void report_cursor_position(void) 3485 { 3486 size_t fullwidth = breadth(openfile->current->data) + 1; 3487 size_t column = xplustabs() + 1; 3488 int linepct, colpct, charpct; 3489 char saved_byte; 3490 size_t sum; 3491 3492 saved_byte = openfile->current->data[openfile->current_x]; 3493 openfile->current->data[openfile->current_x] = '\0'; 3494 3495 /* Determine the size of the file up to the cursor. */ 3496 sum = number_of_characters_in(openfile->filetop, openfile->current); 3497 3498 openfile->current->data[openfile->current_x] = saved_byte; 3499 3500 /* Calculate the percentages. */ 3501 linepct = 100 * openfile->current->lineno / openfile->filebot->lineno; 3502 colpct = 100 * column / fullwidth; 3503 charpct = (openfile->totsize == 0) ? 0 : 100 * sum / openfile->totsize; 3504 3505 statusline(INFO, 3506 _("line %*zd/%zd (%2d%%), col %2zu/%2zu (%3d%%), char %*zu/%zu (%2d%%)"), 3507 digits(openfile->filebot->lineno), 3508 openfile->current->lineno, openfile->filebot->lineno, linepct, 3509 column, fullwidth, colpct, 3510 digits(openfile->totsize), sum, openfile->totsize, charpct); 3511 } 3512 3513 /* Highlight the text between the given two columns on the current line. */ 3514 void spotlight(size_t from_col, size_t to_col) 3515 { 3516 size_t right_edge = get_page_start(from_col) + editwincols; 3517 bool overshoots = (to_col > right_edge); 3518 char *word; 3519 3520 place_the_cursor(); 3521 3522 /* Limit the end column to the edge of the screen. */ 3523 if (overshoots) 3524 to_col = right_edge; 3525 3526 /* If the target text is of zero length, highlight a space instead. */ 3527 if (to_col == from_col) { 3528 word = copy_of(" "); 3529 to_col++; 3530 } else 3531 word = display_string(openfile->current->data, from_col, 3532 to_col - from_col, FALSE, overshoots); 3533 3534 wattron(midwin, interface_color_pair[SPOTLIGHTED]); 3535 waddnstr(midwin, word, actual_x(word, to_col)); 3536 if (overshoots) 3537 mvwaddch(midwin, openfile->cursor_row, COLS - 1 - sidebar, '>'); 3538 wattroff(midwin, interface_color_pair[SPOTLIGHTED]); 3539 3540 free(word); 3541 } 3542 3543 #ifndef NANO_TINY 3544 /* Highlight the text between the given two columns on the current line. */ 3545 void spotlight_softwrapped(size_t from_col, size_t to_col) 3546 { 3547 ssize_t row; 3548 size_t leftedge = leftedge_for(from_col, openfile->current); 3549 size_t break_col; 3550 bool end_of_line = FALSE; 3551 bool kickoff = TRUE; 3552 char *word; 3553 3554 place_the_cursor(); 3555 row = openfile->cursor_row; 3556 3557 while (row < editwinrows) { 3558 break_col = get_softwrap_breakpoint(openfile->current->data, 3559 leftedge, &kickoff, &end_of_line); 3560 3561 /* If the highlighting ends on this chunk, we can stop after it. */ 3562 if (break_col >= to_col) { 3563 end_of_line = TRUE; 3564 break_col = to_col; 3565 } 3566 3567 /* If the target text is of zero length, highlight a space instead. */ 3568 if (break_col == from_col) { 3569 word = copy_of(" "); 3570 break_col++; 3571 } else 3572 word = display_string(openfile->current->data, from_col, 3573 break_col - from_col, FALSE, FALSE); 3574 3575 wattron(midwin, interface_color_pair[SPOTLIGHTED]); 3576 waddnstr(midwin, word, actual_x(word, break_col)); 3577 wattroff(midwin, interface_color_pair[SPOTLIGHTED]); 3578 3579 free(word); 3580 3581 if (end_of_line) 3582 break; 3583 3584 wmove(midwin, ++row, margin); 3585 3586 leftedge = break_col; 3587 from_col = break_col; 3588 } 3589 } 3590 #endif 3591 3592 #ifdef ENABLE_EXTRA 3593 #define CREDIT_LEN 52 3594 #define XLCREDIT_LEN 9 3595 3596 /* Fully blank the terminal screen, then slowly "crawl" the credits over it. 3597 * Abort the crawl upon any keystroke. */ 3598 void do_credits(void) 3599 { 3600 bool with_interface = !ISSET(ZERO); 3601 bool with_help = !ISSET(NO_HELP); 3602 int crpos = 0, xlpos = 0; 3603 3604 const char *credits[CREDIT_LEN] = { 3605 NULL, /* "The nano text editor" */ 3606 NULL, /* "version" */ 3607 VERSION, 3608 "", 3609 NULL, /* "Brought to you by:" */ 3610 "Chris Allegretta", 3611 "Benno Schulenberg", 3612 "David Lawrence Ramsey", 3613 "Jordi Mallach", 3614 "David Benbennick", 3615 "Rocco Corsi", 3616 "Mike Frysinger", 3617 "Adam Rogoyski", 3618 "Rob Siemborski", 3619 "Mark Majeres", 3620 "Ken Tyler", 3621 "Sven Guckes", 3622 "Bill Soudan", 3623 "Christian Weisgerber", 3624 "Erik Andersen", 3625 "Big Gaute", 3626 "Joshua Jensen", 3627 "Ryan Krebs", 3628 "Albert Chin", 3629 "", 3630 NULL, /* "Special thanks to:" */ 3631 "Monique, Brielle & Joseph", 3632 "Plattsburgh State University", 3633 "Benet Laboratories", 3634 "Amy Allegretta", 3635 "Linda Young", 3636 "Jeremy Robichaud", 3637 "Richard Kolb II", 3638 NULL, /* "The Free Software Foundation" */ 3639 "Linus Torvalds", 3640 NULL, /* "the many translators and the TP" */ 3641 NULL, /* "For ncurses:" */ 3642 "Thomas Dickey", 3643 "Pavel Curtis", 3644 "Zeyd Ben-Halim", 3645 "Eric S. Raymond", 3646 NULL, /* "and anyone else we forgot..." */ 3647 "", 3648 "", 3649 NULL, /* "Thank you for using nano!" */ 3650 "", 3651 "", 3652 "(C) 2025", 3653 "Free Software Foundation, Inc.", 3654 "", 3655 "", 3656 "https://nano-editor.org/" 3657 }; 3658 3659 const char *xlcredits[XLCREDIT_LEN] = { 3660 N_("The nano text editor"), 3661 N_("version"), 3662 N_("Brought to you by:"), 3663 N_("Special thanks to:"), 3664 N_("The Free Software Foundation"), 3665 N_("the many translators and the TP"), 3666 N_("For ncurses:"), 3667 N_("and anyone else we forgot..."), 3668 N_("Thank you for using nano!") 3669 }; 3670 3671 if (with_interface || with_help) { 3672 SET(ZERO); 3673 SET(NO_HELP); 3674 window_init(); 3675 } 3676 3677 nodelay(midwin, TRUE); 3678 scrollok(midwin, TRUE); 3679 3680 blank_edit(); 3681 wrefresh(midwin); 3682 napms(600); 3683 3684 for (crpos = 0; crpos < CREDIT_LEN + editwinrows / 2; crpos++) { 3685 if (crpos < CREDIT_LEN) { 3686 const char *text = credits[crpos]; 3687 3688 if (!text) 3689 text = _(xlcredits[xlpos++]); 3690 3691 mvwaddstr(midwin, editwinrows - 1, (COLS - breadth(text)) / 2, text); 3692 wrefresh(midwin); 3693 } 3694 3695 if (wgetch(midwin) != ERR) 3696 break; 3697 3698 napms(600); 3699 wscrl(midwin, 1); 3700 wrefresh(midwin); 3701 3702 if (wgetch(midwin) != ERR) 3703 break; 3704 3705 napms(600); 3706 wscrl(midwin, 1); 3707 wrefresh(midwin); 3708 } 3709 3710 if (with_interface) 3711 UNSET(ZERO); 3712 if (with_help) 3713 UNSET(NO_HELP); 3714 window_init(); 3715 3716 scrollok(midwin, FALSE); 3717 nodelay(midwin, FALSE); 3718 3719 draw_all_subwindows(); 3720 } 3721 #endif /* ENABLE_EXTRA */