nano

nano with my custom patches
git clone git://bsandro.tech/nano
Log | Files | Refs | README | LICENSE

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 */