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