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