winio.c 104 KB
Newer Older
Chris Allegretta's avatar
Chris Allegretta committed
1
/**************************************************************************
2
 *   winio.c  --  This file is part of GNU nano.                          *
Chris Allegretta's avatar
Chris Allegretta committed
3
 *                                                                        *
4
 *   Copyright (C) 1999-2011, 2013-2017 Free Software Foundation, Inc.    *
5
 *   Copyright (C) 2014, 2015, 2016, 2017 Benno Schulenberg               *
6
 *                                                                        *
7
8
9
10
 *   GNU nano is free software: you can redistribute it and/or modify     *
 *   it under the terms of the GNU General Public License as published    *
 *   by the Free Software Foundation, either version 3 of the License,    *
 *   or (at your option) any later version.                               *
Chris Allegretta's avatar
Chris Allegretta committed
11
 *                                                                        *
12
13
14
15
 *   GNU nano is distributed in the hope that it will be useful,          *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty          *
 *   of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.              *
 *   See the GNU General Public License for more details.                 *
Chris Allegretta's avatar
Chris Allegretta committed
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/.  *
Chris Allegretta's avatar
Chris Allegretta committed
19
20
21
 *                                                                        *
 **************************************************************************/

22
#include "proto.h"
23
#include "revision.h"
24

25
#ifdef __linux__
26
#include <sys/ioctl.h>
27
#endif
28

29
#include <stdio.h>
Chris Allegretta's avatar
Chris Allegretta committed
30
31
#include <stdarg.h>
#include <string.h>
32
#include <unistd.h>
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
33
#include <ctype.h>
Chris Allegretta's avatar
Chris Allegretta committed
34

35
36
37
38
39
40
#ifdef REVISION
#define BRANDING REVISION
#else
#define BRANDING PACKAGE_STRING
#endif

41
static int *key_buffer = NULL;
42
43
	/* The keystroke buffer, containing all the keystrokes we
	 * haven't handled yet at a given point. */
44
static size_t key_buffer_len = 0;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
45
	/* The length of the keystroke buffer. */
46
47
static bool solitary = FALSE;
	/* Whether an Esc arrived by itself -- not as leader of a sequence. */
48
static int statusblank = 0;
49
	/* The number of keystrokes left before we blank the statusbar. */
50
static bool suppress_cursorpos = FALSE;
51
	/* Should we skip constant position display for one keystroke? */
52
#ifdef USING_OLD_NCURSES
53
54
static bool seen_wide = FALSE;
	/* Whether we've seen a multicolumn character in the current line. */
55
#endif
56

57
58
/* Control character compatibility:
 *
59
60
61
62
63
 * - Ctrl-H is Backspace under ASCII, ANSI, VT100, and VT220.
 * - Ctrl-I is Tab under ASCII, ANSI, VT100, VT220, and VT320.
 * - Ctrl-M is Enter under ASCII, ANSI, VT100, VT220, and VT320.
 * - Ctrl-Q is XON under ASCII, ANSI, VT100, VT220, and VT320.
 * - Ctrl-S is XOFF under ASCII, ANSI, VT100, VT220, and VT320.
64
65
 * - Ctrl-8 (Ctrl-?) is Delete under ASCII, ANSI, VT100, and VT220,
 *          but is Backspace under VT320.
66
 *
67
 * Note: VT220 and VT320 also generate Esc [ 3 ~ for Delete.  By
68
69
 * default, xterm assumes it's running on a VT320 and generates Ctrl-8
 * (Ctrl-?) for Backspace and Esc [ 3 ~ for Delete.  This causes
70
 * problems for VT100-derived terminals such as the FreeBSD console,
71
 * which expect Ctrl-H for Backspace and Ctrl-8 (Ctrl-?) for Delete, and
72
73
74
75
76
77
78
79
80
 * on which the VT320 sequences are translated by the keypad to KEY_DC
 * and [nothing].  We work around this conflict via the REBIND_DELETE
 * flag: if it's not set, we assume VT320 compatibility, and if it is,
 * we assume VT100 compatibility.  Thanks to Lee Nelson and Wouter van
 * Hemel for helping work this conflict out.
 *
 * Escape sequence compatibility:
 *
 * We support escape sequences for ANSI, VT100, VT220, VT320, the Linux
81
 * console, the FreeBSD console, the Mach console, xterm, rxvt, Eterm,
82
83
 * and Terminal, and some for iTerm2.  Among these, there are several
 * conflicts and omissions, outlined as follows:
84
85
86
87
88
89
 *
 * - Tab on ANSI == PageUp on FreeBSD console; the former is omitted.
 *   (Ctrl-I is also Tab on ANSI, which we already support.)
 * - PageDown on FreeBSD console == Center (5) on numeric keypad with
 *   NumLock off on Linux console; the latter is omitted.  (The editing
 *   keypad key is more important to have working than the numeric
90
 *   keypad key, because the latter has no value when NumLock is off.)
91
92
93
94
 * - F1 on FreeBSD console == the mouse key on xterm/rxvt/Eterm; the
 *   latter is omitted.  (Mouse input will only work properly if the
 *   extended keypad value KEY_MOUSE is generated on mouse events
 *   instead of the escape sequence.)
95
 * - F9 on FreeBSD console == PageDown on Mach console; the former is
96
97
98
 *   omitted.  (The editing keypad is more important to have working
 *   than the function keys, because the functions of the former are not
 *   arbitrary and the functions of the latter are.)
99
 * - F10 on FreeBSD console == PageUp on Mach console; the former is
100
 *   omitted.  (Same as above.)
101
 * - F13 on FreeBSD console == End on Mach console; the former is
102
 *   omitted.  (Same as above.)
103
104
105
106
107
108
 * - F15 on FreeBSD console == Shift-Up on rxvt/Eterm; the former is
 *   omitted.  (The arrow keys, with or without modifiers, are more
 *   important to have working than the function keys, because the
 *   functions of the former are not arbitrary and the functions of the
 *   latter are.)
 * - F16 on FreeBSD console == Shift-Down on rxvt/Eterm; the former is
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
109
 *   omitted.  (Same as above.) */
110

111
/* Read in a sequence of keystrokes from win and save them in the
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
112
113
 * keystroke buffer.  This should only be called when the keystroke
 * buffer is empty. */
114
void get_key_buffer(WINDOW *win)
115
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
116
    int input;
117
    size_t errcount = 0;
118
119
120
121
122

    /* If the keystroke buffer isn't empty, get out. */
    if (key_buffer != NULL)
	return;

123
124
125
126
    /* Just before reading in the first character, display any pending
     * screen updates. */
    doupdate();

127
    /* Read in the first character using whatever mode we're in. */
128
129
    input = wgetch(win);

130
#ifndef NANO_TINY
131
    if (the_window_resized) {
132
	ungetch(input);
133
	regenerate_screen();
134
	input = KEY_WINCH;
135
    }
136
137
#endif

138
139
140
141
142
143
144
145
146
147
148
    if (input == ERR && nodelay_mode)
	return;

    while (input == ERR) {
	/* If we've failed to get a character MAX_BUF_SIZE times in a row,
	 * assume our input source is gone and die gracefully.  We could
	 * check if errno is set to EIO ("Input/output error") and die in
	 * that case, but it's not always set properly.  Argh. */
	if (++errcount == MAX_BUF_SIZE)
	    handle_hupterm(0);

149
#ifndef NANO_TINY
150
151
	if (the_window_resized) {
	    regenerate_screen();
152
153
	    input = KEY_WINCH;
	    break;
154
	}
155
156
#endif
	input = wgetch(win);
157
    }
158

159
160
    /* Increment the length of the keystroke buffer, and save the value
     * of the keystroke at the end of it. */
161
    key_buffer_len++;
162
163
    key_buffer = (int *)nmalloc(sizeof(int));
    key_buffer[0] = input;
164

165
166
167
168
169
170
171
#ifndef NANO_TINY
    /* If we got SIGWINCH, get out immediately since the win argument is
     * no longer valid. */
    if (input == KEY_WINCH)
	return;
#endif

172
173
174
175
    /* Read in the remaining characters using non-blocking input. */
    nodelay(win, TRUE);

    while (TRUE) {
176
	input = wgetch(win);
177

178
	/* If there aren't any more characters, stop reading. */
179
	if (input == ERR)
180
181
	    break;

182
183
	/* Otherwise, increment the length of the keystroke buffer, and
	 * save the value of the keystroke at the end of it. */
184
	key_buffer_len++;
185
186
187
	key_buffer = (int *)nrealloc(key_buffer, key_buffer_len *
		sizeof(int));
	key_buffer[key_buffer_len - 1] = input;
188
189
    }

190
191
192
    /* Restore waiting mode if it was on. */
    if (!nodelay_mode)
	nodelay(win, FALSE);
193
194

#ifdef DEBUG
195
196
    {
	size_t i;
197
	fprintf(stderr, "\nget_key_buffer(): the sequence of hex codes:");
198
199
200
201
	for (i = 0; i < key_buffer_len; i++)
	    fprintf(stderr, " %3x", key_buffer[i]);
	fprintf(stderr, "\n");
    }
202
#endif
203
}
204

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
205
/* Return the length of the keystroke buffer. */
206
size_t get_key_buffer_len(void)
207
208
209
210
{
    return key_buffer_len;
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
211
/* Add the keystrokes in input to the keystroke buffer. */
212
void unget_input(int *input, size_t input_len)
213
214
{
    /* If input is empty, get out. */
215
    if (input_len == 0)
216
217
	return;

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
218
219
    /* If adding input would put the keystroke buffer beyond maximum
     * capacity, only add enough of input to put it at maximum
220
     * capacity. */
221
222
    if (key_buffer_len + input_len < key_buffer_len)
	input_len = (size_t)-1 - key_buffer_len;
223

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
224
225
226
    /* Add the length of input to the length of the keystroke buffer,
     * and reallocate the keystroke buffer so that it has enough room
     * for input. */
227
228
229
    key_buffer_len += input_len;
    key_buffer = (int *)nrealloc(key_buffer, key_buffer_len *
	sizeof(int));
230

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
231
232
    /* If the keystroke buffer wasn't empty before, move its beginning
     * forward far enough so that we can add input to its beginning. */
233
234
235
    if (key_buffer_len > input_len)
	memmove(key_buffer + input_len, key_buffer,
		(key_buffer_len - input_len) * sizeof(int));
236

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
237
    /* Copy input to the beginning of the keystroke buffer. */
238
    memcpy(key_buffer, input, input_len * sizeof(int));
239
240
}

241
242
243
/* Put the character given in kbinput back into the input stream.  If it
 * is a Meta key, also insert an Escape character in front of it. */
void unget_kbinput(int kbinput, bool metakey)
244
{
245
    unget_input(&kbinput, 1);
246

247
    if (metakey) {
248
	kbinput = ESC_CODE;
249
	unget_input(&kbinput, 1);
250
251
252
    }
}

253
/* Try to read input_len codes from the keystroke buffer.  If the
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
254
 * keystroke buffer is empty and win isn't NULL, try to read in more
255
 * codes from win and add them to the keystroke buffer before doing
256
 * anything else.  If the keystroke buffer is (still) empty, return NULL. */
257
int *get_input(WINDOW *win, size_t input_len)
258
{
259
    int *input;
260

261
262
    if (key_buffer_len == 0 && win != NULL)
	get_key_buffer(win);
263

264
265
    if (key_buffer_len == 0)
	return NULL;
266

267
    /* Limit the request to the number of available codes in the buffer. */
268
269
270
    if (input_len > key_buffer_len)
	input_len = key_buffer_len;

271
    /* Copy input_len codes from the head of the keystroke buffer. */
272
273
    input = (int *)nmalloc(input_len * sizeof(int));
    memcpy(input, key_buffer, input_len * sizeof(int));
274
    key_buffer_len -= input_len;
275

276
    /* If the keystroke buffer is now empty, mark it as such. */
277
278
279
280
    if (key_buffer_len == 0) {
	free(key_buffer);
	key_buffer = NULL;
    } else {
281
	/* Trim from the buffer the codes that were copied. */
282
	memmove(key_buffer, key_buffer + input_len, key_buffer_len *
283
284
285
		sizeof(int));
	key_buffer = (int *)nrealloc(key_buffer, key_buffer_len *
		sizeof(int));
286
287
288
    }

    return input;
289
290
}

291
/* Read in a single keystroke, ignoring any that are invalid. */
292
int get_kbinput(WINDOW *win)
293
{
294
    int kbinput = ERR;
295

296
    /* Extract one keystroke from the input stream. */
297
298
    while (kbinput == ERR)
	kbinput = parse_kbinput(win);
299

300
301
302
303
304
#ifdef DEBUG
    fprintf(stderr, "after parsing:  kbinput = %d, meta_key = %s\n",
	kbinput, meta_key ? "TRUE" : "FALSE");
#endif

305
    /* If we read from the edit window, blank the statusbar if needed. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
306
    if (win == edit)
307
308
	check_statusblank();

309
310
311
    return kbinput;
}

312
313
/* Extract a single keystroke from the input stream.  Translate escape
 * sequences and extended keypad codes into their corresponding values.
314
 * Set meta_key to TRUE when appropriate.  Supported extended keypad values
315
316
317
 * are: [arrow key], Ctrl-[arrow key], Shift-[arrow key], Enter, Backspace,
 * the editing keypad (Insert, Delete, Home, End, PageUp, and PageDown),
 * the function keys (F1-F16), and the numeric keypad with NumLock off. */
318
int parse_kbinput(WINDOW *win)
319
{
320
    static int escapes = 0, byte_digits = 0;
321
    static bool double_esc = FALSE;
322
    int *kbinput, keycode, retval = ERR;
323

324
    meta_key = FALSE;
325
    shift_held = FALSE;
326

327
    /* Read in a character. */
328
329
330
331
332
333
    kbinput = get_input(win, 1);

    if (kbinput == NULL && nodelay_mode)
	return 0;

    while (kbinput == NULL)
334
	kbinput = get_input(win, 1);
335

336
337
338
    keycode = *kbinput;
    free(kbinput);

339
340
341
342
343
#ifdef DEBUG
    fprintf(stderr, "before parsing:  keycode = %d, escapes = %d, byte_digits = %d\n",
	keycode, escapes, byte_digits);
#endif

344
345
346
    if (keycode == ERR)
	return ERR;

347
    if (keycode == ESC_CODE) {
348
349
350
351
352
353
354
	/* Increment the escape counter, but trim an overabundance. */
	escapes++;
	if (escapes > 3)
	    escapes = 1;
	/* Take note when an Esc arrived by itself. */
	solitary = (escapes == 1 && key_buffer_len == 0);
	return ERR;
355
356
    }

357
358
359
360
361
362
    switch (escapes) {
	case 0:
	    /* One non-escape: normal input mode. */
	    retval = keycode;
	    break;
	case 1:
363
364
365
	    if (keycode >= 0x80)
		retval = keycode;
	    else if ((keycode != 'O' && keycode != 'o' && keycode != '[') ||
366
			key_buffer_len == 0 || *key_buffer == ESC_CODE) {
367
368
369
370
371
372
373
374
375
		/* One escape followed by a single non-escape:
		 * meta key sequence mode. */
		if (!solitary || (keycode >= 0x20 && keycode < 0x7F))
		    meta_key = TRUE;
		retval = tolower(keycode);
	    } else
		/* One escape followed by a non-escape, and there
		 * are more codes waiting: escape sequence mode. */
		retval = parse_escape_sequence(win, keycode);
376
	    escapes = 0;
377
378
379
	    break;
	case 2:
	    if (double_esc) {
380
381
		/* An "ESC ESC [ X" sequence from Option+arrow, or
		 * an "ESC ESC [ x" sequence from Shift+Alt+arrow. */
382
383
384
385
386
387
388
389
		switch (keycode) {
		    case 'A':
			retval = KEY_HOME;
			break;
		    case 'B':
			retval = KEY_END;
			break;
		    case 'C':
390
			retval = CONTROL_RIGHT;
391
392
			break;
		    case 'D':
393
			retval = CONTROL_LEFT;
394
			break;
395
#ifndef NANO_TINY
396
397
398
399
400
401
402
403
404
405
406
407
		    case 'a':
			retval = shiftaltup;
			break;
		    case 'b':
			retval = shiftaltdown;
			break;
		    case 'c':
			retval = shiftaltright;
			break;
		    case 'd':
			retval = shiftaltleft;
			break;
408
#endif
409
410
411
		}
		double_esc = FALSE;
		escapes = 0;
412
	    } else if (key_buffer_len == 0) {
413
414
415
416
417
418
419
420
421
422
423
424
425
426
		if (('0' <= keycode && keycode <= '2' &&
			byte_digits == 0) || ('0' <= keycode &&
			keycode <= '9' && byte_digits > 0)) {
		    /* Two escapes followed by one or more decimal
		     * digits, and there aren't any other codes
		     * waiting: byte sequence mode.  If the range
		     * of the byte sequence is limited to 2XX (the
		     * first digit is between '0' and '2' and the
		     * others between '0' and '9', interpret it. */
		    int byte;

		    byte_digits++;
		    byte = get_byte_kbinput(keycode);

427
428
		    /* If the decimal byte value is complete, convert it and
		     * put the obtained byte(s) back into the input buffer. */
429
430
431
432
		    if (byte != ERR) {
			char *byte_mb;
			int byte_mb_len, *seq, i;

433
434
			/* Convert the decimal code to one or two bytes. */
			byte_mb = make_mbchar((long)byte, &byte_mb_len);
435

436
			seq = (int *)nmalloc(byte_mb_len * sizeof(int));
437
438
439
440

			for (i = 0; i < byte_mb_len; i++)
			    seq[i] = (unsigned char)byte_mb[i];

441
			/* Insert the byte(s) into the input buffer. */
442
443
444
445
			unget_input(seq, byte_mb_len);

			free(byte_mb);
			free(seq);
446
447
448

			byte_digits = 0;
			escapes = 0;
449
		    }
450
451
452
453
454
455
456
457
458
459
460
461
462
		} else {
		    if (byte_digits == 0)
			/* Two escapes followed by a non-decimal
			 * digit (or a decimal digit that would
			 * create a byte sequence greater than 2XX)
			 * and there aren't any other codes waiting:
			 * control character sequence mode. */
			retval = get_control_kbinput(keycode);
		    else {
			/* An invalid digit in the middle of a byte
			 * sequence: reset the byte sequence counter
			 * and save the code we got as the result. */
			byte_digits = 0;
463
			retval = keycode;
464
		    }
465
		    escapes = 0;
466
467
		}
	    } else if (keycode == '[' && key_buffer_len > 0 &&
468
469
470
			(('A' <= *key_buffer && *key_buffer <= 'D') ||
			('a' <= *key_buffer && *key_buffer <= 'd'))) {
		/* An iTerm2/Eterm/rxvt sequence: ^[ ^[ [ X. */
471
472
473
474
475
		double_esc = TRUE;
	    } else {
		/* Two escapes followed by a non-escape, and there are more
		 * codes waiting: combined meta and escape sequence mode. */
		retval = parse_escape_sequence(win, keycode);
476
477
		meta_key = TRUE;
		escapes = 0;
478
	    }
479
480
	    break;
	case 3:
481
	    if (key_buffer_len == 0)
482
483
484
485
486
487
488
489
490
491
		/* Three escapes followed by a non-escape, and no
		 * other codes are waiting: normal input mode. */
		retval = keycode;
	    else
		/* Three escapes followed by a non-escape, and more
		 * codes are waiting: combined control character and
		 * escape sequence mode.  First interpret the escape
		 * sequence, then the result as a control sequence. */
		retval = get_control_kbinput(
			parse_escape_sequence(win, keycode));
492
	    escapes = 0;
493
494
	    break;
    }
495

496
497
498
    if (retval == ERR)
	return ERR;

499
    if (retval == controlleft)
500
	return CONTROL_LEFT;
501
    else if (retval == controlright)
502
	return CONTROL_RIGHT;
503
    else if (retval == controlup)
504
	return CONTROL_UP;
505
    else if (retval == controldown)
506
	return CONTROL_DOWN;
507
508
509
510
    else if (retval == controlhome)
	return CONTROL_HOME;
    else if (retval == controlend)
	return CONTROL_END;
511
#ifndef NANO_TINY
512
513
    else if (retval == shiftcontrolleft) {
	shift_held = TRUE;
514
	return CONTROL_LEFT;
515
516
    } else if (retval == shiftcontrolright) {
	shift_held = TRUE;
517
	return CONTROL_RIGHT;
518
519
    } else if (retval == shiftcontrolup) {
	shift_held = TRUE;
520
	return CONTROL_UP;
521
522
    } else if (retval == shiftcontroldown) {
	shift_held = TRUE;
523
	return CONTROL_DOWN;
524
525
526
527
528
529
    } else if (retval == shiftcontrolhome) {
	shift_held = TRUE;
	return CONTROL_HOME;
    } else if (retval == shiftcontrolend) {
	shift_held = TRUE;
	return CONTROL_END;
530
531
    } else if (retval == shiftaltleft) {
	shift_held = TRUE;
532
	return KEY_HOME;
533
534
    } else if (retval == shiftaltright) {
	shift_held = TRUE;
535
	return KEY_END;
536
537
    } else if (retval == shiftaltup) {
	shift_held = TRUE;
538
	return KEY_PPAGE;
539
540
    } else if (retval == shiftaltdown) {
	shift_held = TRUE;
541
	return KEY_NPAGE;
542
    }
543
544
#endif

545
#ifdef __linux__
546
    /* When not running under X, check for the bare arrow keys whether
547
548
549
550
     * Shift/Ctrl/Alt are being held together with them. */
    unsigned char modifiers = 6;

    if (console && ioctl(0, TIOCLINUX, &modifiers) >= 0) {
551
552
553
554
555
#ifndef NANO_TINY
	/* Is Shift being held? */
	if (modifiers & 0x01)
	    shift_held = TRUE;
#endif
556
557
	/* Is Ctrl being held? */
	if (modifiers & 0x04) {
558
	    if (retval == KEY_UP)
559
		return CONTROL_UP;
560
	    else if (retval == KEY_DOWN)
561
		return CONTROL_DOWN;
562
	    else if (retval == KEY_LEFT)
563
		return CONTROL_LEFT;
564
	    else if (retval == KEY_RIGHT)
565
		return CONTROL_RIGHT;
566
567
568
569
	    else if (retval == KEY_HOME)
		return CONTROL_HOME;
	    else if (retval == KEY_END)
		return CONTROL_END;
570
	}
571
#ifndef NANO_TINY
572
573
574
	/* Are both Shift and Alt being held? */
	if ((modifiers & 0x09) == 0x09) {
	    if (retval == KEY_UP)
575
		return KEY_PPAGE;
576
	    else if (retval == KEY_DOWN)
577
		return KEY_NPAGE;
578
	    else if (retval == KEY_LEFT)
579
		return KEY_HOME;
580
	    else if (retval == KEY_RIGHT)
581
		return KEY_END;
582
	}
583
#endif
584
    }
585
#endif /* __linux__ */
586

587
    switch (retval) {
588
#ifdef KEY_SLEFT
589
590
	/* Slang doesn't support KEY_SLEFT. */
	case KEY_SLEFT:
591
	    shift_held = TRUE;
592
	    return KEY_LEFT;
593
#endif
594
#ifdef KEY_SRIGHT
595
596
	/* Slang doesn't support KEY_SRIGHT. */
	case KEY_SRIGHT:
597
	    shift_held = TRUE;
598
	    return KEY_RIGHT;
599
#endif
600
#ifdef KEY_SR
601
#ifdef KEY_SUP
602
603
	/* ncurses and Slang don't support KEY_SUP. */
	case KEY_SUP:
604
#endif
605
606
	case KEY_SR:	/* Scroll backward, on Xfce4-terminal. */
	    shift_held = TRUE;
607
	    return KEY_UP;
608
609
#endif
#ifdef KEY_SF
610
#ifdef KEY_SDOWN
611
612
	/* ncurses and Slang don't support KEY_SDOWN. */
	case KEY_SDOWN:
613
#endif
614
615
	case KEY_SF:	/* Scroll forward, on Xfce4-terminal. */
	    shift_held = TRUE;
616
	    return KEY_DOWN;
617
#endif
618
#ifdef KEY_SHOME
619
620
	/* HP-UX 10-11 and Slang don't support KEY_SHOME. */
	case KEY_SHOME:
621
#endif
622
623
	case SHIFT_HOME:
	    shift_held = TRUE;
624
	case KEY_A1:	/* Home (7) on keypad with NumLock off. */
625
	    return KEY_HOME;
626
#ifdef KEY_SEND
627
628
	/* HP-UX 10-11 and Slang don't support KEY_SEND. */
	case KEY_SEND:
629
#endif
630
631
	case SHIFT_END:
	    shift_held = TRUE;
632
	case KEY_C1:	/* End (1) on keypad with NumLock off. */
633
	    return KEY_END;
634
#ifndef NANO_TINY
635
636
637
638
#ifdef KEY_SPREVIOUS
	case KEY_SPREVIOUS:
#endif
	case SHIFT_PAGEUP:	/* Fake key, from Shift+Alt+Up. */
639
640
	    shift_held = TRUE;
#endif
641
	case KEY_A3:	/* PageUp (9) on keypad with NumLock off. */
642
	    return KEY_PPAGE;
643
#ifndef NANO_TINY
644
645
646
#ifdef KEY_SNEXT
	case KEY_SNEXT:
#endif
647
648
649
	case SHIFT_PAGEDOWN:	/* Fake key, from Shift+Alt+Down. */
	    shift_held = TRUE;
#endif
650
	case KEY_C3:	/* PageDown (3) on keypad with NumLock off. */
651
	    return KEY_NPAGE;
652
#ifdef KEY_SDC
653
654
	/* Slang doesn't support KEY_SDC. */
	case KEY_SDC:
655
#endif
656
	case DEL_CODE:
657
	    if (ISSET(REBIND_DELETE))
658
		return the_code_for(do_delete, KEY_DC);
659
	    else
660
		return KEY_BACKSPACE;
661
#ifdef KEY_SIC
662
663
	/* Slang doesn't support KEY_SIC. */
	case KEY_SIC:
664
	    return the_code_for(do_insertfile_void, KEY_IC);
665
#endif
666
#ifdef KEY_SBEG
667
668
	/* Slang doesn't support KEY_SBEG. */
	case KEY_SBEG:
669
#endif
670
#ifdef KEY_BEG
671
672
	/* Slang doesn't support KEY_BEG. */
	case KEY_BEG:
673
#endif
674
675
	case KEY_B2:	/* Center (5) on keypad with NumLock off. */
	    return ERR;
676
#ifdef KEY_CANCEL
677
#ifdef KEY_SCANCEL
678
679
	/* Slang doesn't support KEY_SCANCEL. */
	case KEY_SCANCEL:
680
#endif
681
682
	/* Slang doesn't support KEY_CANCEL. */
	case KEY_CANCEL:
683
	    return the_code_for(do_cancel, 0x03);
684
#endif
685
#ifdef KEY_SUSPEND
686
#ifdef KEY_SSUSPEND
687
688
	/* Slang doesn't support KEY_SSUSPEND. */
	case KEY_SSUSPEND:
689
#endif
690
691
	/* Slang doesn't support KEY_SUSPEND. */
	case KEY_SUSPEND:
692
	    return the_code_for(do_suspend_void, KEY_SUSPEND);
693
694
#endif
#ifdef PDCURSES
695
696
697
698
699
700
701
	case KEY_SHIFT_L:
	case KEY_SHIFT_R:
	case KEY_CONTROL_L:
	case KEY_CONTROL_R:
	case KEY_ALT_L:
	case KEY_ALT_R:
	    return ERR;
702
#endif
703
704
#ifdef KEY_RESIZE
	/* Slang and SunOS 5.7-5.9 don't support KEY_RESIZE. */
705
706
	case KEY_RESIZE:
	    return ERR;
707
#endif
708
    }
709

710
711
712
    return retval;
}

713
/* Translate escape sequences, most of which correspond to extended
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
714
 * keypad values, into their corresponding key values.  These sequences
715
716
 * are generated when the keypad doesn't support the needed keys.
 * Assume that Escape has already been read in. */
717
int convert_sequence(const int *seq, size_t seq_len)
718
{
719
    if (seq_len > 1) {
720
	switch (seq[0]) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
721
	    case 'O':
722
		switch (seq[1]) {
723
		    case '1':
724
725
			if (seq_len > 4  && seq[2] == ';') {

726
727
	switch (seq[3]) {
	    case '2':
728
729
730
731
732
		switch (seq[4]) {
		    case 'A': /* Esc O 1 ; 2 A == Shift-Up on Terminal. */
		    case 'B': /* Esc O 1 ; 2 B == Shift-Down on Terminal. */
		    case 'C': /* Esc O 1 ; 2 C == Shift-Right on Terminal. */
		    case 'D': /* Esc O 1 ; 2 D == Shift-Left on Terminal. */
733
			shift_held = TRUE;
734
735
736
737
738
739
740
741
742
743
			return arrow_from_abcd(seq[4]);
		    case 'P': /* Esc O 1 ; 2 P == F13 on Terminal. */
			return KEY_F(13);
		    case 'Q': /* Esc O 1 ; 2 Q == F14 on Terminal. */
			return KEY_F(14);
		    case 'R': /* Esc O 1 ; 2 R == F15 on Terminal. */
			return KEY_F(15);
		    case 'S': /* Esc O 1 ; 2 S == F16 on Terminal. */
			return KEY_F(16);
		}
744
745
		break;
	    case '5':
746
747
748
749
750
751
752
753
754
755
		switch (seq[4]) {
		    case 'A': /* Esc O 1 ; 5 A == Ctrl-Up on Terminal. */
			return CONTROL_UP;
		    case 'B': /* Esc O 1 ; 5 B == Ctrl-Down on Terminal. */
			return CONTROL_DOWN;
		    case 'C': /* Esc O 1 ; 5 C == Ctrl-Right on Terminal. */
			return CONTROL_RIGHT;
		    case 'D': /* Esc O 1 ; 5 D == Ctrl-Left on Terminal. */
			return CONTROL_LEFT;
		}
756
757
		break;
	}
758

759
760
			}
			break;
761
		    case '2':
762
			if (seq_len >= 3) {
763
			    switch (seq[2]) {
764
				case 'P': /* Esc O 2 P == F13 on xterm. */
765
				    return KEY_F(13);
766
				case 'Q': /* Esc O 2 Q == F14 on xterm. */
767
				    return KEY_F(14);
768
				case 'R': /* Esc O 2 R == F15 on xterm. */
769
				    return KEY_F(15);
770
				case 'S': /* Esc O 2 S == F16 on xterm. */
771
				    return KEY_F(16);
772
773
			    }
			}
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
774
			break;
775
		    case 'A': /* Esc O A == Up on VT100/VT320/xterm. */
776
777
778
		    case 'B': /* Esc O B == Down on VT100/VT320/xterm. */
		    case 'C': /* Esc O C == Right on VT100/VT320/xterm. */
		    case 'D': /* Esc O D == Left on VT100/VT320/xterm. */
779
			return arrow_from_abcd(seq[1]);
780
781
		    case 'E': /* Esc O E == Center (5) on numeric keypad
			       * with NumLock off on xterm. */
782
			return KEY_B2;
783
		    case 'F': /* Esc O F == End on xterm/Terminal. */
784
			return KEY_END;
785
		    case 'H': /* Esc O H == Home on xterm/Terminal. */
786
			return KEY_HOME;
787
		    case 'M': /* Esc O M == Enter on numeric keypad with
788
			       * NumLock off on VT100/VT220/VT320/xterm/
789
			       * rxvt/Eterm. */
790
			return KEY_ENTER;
791
		    case 'P': /* Esc O P == F1 on VT100/VT220/VT320/Mach
792
			       * console. */
793
			return KEY_F(1);
794
		    case 'Q': /* Esc O Q == F2 on VT100/VT220/VT320/Mach
795
			       * console. */
796
			return KEY_F(2);
797
		    case 'R': /* Esc O R == F3 on VT100/VT220/VT320/Mach
798
			       * console. */
799
			return KEY_F(3);
800
		    case 'S': /* Esc O S == F4 on VT100/VT220/VT320/Mach
801
			       * console. */
802
			return KEY_F(4);
803
		    case 'T': /* Esc O T == F5 on Mach console. */
804
			return KEY_F(5);
805
		    case 'U': /* Esc O U == F6 on Mach console. */
806
			return KEY_F(6);
807
		    case 'V': /* Esc O V == F7 on Mach console. */
808
			return KEY_F(7);
809
		    case 'W': /* Esc O W == F8 on Mach console. */
810
			return KEY_F(8);
811
		    case 'X': /* Esc O X == F9 on Mach console. */
812
			return KEY_F(9);
813
		    case 'Y': /* Esc O Y == F10 on Mach console. */
814
			return KEY_F(10);
815
		    case 'a': /* Esc O a == Ctrl-Up on rxvt. */
816
			return CONTROL_UP;
817
		    case 'b': /* Esc O b == Ctrl-Down on rxvt. */
818
			return CONTROL_DOWN;
819
		    case 'c': /* Esc O c == Ctrl-Right on rxvt. */
820
			return CONTROL_RIGHT;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
821
		    case 'd': /* Esc O d == Ctrl-Left on rxvt. */
822
			return CONTROL_LEFT;
823
		    case 'j': /* Esc O j == '*' on numeric keypad with
824
			       * NumLock off on VT100/VT220/VT320/xterm/
825
			       * rxvt/Eterm/Terminal. */
826
			return '*';
827
		    case 'k': /* Esc O k == '+' on numeric keypad with
828
			       * NumLock off on VT100/VT220/VT320/xterm/
829
			       * rxvt/Eterm/Terminal. */
830
			return '+';
831
		    case 'l': /* Esc O l == ',' on numeric keypad with
832
			       * NumLock off on VT100/VT220/VT320/xterm/
833
			       * rxvt/Eterm/Terminal. */
834
			return ',';
835
		    case 'm': /* Esc O m == '-' on numeric keypad with
836
			       * NumLock off on VT100/VT220/VT320/xterm/
837
			       * rxvt/Eterm/Terminal. */
838
			return '-';
839
		    case 'n': /* Esc O n == Delete (.) on numeric keypad
840
			       * with NumLock off on VT100/VT220/VT320/
841
			       * xterm/rxvt/Eterm/Terminal. */
842
			return KEY_DC;
843
		    case 'o': /* Esc O o == '/' on numeric keypad with
844
			       * NumLock off on VT100/VT220/VT320/xterm/
845
			       * rxvt/Eterm/Terminal. */
846
			return '/';
847
		    case 'p': /* Esc O p == Insert (0) on numeric keypad
848
			       * with NumLock off on VT100/VT220/VT320/
849
			       * rxvt/Eterm/Terminal. */
850
			return KEY_IC;
851
		    case 'q': /* Esc O q == End (1) on numeric keypad
852
			       * with NumLock off on VT100/VT220/VT320/
853
			       * rxvt/Eterm/Terminal. */
854
			return KEY_END;
855
		    case 'r': /* Esc O r == Down (2) on numeric keypad
856
			       * with NumLock off on VT100/VT220/VT320/
857
			       * rxvt/Eterm/Terminal. */
858
			return KEY_DOWN;
859
		    case 's': /* Esc O s == PageDown (3) on numeric
860
			       * keypad with NumLock off on VT100/VT220/
861
			       * VT320/rxvt/Eterm/Terminal. */
862
			return KEY_NPAGE;
863
		    case 't': /* Esc O t == Left (4) on numeric keypad
864
			       * with NumLock off on VT100/VT220/VT320/
865
			       * rxvt/Eterm/Terminal. */
866
			return KEY_LEFT;
867
		    case 'u': /* Esc O u == Center (5) on numeric keypad
868
869
			       * with NumLock off on VT100/VT220/VT320/
			       * rxvt/Eterm. */
870
			return KEY_B2;
871
		    case 'v': /* Esc O v == Right (6) on numeric keypad
872
			       * with NumLock off on VT100/VT220/VT320/
873
			       * rxvt/Eterm/Terminal. */
874
			return KEY_RIGHT;
875
		    case 'w': /* Esc O w == Home (7) on numeric keypad
876
			       * with NumLock off on VT100/VT220/VT320/
877
			       * rxvt/Eterm/Terminal. */
878
			return KEY_HOME;
879
		    case 'x': /* Esc O x == Up (8) on numeric keypad
880
			       * with NumLock off on VT100/VT220/VT320/
881
			       * rxvt/Eterm/Terminal. */
882
			return KEY_UP;
883
		    case 'y': /* Esc O y == PageUp (9) on numeric keypad
884
			       * with NumLock off on VT100/VT220/VT320/
885
			       * rxvt/Eterm/Terminal. */
886
			return KEY_PPAGE;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
887
888
889
		}
		break;
	    case 'o':
890
		switch (seq[1]) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
891
		    case 'a': /* Esc o a == Ctrl-Up on Eterm. */
892
			return CONTROL_UP;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
893
		    case 'b': /* Esc o b == Ctrl-Down on Eterm. */
894
			return CONTROL_DOWN;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
895
		    case 'c': /* Esc o c == Ctrl-Right on Eterm. */
896
			return CONTROL_RIGHT;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
897
		    case 'd': /* Esc o d == Ctrl-Left on Eterm. */
898
			return CONTROL_LEFT;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
899
900
901
		}
		break;
	    case '[':
902
		switch (seq[1]) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
903
		    case '1':
904
			if (seq_len > 3 && seq[3] == '~') {
905
			    switch (seq[2]) {
906
				case '1': /* Esc [ 1 1 ~ == F1 on rxvt/Eterm. */
907
				    return KEY_F(1);
908
				case '2': /* Esc [ 1 2 ~ == F2 on rxvt/Eterm. */
909
				    return KEY_F(2);
910
				case '3': /* Esc [ 1 3 ~ == F3 on rxvt/Eterm. */
911
				    return KEY_F(3);
912
				case '4': /* Esc [ 1 4 ~ == F4 on rxvt/Eterm. */
913
				    return KEY_F(4);
914
915
				case '5': /* Esc [ 1 5 ~ == F5 on xterm/
					   * rxvt/Eterm. */
916
				    return KEY_F(5);
917
				case '7': /* Esc [ 1 7 ~ == F6 on
918
919
					   * VT220/VT320/Linux console/
					   * xterm/rxvt/Eterm. */
920
				    return KEY_F(6);
921
				case '8': /* Esc [ 1 8 ~ == F7 on
922
923
					   * VT220/VT320/Linux console/
					   * xterm/rxvt/Eterm. */
924
				    return KEY_F(7);
925
				case '9': /* Esc [ 1 9 ~ == F8 on
926
927
					   * VT220/VT320/Linux console/
					   * xterm/rxvt/Eterm. */
928
				    return KEY_F(8);
929
930
931
			    }
			} else if (seq_len > 4 && seq[2] == ';') {

932
	switch (seq[3]) {
933
	    case '2':
934
935
936
937
938
		switch (seq[4]) {
		    case 'A': /* Esc [ 1 ; 2 A == Shift-Up on xterm. */
		    case 'B': /* Esc [ 1 ; 2 B == Shift-Down on xterm. */
		    case 'C': /* Esc [ 1 ; 2 C == Shift-Right on xterm. */
		    case 'D': /* Esc [ 1 ; 2 D == Shift-Left on xterm. */
939
			shift_held = TRUE;
940
941
			return arrow_from_abcd(seq[4]);
		}
942
		break;
943
944
945
946
947
948
949
950
951
952
#ifndef NANO_TINY
	    case '4':
		/* When the arrow keys are held together with Shift+Meta,
		 * act as if they are Home/End/PgUp/PgDown with Shift. */
		switch (seq[4]) {
		    case 'A': /* Esc [ 1 ; 4 A == Shift-Alt-Up on xterm. */
			return SHIFT_PAGEUP;
		    case 'B': /* Esc [ 1 ; 4 B == Shift-Alt-Down on xterm. */
			return SHIFT_PAGEDOWN;
		    case 'C': /* Esc [ 1 ; 4 C == Shift-Alt-Right on xterm. */
953
			return SHIFT_END;
954
		    case 'D': /* Esc [ 1 ; 4 D == Shift-Alt-Left on xterm. */
955
			return SHIFT_HOME;
956
957
958
		}
		break;
#endif
959
	    case '5':
960
961
962
963
964
965
966
967
968
		switch (seq[4]) {
		    case 'A': /* Esc [ 1 ; 5 A == Ctrl-Up on xterm. */
			return CONTROL_UP;
		    case 'B': /* Esc [ 1 ; 5 B == Ctrl-Down on xterm. */
			return CONTROL_DOWN;
		    case 'C': /* Esc [ 1 ; 5 C == Ctrl-Right on xterm. */
			return CONTROL_RIGHT;
		    case 'D': /* Esc [ 1 ; 5 D == Ctrl-Left on xterm. */
			return CONTROL_LEFT;
969
970
971
972
		    case 'F': /* Esc [ 1 ; 5 F == Ctrl-End on xterm. */
			return CONTROL_END;
		    case 'H': /* Esc [ 1 ; 5 H == Ctrl-Home on xterm. */
			return CONTROL_HOME;
973
		}
974
		break;
975
976
977
978
979
980
981
982
983
984
985
#ifndef NANO_TINY
	    case '6':
		switch (seq[4]) {
		    case 'A': /* Esc [ 1 ; 6 A == Shift-Ctrl-Up on xterm. */
			return shiftcontrolup;
		    case 'B': /* Esc [ 1 ; 6 B == Shift-Ctrl-Down on xterm. */
			return shiftcontroldown;
		    case 'C': /* Esc [ 1 ; 6 C == Shift-Ctrl-Right on xterm. */
			return shiftcontrolright;
		    case 'D': /* Esc [ 1 ; 6 D == Shift-Ctrl-Left on xterm. */
			return shiftcontrolleft;
986
987
988
989
		    case 'F': /* Esc [ 1 ; 6 F == Shift-Ctrl-End on xterm. */
			return shiftcontrolend;
		    case 'H': /* Esc [ 1 ; 6 H == Shift-Ctrl-Home on xterm. */
			return shiftcontrolhome;
990
991
992
		}
		break;
#endif
993
	}
994
995
996
997

			} else if (seq_len > 2 && seq[2] == '~')
			    /* Esc [ 1 ~ == Home on VT320/Linux console. */
			    return KEY_HOME;
998
999
			break;
		    case '2':
1000
			if (seq_len > 3 && seq[3] == '~') {
1001
			    switch (seq[2]) {
1002
1003
				case '0': /* Esc [ 2 0 ~ == F9 on VT220/VT320/
					   * Linux console/xterm/rxvt/Eterm. */
1004
				    return KEY_F(9);
1005
1006
				case '1': /* Esc [ 2 1 ~ == F10 on VT220/VT320/
					   * Linux console/xterm/rxvt/Eterm. */
1007
				    return KEY_F(10);
1008
1009
				case '3': /* Esc [ 2 3 ~ == F11 on VT220/VT320/
					   * Linux console/xterm/rxvt/Eterm. */
1010
				    return KEY_F(11);
1011
1012
				case '4': /* Esc [ 2 4 ~ == F12 on VT220/VT320/
					   * Linux console/xterm/rxvt/Eterm. */
1013
				    return KEY_F(12);
1014
1015
				case '5': /* Esc [ 2 5 ~ == F13 on VT220/VT320/
					   * Linux console/rxvt/Eterm. */
1016
				    return KEY_F(13);
1017
1018
				case '6': /* Esc [ 2 6 ~ == F14 on VT220/VT320/
					   * Linux console/rxvt/Eterm. */
1019
				    return KEY_F(14);
1020
1021
				case '8': /* Esc [ 2 8 ~ == F15 on VT220/VT320/
					   * Linux console/rxvt/Eterm. */
1022
				    return KEY_F(15);
1023
1024
				case '9': /* Esc [ 2 9 ~ == F16 on VT220/VT320/
					   * Linux console/rxvt/Eterm. */
1025
				    return KEY_F(16);
1026
			    }
1027
1028
1029
1030
			} else if (seq_len > 2 && seq[2] == '~')
			    /* Esc [ 2 ~ == Insert on VT220/VT320/
			     * Linux console/xterm/Terminal. */
			    return KEY_IC;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1031
			break;
1032
		    case '3': /* Esc [ 3 ~ == Delete on VT220/VT320/
1033
			       * Linux console/xterm/Terminal. */
1034
1035
1036
			if (seq_len > 2 && seq[2] == '~')
			    return KEY_DC;
			break;
1037
		    case '4': /* Esc [ 4 ~ == End on VT220/VT320/Linux
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1038
			       * console/xterm. */
1039
1040
1041
			if (seq_len > 2 && seq[2] == '~')
			    return KEY_END;
			break;
1042
		    case '5': /* Esc [ 5 ~ == PageUp on VT220/VT320/
1043
1044
			       * Linux console/xterm/Terminal;
			       * Esc [ 5 ^ == PageUp on Eterm. */
1045
1046
1047
			if (seq_len > 2 && (seq[2] == '~' || seq[2] == '^'))
			    return KEY_PPAGE;
			break;
1048
		    case '6': /* Esc [ 6 ~ == PageDown on VT220/VT320/
1049
			       * Linux console/xterm/Terminal;
1050
			       * Esc [ 6 ^ == PageDown on Eterm. */
1051
1052
1053
			if (seq_len > 2 && (seq[2] == '~' || seq[2] == '^'))
			    return KEY_NPAGE;
			break;
1054
1055
1056
1057
		    case '7': /* Esc [ 7 ~ == Home on Eterm/rxvt;
			       * Esc [ 7 $ == Shift-Home on Eterm/rxvt;
			       * Esc [ 7 ^ == Control-Home on Eterm/rxvt;
			       * Esc [ 7 @ == Shift-Control-Home on same. */
1058
1059
1060
1061
			if (seq_len > 2 && seq[2] == '~')
			    return KEY_HOME;
			else if (seq_len > 2 && seq[2] == '$')
			    return SHIFT_HOME;
1062
1063
1064
1065
1066
1067
			else if (seq_len > 2 && seq[2] == '^')
			    return CONTROL_HOME;
#ifndef NANO_TINY
			else if (seq_len > 2 && seq[2] == '@')
			    return shiftcontrolhome;
#endif
1068
			break;
1069
1070
1071
1072
		    case '8': /* Esc [ 8 ~ == End on Eterm/rxvt;
			       * Esc [ 8 $ == Shift-End on Eterm/rxvt;
			       * Esc [ 8 ^ == Control-End on Eterm/rxvt;
			       * Esc [ 8 @ == Shift-Control-End on same. */
1073
1074
1075
1076
			if (seq_len > 2 && seq[2] == '~')
			    return KEY_END;
			else if (seq_len > 2 && seq[2] == '$')
			    return SHIFT_END;
1077
1078
1079
1080
1081
1082
			else if (seq_len > 2 && seq[2] == '^')
			    return CONTROL_END;
#ifndef NANO_TINY
			else if (seq_len > 2 && seq[2] == '@')
			    return shiftcontrolend;
#endif
1083
			break;
1084
		    case '9': /* Esc [ 9 == Delete on Mach console. */
1085
			return KEY_DC;
1086
		    case '@': /* Esc [ @ == Insert on Mach console. */
1087
			return KEY_IC;
1088
		    case 'A': /* Esc [ A == Up on ANSI/VT220/Linux
1089
			       * console/FreeBSD console/Mach console/
1090
			       * rxvt/Eterm/Terminal. */
1091
		    case 'B': /* Esc [ B == Down on ANSI/VT220/Linux
1092
			       * console/FreeBSD console/Mach console/
1093
			       * rxvt/Eterm/Terminal. */
1094
		    case 'C': /* Esc [ C == Right on ANSI/VT220/Linux
1095
			       * console/FreeBSD console/Mach console/
1096
			       * rxvt/Eterm/Terminal. */
1097
		    case 'D': /* Esc [ D == Left on ANSI/VT220/Linux
1098
			       * console/FreeBSD console/Mach console/
1099
			       * rxvt/Eterm/Terminal. */
1100
			return arrow_from_abcd(seq[1]);
1101
		    case 'E': /* Esc [ E == Center (5) on numeric keypad
1102
1103
			       * with NumLock off on FreeBSD console/
			       * Terminal. */
1104
			return KEY_B2;
1105
		    case 'F': /* Esc [ F == End on FreeBSD console/Eterm. */
1106
			return KEY_END;
1107
		    case 'G': /* Esc [ G == PageDown on FreeBSD console. */
1108
			return KEY_NPAGE;
1109
		    case 'H': /* Esc [ H == Home on ANSI/VT220/FreeBSD
1110
			       * console/Mach console/Eterm. */
1111
			return KEY_HOME;
1112
		    case 'I': /* Esc [ I == PageUp on FreeBSD console. */
1113
			return KEY_PPAGE;
1114
		    case 'L': /* Esc [ L == Insert on ANSI/FreeBSD console. */
1115
			return KEY_IC;
1116
		    case 'M': /* Esc [ M == F1 on FreeBSD console. */
1117
			return KEY_F(1);
1118
		    case 'N': /* Esc [ N == F2 on FreeBSD console. */
1119
			return KEY_F(2);
1120
		    case 'O':
1121
			if (seq_len > 2) {
1122
			    switch (seq[2]) {
1123
				case 'P': /* Esc [ O P == F1 on xterm. */
1124
				    return KEY_F(1);
1125
				case 'Q': /* Esc [ O Q == F2 on xterm. */
1126
				    return KEY_F(2);
1127
				case 'R': /* Esc [ O R == F3 on xterm. */
1128
				    return KEY_F(3);
1129
				case 'S': /* Esc [ O S == F4 on xterm. */
1130
				    return KEY_F(4);
1131
			    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1132
			} else
1133
			    /* Esc [ O == F3 on FreeBSD console. */
1134
			    return KEY_F(3);
1135
1136
			break;
		    case 'P': /* Esc [ P == F4 on FreeBSD console. */
1137
			return KEY_F(4);
1138
		    case 'Q': /* Esc [ Q == F5 on FreeBSD console. */
1139
			return KEY_F(5);
1140
		    case 'R': /* Esc [ R == F6 on FreeBSD console. */
1141
			return KEY_F(6);
1142
		    case 'S': /* Esc [ S == F7 on FreeBSD console. */
1143
			return KEY_F(7);
1144
		    case 'T': /* Esc [ T == F8 on FreeBSD console. */
1145
			return KEY_F(8);
1146
		    case 'U': /* Esc [ U == PageDown on Mach console. */
1147
			return KEY_NPAGE;
1148
		    case 'V': /* Esc [ V == PageUp on Mach console. */
1149
			return KEY_PPAGE;
1150
		    case 'W': /* Esc [ W == F11 on FreeBSD console. */
1151
			return KEY_F(11);
1152
		    case 'X': /* Esc [ X == F12 on FreeBSD console. */
1153
			return KEY_F(12);
1154
		    case 'Y': /* Esc [ Y == End on Mach console. */
1155
			return KEY_END;
1156
		    case 'Z': /* Esc [ Z == F14 on FreeBSD console. */
1157
			return KEY_F(14);
1158
		    case 'a': /* Esc [ a == Shift-Up on rxvt/Eterm. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1159
		    case 'b': /* Esc [ b == Shift-Down on rxvt/Eterm. */
1160
		    case 'c': /* Esc [ c == Shift-Right on rxvt/Eterm. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1161
		    case 'd': /* Esc [ d == Shift-Left on rxvt/Eterm. */
1162
			shift_held = TRUE;
1163
			return arrow_from_abcd(seq[1]);
1164
		    case '[':
1165
			if (seq_len > 2 ) {
1166
			    switch (seq[2]) {
1167
1168
				case 'A': /* Esc [ [ A == F1 on Linux
					   * console. */
1169
				    return KEY_F(1);
1170
1171
				case 'B': /* Esc [ [ B == F2 on Linux
					   * console. */
1172
				    return KEY_F(2);
1173
1174
				case 'C': /* Esc [ [ C == F3 on Linux
					   * console. */
1175
				    return KEY_F(3);
1176
1177
				case 'D': /* Esc [ [ D == F4 on Linux
					   * console. */
1178
				    return KEY_F(4);
1179
1180
				case 'E': /* Esc [ [ E == F5 on Linux
					   * console. */
1181
				    return KEY_F(5);
1182
1183
			    }
			}
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1184
1185
1186
1187
			break;
		}
		break;
	}
1188
1189
    }

1190
    return ERR;
1191
1192
}

1193
1194
/* Return the equivalent arrow-key value for the first four letters
 * in the alphabet, common to many escape sequences. */
1195
int arrow_from_abcd(int kbinput)
1196
1197
1198
{
    switch (tolower(kbinput)) {
	case 'a':
1199
	    return KEY_UP;
1200
	case 'b':
1201
	    return KEY_DOWN;
1202
	case 'c':
1203
	    return KEY_RIGHT;
1204
	case 'd':
1205
	    return KEY_LEFT;
1206
1207
1208
1209
1210
	default:
	    return ERR;
    }
}

1211
/* Interpret the escape sequence in the keystroke buffer, the first
1212
1213
 * character of which is kbinput.  Assume that the keystroke buffer
 * isn't empty, and that the initial escape has already been read in. */
1214
int parse_escape_sequence(WINDOW *win, int kbinput)
1215
1216
1217
1218
1219
1220
1221
1222
{
    int retval, *seq;
    size_t seq_len;

    /* Put back the non-escape character, get the complete escape
     * sequence, translate the sequence into its corresponding key
     * value, and save that as the result. */
    unget_input(&kbinput, 1);
1223
    seq_len = key_buffer_len;
1224
    seq = get_input(NULL, seq_len);
1225
    retval = convert_sequence(seq, seq_len);
1226
1227
1228

    free(seq);

1229
    /* If we got an unrecognized escape sequence, notify the user. */
1230
    if (retval == ERR) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1231
	if (win == edit) {
1232
1233
	    /* TRANSLATORS: This refers to a sequence of escape codes
	     * (from the keyboard) that nano does not know about. */
1234
	    statusline(ALERT, _("Unknown sequence"));
1235
	    suppress_cursorpos = FALSE;
1236
	    lastmessage = HUSH;
1237
	    if (currmenu == MMAIN) {
1238
		place_the_cursor(TRUE);
1239
1240
		curs_set(1);
	    }
1241
1242
1243
	}
    }

1244
#ifdef DEBUG
1245
1246
    fprintf(stderr, "parse_escape_sequence(): kbinput = %d, seq_len = %lu, retval = %d\n",
		kbinput, (unsigned long)seq_len, retval);
1247
1248
1249
1250
1251
#endif

    return retval;
}

1252
1253
/* Translate a byte sequence: turn a three-digit decimal number (from
 * 000 to 255) into its corresponding byte value. */
1254
int get_byte_kbinput(int kbinput)
1255
{
1256
    static int byte_digits = 0, byte = 0;
1257
    int retval = ERR;
1258

1259
1260
    /* Increment the byte digit counter. */
    byte_digits++;
1261

1262
    switch (byte_digits) {
1263
	case 1:
1264
1265
	    /* First digit: This must be from zero to two.  Put it in
	     * the 100's position of the byte sequence holder. */
1266
	    if ('0' <= kbinput && kbinput <= '2')
1267
		byte = (kbinput - '0') * 100;
1268
	    else
1269
1270
		/* This isn't the start of a byte sequence.  Return this
		 * character as the result. */
1271
1272
1273
		retval = kbinput;
	    break;
	case 2:
1274
1275
1276
1277
	    /* Second digit: This must be from zero to five if the first
	     * was two, and may be any decimal value if the first was
	     * zero or one.  Put it in the 10's position of the byte
	     * sequence holder. */
1278
1279
1280
	    if (('0' <= kbinput && kbinput <= '5') || (byte < 200 &&
		'6' <= kbinput && kbinput <= '9'))
		byte += (kbinput - '0') * 10;
1281
	    else
1282
1283
		/* This isn't the second digit of a byte sequence.
		 * Return this character as the result. */
1284
1285
1286
		retval = kbinput;
	    break;
	case 3:
1287
	    /* Third digit: This must be from zero to five if the first
1288
1289
1290
	     * was two and the second was five, and may be any decimal
	     * value otherwise.  Put it in the 1's position of the byte
	     * sequence holder. */
1291
1292
	    if (('0' <= kbinput && kbinput <= '5') || (byte < 250 &&
		'6' <= kbinput && kbinput <= '9')) {
1293
		byte += kbinput - '0';
1294
		/* The byte sequence is complete. */
1295
		retval = byte;
1296
	    } else
1297
1298
		/* This isn't the third digit of a byte sequence.
		 * Return this character as the result. */
1299
1300
		retval = kbinput;
	    break;
1301
	default:
1302
1303
1304
	    /* If there are more than three digits, return this
	     * character as the result.  (Maybe we should produce an
	     * error instead?) */
1305
1306
1307
1308
1309
1310
1311
1312
	    retval = kbinput;
	    break;
    }

    /* If we have a result, reset the byte digit counter and the byte
     * sequence holder. */
    if (retval != ERR) {
	byte_digits = 0;
1313
	byte = 0;
1314
1315
1316
    }

#ifdef DEBUG
1317
    fprintf(stderr, "get_byte_kbinput(): kbinput = %d, byte_digits = %d, byte = %d, retval = %d\n", kbinput, byte_digits, byte, retval);
1318
1319
1320
1321
1322
#endif

    return retval;
}

1323
#ifdef ENABLE_UTF8
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1324
/* If the character in kbinput is a valid hexadecimal digit, multiply it
1325
 * by factor and add the result to uni, and return ERR to signify okay. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1326
1327
1328
1329
1330
1331
1332
long add_unicode_digit(int kbinput, long factor, long *uni)
{
    if ('0' <= kbinput && kbinput <= '9')
	*uni += (kbinput - '0') * factor;
    else if ('a' <= tolower(kbinput) && tolower(kbinput) <= 'f')
	*uni += (tolower(kbinput) - 'a' + 10) * factor;
    else
1333
1334
	/* The character isn't hexadecimal; give it as the result. */
	return (long)kbinput;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1335

1336
    return ERR;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1337
1338
}

1339
/* Translate a Unicode sequence: turn a six-digit hexadecimal number
1340
 * (from 000000 to 10FFFF, case-insensitive) into its corresponding
1341
 * multibyte value. */
1342
long get_unicode_kbinput(WINDOW *win, int kbinput)
1343
{
1344
1345
1346
    static int uni_digits = 0;
    static long uni = 0;
    long retval = ERR;
1347

1348
    /* Increment the Unicode digit counter. */
1349
    uni_digits++;
1350

1351
    switch (uni_digits) {
1352
	case 1:
1353
1354
1355
1356
	    /* The first digit must be zero or one.  Put it in the
	     * 0x100000's position of the Unicode sequence holder.
	     * Otherwise, return the character itself as the result. */
	    if (kbinput == '0' || kbinput == '1')
1357
		uni = (kbinput - '0') * 0x100000;
1358
1359
1360
1361
	    else
		retval = kbinput;
	    break;
	case 2:
1362
1363
1364
	    /* The second digit must be zero if the first was one, but
	     * may be any hexadecimal value if the first was zero. */
	    if (kbinput == '0' || uni == 0)
1365
		retval = add_unicode_digit(kbinput, 0x10000, &uni);
1366
1367
1368
1369
	    else
		retval = kbinput;
	    break;
	case 3:
1370
	    /* Later digits may be any hexadecimal value. */
1371
	    retval = add_unicode_digit(kbinput, 0x1000, &uni);
1372
	    break;
1373
	case 4:
1374
	    retval = add_unicode_digit(kbinput, 0x100, &uni);
1375
	    break;
1376
	case 5:
1377
	    retval = add_unicode_digit(kbinput, 0x10, &uni);
1378
	    break;
1379
	case 6:
1380
	    retval = add_unicode_digit(kbinput, 0x1, &uni);
1381
1382
	    /* If also the sixth digit was a valid hexadecimal value, then
	     * the Unicode sequence is complete, so return it. */
1383
	    if (retval == ERR)
1384
		retval = uni;
1385
1386
	    break;
    }
1387

1388
    /* Show feedback only when editing, not when at a prompt. */
1389
    if (retval == ERR && win == edit) {
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
	char partial[7] = "......";

	/* Construct the partial result, right-padding it with dots. */
	snprintf(partial, uni_digits + 1, "%06lX", uni);
	partial[uni_digits] = '.';

	/* TRANSLATORS: This is shown while a six-digit hexadecimal
	 * Unicode character code (%s) is being typed in. */
	statusline(HUSH, _("Unicode Input: %s"), partial);
    }
1400

1401
#ifdef DEBUG
1402
1403
    fprintf(stderr, "get_unicode_kbinput(): kbinput = %d, uni_digits = %d, uni = %ld, retval = %ld\n",
						kbinput, uni_digits, uni, retval);
1404
1405
#endif

1406
1407
1408
1409
    /* If we have an end result, reset the Unicode digit counter. */
    if (retval != ERR)
	uni_digits = 0;

1410
1411
    return retval;
}
1412
#endif /* ENABLE_UTF8 */
1413

1414
1415
1416
1417
1418
1419
/* Translate a control character sequence: turn an ASCII non-control
 * character into its corresponding control character. */
int get_control_kbinput(int kbinput)
{
    int retval;

1420
    /* Ctrl-Space (Ctrl-2, Ctrl-@, Ctrl-`) */
1421
    if (kbinput == ' ' || kbinput == '2')
1422
	retval = 0;
1423
1424
    /* Ctrl-/ (Ctrl-7, Ctrl-_) */
    else if (kbinput == '/')
1425
	retval = 31;
1426
    /* Ctrl-3 (Ctrl-[, Esc) to Ctrl-7 (Ctrl-/, Ctrl-_) */
1427
1428
1429
    else if ('3' <= kbinput && kbinput <= '7')
	retval = kbinput - 24;
    /* Ctrl-8 (Ctrl-?) */
1430
    else if (kbinput == '8' || kbinput == '?')
1431
	retval = DEL_CODE;
1432
1433
    /* Ctrl-@ (Ctrl-Space, Ctrl-2, Ctrl-`) to Ctrl-_ (Ctrl-/, Ctrl-7) */
    else if ('@' <= kbinput && kbinput <= '_')
1434
	retval = kbinput - '@';
1435
1436
    /* Ctrl-` (Ctrl-2, Ctrl-Space, Ctrl-@) to Ctrl-~ (Ctrl-6, Ctrl-^) */
    else if ('`' <= kbinput && kbinput <= '~')
1437
	retval = kbinput - '`';
1438
1439
1440
    else
	retval = kbinput;

1441
#ifdef DEBUG
1442
    fprintf(stderr, "get_control_kbinput(): kbinput = %d, retval = %d\n", kbinput, retval);
1443
1444
#endif

1445
1446
    return retval;
}
1447

1448
/* Read in a stream of characters verbatim, and return the length of the
1449
1450
1451
1452
 * string in kbinput_len.  Assume nodelay(win) is FALSE. */
int *get_verbatim_kbinput(WINDOW *win, size_t *kbinput_len)
{
    int *retval;
1453

1454
    /* Turn off flow control characters if necessary so that we can type
1455
1456
     * them in verbatim, and turn the keypad off if necessary so that we
     * don't get extended keypad values. */
1457
1458
    if (ISSET(PRESERVE))
	disable_flow_control();
1459
1460
    if (!ISSET(REBIND_KEYPAD))
	keypad(win, FALSE);
1461

1462
    /* Read in one keycode, or one or two escapes. */
1463
    retval = parse_verbatim_kbinput(win, kbinput_len);
1464

1465
    /* If the code is invalid in the current mode, discard it. */
1466
1467
    if (retval != NULL && ((*retval == '\n' && as_an_at) ||
				(*retval == '\0' && !as_an_at))) {
1468
1469
1470
1471
	*kbinput_len = 0;
	beep();
    }

1472
    /* Turn flow control characters back on if necessary and turn the
1473
     * keypad back on if necessary now that we're done. */
1474
1475
    if (ISSET(PRESERVE))
	enable_flow_control();
1476
1477
1478
1479
1480
1481
    /* Use the global window pointers, because a resize may have freed
     * the data that the win parameter points to. */
    if (!ISSET(REBIND_KEYPAD)) {
	keypad(edit, TRUE);
	keypad(bottomwin, TRUE);
    }
1482

1483
    return retval;
1484
1485
}

1486
1487
1488
1489
/* Read in one control character (or an iTerm/Eterm/rxvt double Escape),
 * or convert a series of six digits into a Unicode codepoint.  Return
 * in count either 1 (for a control character or the first byte of a
 * multibyte sequence), or 2 (for an iTerm/Eterm/rxvt double Escape). */
1490
int *parse_verbatim_kbinput(WINDOW *win, size_t *count)
1491
{
1492
    int *kbinput;
1493

1494
    /* Read in the first code. */
1495
1496
    while ((kbinput = get_input(win, 1)) == NULL)
	;
1497

1498
#ifndef NANO_TINY
1499
1500
1501
    /* When the window was resized, abort and return nothing. */
    if (*kbinput == KEY_WINCH) {
	free(kbinput);
1502
	*count = 0;
1503
1504
	return NULL;
    }
1505
#endif
1506

1507
1508
    *count = 1;

1509
#ifdef ENABLE_UTF8
1510
    if (using_utf8()) {
1511
	/* Check whether the first code is a valid starter digit: 0 or 1. */
1512
	long unicode = get_unicode_kbinput(win, *kbinput);
1513

1514
	/* If the first code isn't the digit 0 nor 1, put it back. */
1515
	if (unicode != ERR)
1516
	    unget_input(kbinput, 1);
1517
1518
	/* Otherwise, continue reading in digits until we have a complete
	 * Unicode value, and put back the corresponding byte(s). */
1519
	else {
1520
	    char *multibyte;
1521
	    int onebyte, i;
1522

1523
	    while (unicode == ERR) {
1524
		free(kbinput);
1525
1526
		while ((kbinput = get_input(win, 1)) == NULL)
		    ;
1527
		unicode = get_unicode_kbinput(win, *kbinput);
1528
	    }
1529

1530
	    /* Convert the Unicode value to a multibyte sequence. */
1531
	    multibyte = make_mbchar(unicode, (int *)count);
1532

1533
	    /* Insert the multibyte sequence into the input buffer. */
1534
	    for (i = *count; i > 0 ; i--) {
1535
		onebyte = (unsigned char)multibyte[i - 1];
1536
1537
		unget_input(&onebyte, 1);
	    }
1538

1539
	    free(multibyte);
1540
	}
1541
    } else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1542
#endif /* ENABLE_UTF8 */
1543
	/* Put back the first code. */
1544
	unget_input(kbinput, 1);
1545

1546
1547
    free(kbinput);

1548
    /* If this is an iTerm/Eterm/rxvt double escape, take both Escapes. */
1549
1550
1551
1552
1553
    if (key_buffer_len > 3 && *key_buffer == ESC_CODE &&
		key_buffer[1] == ESC_CODE && key_buffer[2] == '[')
	*count = 2;

    return get_input(NULL, *count);
1554
1555
}

1556
#ifdef ENABLE_MOUSE
1557
/* Handle any mouse event that may have occurred.  We currently handle
1558
1559
1560
1561
1562
1563
1564
 * releases/clicks of the first mouse button.  If allow_shortcuts is
 * TRUE, releasing/clicking on a visible shortcut will put back the
 * keystroke associated with that shortcut.  If NCURSES_MOUSE_VERSION is
 * at least 2, we also currently handle presses of the fourth mouse
 * button (upward rolls of the mouse wheel) by putting back the
 * keystrokes to move up, and presses of the fifth mouse button
 * (downward rolls of the mouse wheel) by putting back the keystrokes to
1565
1566
1567
1568
1569
1570
 * move down.  We also store the coordinates of a mouse event that needs
 * to be handled in mouse_x and mouse_y, relative to the entire screen.
 * Return -1 on error, 0 if the mouse event needs to be handled, 1 if
 * it's been handled by putting back keystrokes that need to be handled.
 * or 2 if it's been ignored.  Assume that KEY_MOUSE has already been
 * read in. */
1571
int get_mouseinput(int *mouse_x, int *mouse_y, bool allow_shortcuts)
1572
1573
{
    MEVENT mevent;
1574
    bool in_bottomwin;
1575
    subnfunc *f;
1576
1577
1578
1579
1580
1581

    *mouse_x = -1;
    *mouse_y = -1;

    /* First, get the actual mouse event. */
    if (getmouse(&mevent) == ERR)
1582
	return -1;
1583

1584
    /* Save the screen coordinates where the mouse event took place. */
1585
    *mouse_x = mevent.x - margin;
1586
    *mouse_y = mevent.y;
1587

1588
1589
    in_bottomwin = wenclose(bottomwin, *mouse_y, *mouse_x);

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1590
    /* Handle releases/clicks of the first mouse button. */
1591
    if (mevent.bstate & (BUTTON1_RELEASED | BUTTON1_CLICKED)) {
1592
1593
	/* If we're allowing shortcuts, the current shortcut list is
	 * being displayed on the last two lines of the screen, and the
1594
1595
1596
	 * first mouse button was released on/clicked inside it, we need
	 * to figure out which shortcut was released on/clicked and put
	 * back the equivalent keystroke(s) for it. */
1597
	if (allow_shortcuts && !ISSET(NO_HELP) && in_bottomwin) {
1598
1599
1600
1601
	    int i;
		/* The width of all the shortcuts, except for the last
		 * two, in the shortcut list in bottomwin. */
	    int j;
1602
		/* The calculated index number of the clicked item. */
1603
1604
	    size_t number;
		/* The number of available shortcuts in the current menu. */
1605

1606
1607
1608
1609
1610
1611
1612
1613
1614
	    /* Translate the mouse event coordinates so that they're
	     * relative to bottomwin. */
	    wmouse_trafo(bottomwin, mouse_y, mouse_x, FALSE);

	    /* Handle releases/clicks of the first mouse button on the
	     * statusbar elsewhere. */
	    if (*mouse_y == 0) {
		/* Restore the untranslated mouse event coordinates, so
		 * that they're relative to the entire screen again. */
1615
		*mouse_x = mevent.x - margin;
1616
1617
1618
1619
		*mouse_y = mevent.y;

		return 0;
	    }
1620

1621
	    /* Determine how many shortcuts are being shown. */
1622
	    number = length_of_list(currmenu);
1623

1624
1625
	    if (number > MAIN_VISIBLE)
		number = MAIN_VISIBLE;
1626

1627
1628
1629
	    /* Calculate the width of all of the shortcuts in the list
	     * except for the last two, which are longer by (COLS % i)
	     * columns so as to not waste space. */
1630
	    if (number < 2)
1631
1632
		i = COLS / (MAIN_VISIBLE / 2);
	    else
1633
		i = COLS / ((number / 2) + (number % 2));
1634

1635
1636
	    /* Calculate the one-based index in the shortcut list. */
	    j = (*mouse_x / i) * 2 + *mouse_y;
1637

1638
	    /* Adjust the index if we hit the last two wider ones. */
1639
	    if ((j > number) && (*mouse_x % i < COLS % i))
1640
		j -= 2;
1641
1642
1643
#ifdef DEBUG
	    fprintf(stderr, "Calculated %i as index in shortcut list, currmenu = %x.\n", j, currmenu);
#endif
1644
1645
	    /* Ignore releases/clicks of the first mouse button beyond
	     * the last shortcut. */
1646
	    if (j > number)
1647
		return 2;
1648

1649
1650
	    /* Go through the list of functions to determine which
	     * shortcut in the current menu we released/clicked on. */
1651
	    for (f = allfuncs; f != NULL; f = f->next) {
1652
		if ((f->menus & currmenu) == 0)
1653
1654
		    continue;
		if (first_sc_for(currmenu, f->scfunc) == NULL)
1655
		    continue;
1656
1657
		/* Tick off an actually shown shortcut. */
		j -= 1;
1658
1659
		if (j == 0)
		    break;
1660
	    }
1661
#ifdef DEBUG
1662
	    fprintf(stderr, "Stopped on func %ld present in menus %x\n", (long)f->scfunc, f->menus);
1663
#endif
1664

1665
	    /* And put the corresponding key into the keyboard buffer. */
1666
	    if (f != NULL) {
1667
		const sc *s = first_sc_for(currmenu, f->scfunc);
1668
		unget_kbinput(s->keycode, s->meta);
1669
	    }
1670
	    return 1;
1671
	} else
1672
1673
	    /* Handle releases/clicks of the first mouse button that
	     * aren't on the current shortcut list elsewhere. */
1674
	    return 0;
1675
    }
1676
1677
#if NCURSES_MOUSE_VERSION >= 2
    /* Handle presses of the fourth mouse button (upward rolls of the
1678
1679
1680
     * mouse wheel) and presses of the fifth mouse button (downward
     * rolls of the mouse wheel) . */
    else if (mevent.bstate & (BUTTON4_PRESSED | BUTTON5_PRESSED)) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1681
	bool in_edit = wenclose(edit, *mouse_y, *mouse_x);
1682

1683
1684
1685
1686
	if (in_bottomwin)
	    /* Translate the mouse event coordinates so that they're
	     * relative to bottomwin. */
	    wmouse_trafo(bottomwin, mouse_y, mouse_x, FALSE);
1687

1688
	if (in_edit || (in_bottomwin && *mouse_y == 0)) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1689
1690
	    int i;

1691
1692
1693
1694
1695
	    /* One upward roll of the mouse wheel is equivalent to
	     * moving up three lines, and one downward roll of the mouse
	     * wheel is equivalent to moving down three lines. */
	    for (i = 0; i < 3; i++)
		unget_kbinput((mevent.bstate & BUTTON4_PRESSED) ?
1696
				KEY_PPAGE : KEY_NPAGE, FALSE);
1697
1698
1699
1700
1701
1702
1703

	    return 1;
	} else
	    /* Ignore presses of the fourth mouse button and presses of
	     * the fifth mouse buttons that aren't on the edit window or
	     * the statusbar. */
	    return 2;
1704
1705
    }
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1706
1707
1708

    /* Ignore all other mouse events. */
    return 2;
1709
}
1710
#endif /* ENABLE_MOUSE */
1711

1712
1713
1714
1715
/* Return the shortcut that corresponds to the values of kbinput (the
 * key itself) and meta_key (whether the key is a meta sequence).  The
 * returned shortcut will be the first in the list that corresponds to
 * the given sequence. */
1716
const sc *get_shortcut(int *kbinput)
1717
{
1718
    sc *s;
1719

1720
#ifdef DEBUG
1721
1722
    fprintf(stderr, "get_shortcut(): kbinput = %d, meta_key = %s -- ",
				*kbinput, meta_key ? "TRUE" : "FALSE");
1723
1724
#endif

1725
    for (s = sclist; s != NULL; s = s->next) {
1726
	if ((s->menus & currmenu) && *kbinput == s->keycode &&
1727
					meta_key == s->meta) {
1728
#ifdef DEBUG
1729
1730
	    fprintf (stderr, "matched seq '%s'  (menu is %x from %x)\n",
				s->keystr, currmenu, s->menus);
1731
#endif
1732
	    return s;
1733
1734
	}
    }
1735
#ifdef DEBUG
1736
    fprintf (stderr, "matched nothing\n");
1737
#endif
1738
1739
1740
1741

    return NULL;
}

1742
1743
/* Move to (x, y) in win, and display a line of n spaces with the
 * current attributes. */
1744
void blank_row(WINDOW *win, int y, int x, int n)
1745
1746
{
    wmove(win, y, x);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1747

1748
1749
1750
1751
    for (; n > 0; n--)
	waddch(win, ' ');
}

1752
/* Blank the first line of the top portion of the window. */
1753
void blank_titlebar(void)
Chris Allegretta's avatar
Chris Allegretta committed
1754
{
1755
    blank_row(topwin, 0, 0, COLS);
1756
1757
}

1758
/* Blank all the lines of the middle portion of the window, i.e. the
1759
 * edit window. */
Chris Allegretta's avatar
Chris Allegretta committed
1760
1761
void blank_edit(void)
{
1762
    int row;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1763

1764
1765
    for (row = 0; row < editwinrows; row++)
	blank_row(edit, row, 0, COLS);
Chris Allegretta's avatar
Chris Allegretta committed
1766
1767
}

1768
/* Blank the first line of the bottom portion of the window. */
Chris Allegretta's avatar
Chris Allegretta committed
1769
1770
void blank_statusbar(void)
{
1771
    blank_row(bottomwin, 0, 0, COLS);
Chris Allegretta's avatar
Chris Allegretta committed
1772
1773
}

1774
1775
/* If the NO_HELP flag isn't set, blank the last two lines of the bottom
 * portion of the window. */
1776
1777
void blank_bottombars(void)
{
1778
    if (!ISSET(NO_HELP) && LINES > 4) {
1779
1780
	blank_row(bottomwin, 1, 0, COLS);
	blank_row(bottomwin, 2, 0, COLS);
1781
1782
1783
    }
}

1784
1785
/* Check if the number of keystrokes needed to blank the statusbar has
 * been pressed.  If so, blank the statusbar, unless constant cursor
1786
 * position display is on and we are in the editing screen. */
1787
void check_statusblank(void)
Chris Allegretta's avatar
Chris Allegretta committed
1788
{
1789
1790
1791
1792
1793
1794
    if (statusblank == 0)
	return;

    statusblank--;

    /* When editing and 'constantshow' is active, skip the blanking. */
1795
    if (currmenu == MMAIN && ISSET(CONSTANT_SHOW))
1796
1797
1798
1799
1800
	return;

    if (statusblank == 0) {
	blank_statusbar();
	wnoutrefresh(bottomwin);
Chris Allegretta's avatar
Chris Allegretta committed
1801
    }
1802
1803
1804
1805

    /* If the subwindows overlap, make sure to show the edit window now. */
    if (LINES == 1)
	edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
1806
1807
}

1808
1809
1810
1811
1812
1813
1814
/* Convert buf into a string that can be displayed on screen.  The caller
 * wants to display buf starting with the given column, and extending for
 * at most span columns.  column is zero-based, and span is one-based, so
 * span == 0 means you get "" returned.  The returned string is dynamically
 * allocated, and should be freed.  If isdata is TRUE, the caller might put
 * "$" at the beginning or end of the line if it's too long. */
char *display_string(const char *buf, size_t column, size_t span, bool isdata)
1815
{
1816
1817
1818
1819
    size_t start_index = actual_x(buf, column);
	/* The index of the first character that the caller wishes to show. */
    size_t start_col = strnlenpt(buf, start_index);
	/* The actual column where that first character starts. */
1820
    char *converted;
1821
	/* The expanded string we will return. */
1822
    size_t index = 0;
1823
	/* Current position in converted. */
1824
    size_t beyond = column + span;
1825
	/* The column number just beyond the last shown character. */
1826

1827
#ifdef USING_OLD_NCURSES
1828
    seen_wide = FALSE;
1829
#endif
1830
    buf += start_index;
1831

1832
    /* Allocate enough space for converting the relevant part of the line. */
1833
    converted = charalloc(strlen(buf) * (MAXCHARLEN + tabsize) + 1);
1834

1835
    /* If the first character starts before the left edge, or would be
1836
     * overwritten by a "$" token, then show placeholders instead. */
1837
1838
    if (*buf != '\0' && *buf != '\t' && (start_col < column ||
			(start_col > 0 && isdata && !ISSET(SOFTWRAP)))) {
1839
	if (is_cntrl_mbchar(buf)) {
1840
	    if (start_col < column) {
1841
		converted[index++] = control_mbrep(buf, isdata);
1842
		column++;
1843
		buf += parse_mbchar(buf, NULL, NULL);
1844
	    }
1845
	}
1846
#ifdef ENABLE_UTF8
1847
	else if (mbwidth(buf) == 2) {
1848
	    if (start_col == column) {
1849
		converted[index++] = ' ';
1850
		column++;
1851
1852
	    }

1853
1854
	    /* Display the right half of a two-column character as '<'. */
	    converted[index++] = '<';
1855
	    column++;
1856
	    buf += parse_mbchar(buf, NULL, NULL);
1857
	}
1858
#endif
1859
1860
    }

1861
    while (*buf != '\0' && column < beyond) {
1862
	int charlength, charwidth = 1;
1863

1864
	if (*buf == ' ') {
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
	    /* Show a space as a visible character, or as a space. */
#ifndef NANO_TINY
	    if (ISSET(WHITESPACE_DISPLAY)) {
		int i = whitespace_len[0];

		while (i < whitespace_len[0] + whitespace_len[1])
		    converted[index++] = whitespace[i++];
	    } else
#endif
		converted[index++] = ' ';
1875
	    column++;
1876
1877
	    buf++;
	    continue;
1878
	} else if (*buf == '\t') {
1879
	    /* Show a tab as a visible character, or as as a space. */
1880
#ifndef NANO_TINY
1881
	    if (ISSET(WHITESPACE_DISPLAY)) {
1882
		int i = 0;
1883

1884
1885
		while (i < whitespace_len[0])
		    converted[index++] = whitespace[i++];
1886
	    } else
1887
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1888
		converted[index++] = ' ';
1889
	    column++;
1890
	    /* Fill the tab up with the required number of spaces. */
1891
	    while (column % tabsize != 0) {
1892
		converted[index++] = ' ';
1893
		column++;
1894
	    }
1895
1896
1897
1898
	    buf++;
	    continue;
	}

1899
	charlength = length_of_char(buf, &charwidth);
1900

1901
	/* If buf contains a control character, represent it. */
1902
	if (is_cntrl_mbchar(buf)) {
1903
	    converted[index++] = '^';
1904
	    converted[index++] = control_mbrep(buf, isdata);
1905
	    column += 2;
1906
1907
1908
	    buf += charlength;
	    continue;
	}
1909

1910
1911
1912
1913
	/* If buf contains a valid non-control character, simply copy it. */
	if (charlength > 0) {
	    for (; charlength > 0; charlength--)
		converted[index++] = *(buf++);
1914

1915
	    column += charwidth;
1916
#ifdef USING_OLD_NCURSES
1917
	    if (charwidth > 1)
1918
		seen_wide = TRUE;
1919
#endif
1920
	    continue;
1921
1922
	}

1923
1924
1925
1926
	/* Represent an invalid sequence with the Replacement Character. */
	converted[index++] = '\xEF';
	converted[index++] = '\xBF';
	converted[index++] = '\xBD';
1927
	column++;
1928
1929
1930
1931
1932
	buf++;

	/* For invalid codepoints, skip extra bytes. */
	if (charlength < -1)
	   buf += charlength + 7;
1933
1934
    }

1935
    /* If there is more text than can be shown, make room for the $ or >. */
1936
    if (*buf != '\0' && (column > beyond || (isdata && !ISSET(SOFTWRAP)))) {
1937
	index = move_mbleft(converted, index);
1938

1939
1940
#ifdef ENABLE_UTF8
	/* Display the left half of a two-column character as '>'. */
1941
	if (mbwidth(converted + index) == 2)
1942
1943
1944
1945
	    converted[index++] = '>';
#endif
    }

1946
1947
    /* Null-terminate the converted string. */
    converted[index] = '\0';
1948

1949
    return converted;
1950
1951
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1952
1953
/* If path is NULL, we're in normal editing mode, so display the current
 * version of nano, the current filename, and whether the current file
1954
1955
1956
 * has been modified on the titlebar.  If path isn't NULL, we're either
 * in the file browser or the help viewer, so show either the current
 * directory or the title of help text, that is: whatever is in path. */
1957
void titlebar(const char *path)
Chris Allegretta's avatar
Chris Allegretta committed
1958
{
1959
1960
1961
1962
1963
1964
    size_t verlen, prefixlen, pathlen, statelen;
	/* The width of the different titlebar elements, in columns. */
    size_t pluglen = 0;
	/* The width that "Modified" would take up. */
    size_t offset = 0;
	/* The position at which the center part of the titlebar starts. */
1965
1966
    const char *branding = BRANDING;
	/* What is shown in the top left corner. */
1967
1968
1969
1970
    const char *prefix = "";
	/* What is shown before the path -- "File:", "DIR:", or "". */
    const char *state = "";
	/* The state of the current buffer -- "Modified", "View", or "". */
1971
1972
    char *caption;
	/* The presentable form of the pathname. */
1973

1974
1975
1976
1977
    /* If the screen is too small, there is no titlebar. */
    if (topwin == NULL)
	return;

1978
    assert(path != NULL || openfile->filename != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1979

1980
    wattron(topwin, interface_color_pair[TITLE_BAR]);
1981

1982
    blank_titlebar();
1983
    as_an_at = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
1984

1985
1986
1987
    /* Do as Pico: if there is not enough width available for all items,
     * first sacrifice the version string, then eat up the side spaces,
     * then sacrifice the prefix, and only then start dottifying. */
Chris Allegretta's avatar
Chris Allegretta committed
1988

1989
    /* Figure out the path, prefix and state strings. */
1990
    if (inhelp)
1991
	branding = "";
1992
#ifdef ENABLE_BROWSER
1993
    else if (path != NULL)
1994
1995
	prefix = _("DIR:");
#endif
1996
    else {
1997
1998
1999
2000
2001
2002
	if (openfile->filename[0] == '\0')
	    path = _("New Buffer");
	else {
	    path = openfile->filename;
	    prefix = _("File:");
	}
2003

2004
2005
2006
2007
	if (openfile->modified)
	    state = _("Modified");
	else if (ISSET(VIEW_MODE))
	    state = _("View");
2008

2009
2010
	pluglen = strlenpt(_("Modified")) + 1;
    }
2011

2012
    /* Determine the widths of the four elements, including their padding. */
2013
    verlen = strlenpt(branding) + 3;
2014
2015
2016
    prefixlen = strlenpt(prefix);
    if (prefixlen > 0)
	prefixlen++;
2017
    pathlen = strlenpt(path);
2018
2019
2020
2021
    statelen = strlenpt(state) + 2;
    if (statelen > 2) {
	pathlen++;
	pluglen = 0;
2022
2023
    }

2024
2025
    /* Only print the version message when there is room for it. */
    if (verlen + prefixlen + pathlen + pluglen + statelen <= COLS)
2026
	mvwaddstr(topwin, 0, 2, branding);
2027
2028
2029
2030
2031
2032
2033
2034
2035
    else {
	verlen = 2;
	/* If things don't fit yet, give up the placeholder. */
	if (verlen + prefixlen + pathlen + pluglen + statelen > COLS)
	    pluglen = 0;
	/* If things still don't fit, give up the side spaces. */
	if (verlen + prefixlen + pathlen + pluglen + statelen > COLS) {
	    verlen = 0;
	    statelen -= 2;
2036
2037
2038
	}
    }

2039
2040
2041
2042
    /* If we have side spaces left, center the path name. */
    if (verlen > 0)
	offset = verlen + (COLS - (verlen + pluglen + statelen) -
					(prefixlen + pathlen)) / 2;
2043

2044
2045
2046
2047
2048
2049
2050
2051
2052
    /* Only print the prefix when there is room for it. */
    if (verlen + prefixlen + pathlen + pluglen + statelen <= COLS) {
	mvwaddstr(topwin, 0, offset, prefix);
	if (prefixlen > 0)
	    waddstr(topwin, " ");
    } else
	wmove(topwin, 0, offset);

    /* Print the full path if there's room; otherwise, dottify it. */
2053
2054
2055
2056
2057
    if (pathlen + pluglen + statelen <= COLS) {
	caption = display_string(path, 0, pathlen, FALSE);
	waddstr(topwin, caption);
	free(caption);
    } else if (5 + statelen <= COLS) {
2058
	waddstr(topwin, "...");
2059
	caption = display_string(path, 3 + pathlen - COLS + statelen,
2060
					COLS - statelen, FALSE);
2061
2062
	waddstr(topwin, caption);
	free(caption);
2063
    }
2064

2065
2066
2067
2068
2069
2070
    /* Right-align the state if there's room; otherwise, trim it. */
    if (statelen > 0 && statelen <= COLS)
	mvwaddstr(topwin, 0, COLS - statelen, state);
    else if (statelen > 0)
	mvwaddnstr(topwin, 0, 0, state, actual_x(state, COLS));

2071
    wattroff(topwin, interface_color_pair[TITLE_BAR]);
2072

2073
    wrefresh(topwin);
Chris Allegretta's avatar
Chris Allegretta committed
2074
2075
}

2076
2077
2078
2079
2080
2081
/* Display a normal message on the statusbar, quietly. */
void statusbar(const char *msg)
{
    statusline(HUSH, msg);
}

2082
2083
2084
2085
2086
2087
2088
2089
2090
/* Warn the user on the statusbar and pause for a moment, so that the
 * message can be noticed and read. */
void warn_and_shortly_pause(const char *msg)
{
    statusbar(msg);
    beep();
    napms(1800);
}

2091
/* Display a message on the statusbar, and set suppress_cursorpos to
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2092
2093
 * TRUE, so that the message won't be immediately overwritten if
 * constant cursor position display is on. */
2094
void statusline(message_type importance, const char *msg, ...)
2095
2096
{
    va_list ap;
2097
    static int alerts = 0;
2098
    char *compound, *message;
2099
    size_t start_col;
2100
    bool bracketed;
2101
2102
2103
2104
#ifndef NANO_TINY
    bool old_whitespace = ISSET(WHITESPACE_DISPLAY);

    UNSET(WHITESPACE_DISPLAY);
2105
#endif
2106
2107
2108
2109
2110

    va_start(ap, msg);

    /* Curses mode is turned off.  If we use wmove() now, it will muck
     * up the terminal settings.  So we just use vfprintf(). */
2111
    if (isendwin()) {
2112
2113
2114
2115
2116
	vfprintf(stderr, msg, ap);
	va_end(ap);
	return;
    }

2117
2118
2119
2120
2121
    /* If there already was an alert message, ignore lesser ones. */
    if ((lastmessage == ALERT && importance != ALERT) ||
		(lastmessage == MILD && importance == HUSH))
	return;

2122
2123
2124
2125
2126
2127
    /* If the ALERT status has been reset, reset the counter. */
    if (lastmessage == HUSH)
	alerts = 0;

    /* Shortly pause after each of the first three alert messages,
     * to give the user time to read them. */
2128
    if (lastmessage == ALERT && alerts < 4 && !ISSET(NO_PAUSES))
2129
2130
	napms(1200);

2131
    if (importance == ALERT) {
2132
	if (++alerts > 3 && !ISSET(NO_PAUSES))
2133
	    msg = _("Further warnings were suppressed");
2134
	beep();
2135
    }
2136
2137

    lastmessage = importance;
2138

2139
2140
2141
    /* Turn the cursor off while fiddling in the statusbar. */
    curs_set(0);

2142
2143
    blank_statusbar();

2144
    /* Construct the message out of all the arguments. */
2145
2146
    compound = charalloc(MAXCHARLEN * (COLS + 1));
    vsnprintf(compound, MAXCHARLEN * (COLS + 1), msg, ap);
2147
    va_end(ap);
2148
2149
    message = display_string(compound, 0, COLS, FALSE);
    free(compound);
2150

2151
2152
    start_col = (COLS - strlenpt(message)) / 2;
    bracketed = (start_col > 1);
2153

2154
    wmove(bottomwin, 0, (bracketed ? start_col - 2 : start_col));
2155
    wattron(bottomwin, interface_color_pair[STATUS_BAR]);
2156
2157
    if (bracketed)
	waddstr(bottomwin, "[ ");
2158
2159
    waddstr(bottomwin, message);
    free(message);
2160
2161
    if (bracketed)
	waddstr(bottomwin, " ]");
2162
    wattroff(bottomwin, interface_color_pair[STATUS_BAR]);
2163

2164
    /* Push the message to the screen straightaway. */
2165
    wrefresh(bottomwin);
2166

2167
    suppress_cursorpos = TRUE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2168

2169
#ifndef NANO_TINY
2170
2171
    if (old_whitespace)
	SET(WHITESPACE_DISPLAY);
2172
#endif
2173
2174
2175

    /* If doing quick blanking, blank the statusbar after just one keystroke.
     * Otherwise, blank it after twenty-six keystrokes, as Pico does. */
2176
2177
2178
2179
    if (ISSET(QUICK_BLANK))
	statusblank = 1;
    else
	statusblank = 26;
2180
2181
}

2182
2183
/* Display the shortcut list corresponding to menu on the last two rows
 * of the bottom portion of the window. */
2184
void bottombars(int menu)
Chris Allegretta's avatar
Chris Allegretta committed
2185
{
2186
    size_t number, itemwidth, i;
2187
2188
    subnfunc *f;
    const sc *s;
2189

2190
2191
2192
    /* Set the global variable to the given menu. */
    currmenu = menu;

2193
    if (ISSET(NO_HELP) || LINES < 5)
Chris Allegretta's avatar
Chris Allegretta committed
2194
2195
	return;

2196
    /* Determine how many shortcuts there are to show. */
2197
    number = length_of_list(menu);
2198

2199
2200
    if (number > MAIN_VISIBLE)
	number = MAIN_VISIBLE;
2201

2202
    /* Compute the width of each keyname-plus-explanation pair. */
2203
    itemwidth = COLS / ((number / 2) + (number % 2));
Chris Allegretta's avatar
Chris Allegretta committed
2204

2205
    /* If there is no room, don't print anything. */
2206
    if (itemwidth == 0)
2207
2208
	return;

2209
    blank_bottombars();
2210

2211
#ifdef DEBUG
2212
    fprintf(stderr, "In bottombars, number of items == \"%d\"\n", (int) number);
2213
#endif
2214

2215
    for (f = allfuncs, i = 0; i < number && f != NULL; f = f->next) {
2216
#ifdef DEBUG
2217
	fprintf(stderr, "Checking menu items....");
2218
#endif
2219
	if ((f->menus & menu) == 0)
2220
	    continue;
2221

2222
#ifdef DEBUG
2223
	fprintf(stderr, "found one! f->menus = %x, desc = \"%s\"\n", f->menus, f->desc);
2224
#endif
2225
2226
	s = first_sc_for(menu, f->scfunc);
	if (s == NULL) {
2227
2228
2229
#ifdef DEBUG
	    fprintf(stderr, "Whoops, guess not, no shortcut key found for func!\n");
#endif
2230
2231
	    continue;
	}
2232
2233

	wmove(bottomwin, 1 + i % 2, (i / 2) * itemwidth);
2234
#ifdef DEBUG
2235
	fprintf(stderr, "Calling onekey with keystr \"%s\" and desc \"%s\"\n", s->keystr, f->desc);
2236
#endif
2237
	onekey(s->keystr, _(f->desc), itemwidth + (COLS % itemwidth));
2238
	i++;
Chris Allegretta's avatar
Chris Allegretta committed
2239
    }
2240

2241
2242
    /* Defeat a VTE bug by moving the cursor and forcing a screen update. */
    wmove(bottomwin, 0, 0);
2243
    wrefresh(bottomwin);
Chris Allegretta's avatar
Chris Allegretta committed
2244
2245
}

2246
2247
/* Write a shortcut key to the help area at the bottom of the window.
 * keystroke is e.g. "^G" and desc is e.g. "Get Help".  We are careful
2248
 * to write at most length characters, even if length is very small and
2249
2250
 * keystroke and desc are long.  Note that waddnstr(,,(size_t)-1) adds
 * the whole string!  We do not bother padding the entry with blanks. */
2251
void onekey(const char *keystroke, const char *desc, int length)
Chris Allegretta's avatar
Chris Allegretta committed
2252
{
2253
2254
    assert(keystroke != NULL && desc != NULL);

2255
    wattron(bottomwin, interface_color_pair[KEY_COMBO]);
2256
    waddnstr(bottomwin, keystroke, actual_x(keystroke, length));
2257
    wattroff(bottomwin, interface_color_pair[KEY_COMBO]);
2258

2259
    length -= strlenpt(keystroke) + 1;
2260

2261
    if (length > 0) {
2262
	waddch(bottomwin, ' ');
2263
	wattron(bottomwin, interface_color_pair[FUNCTION_TAG]);
2264
	waddnstr(bottomwin, desc, actual_x(desc, length));
2265
	wattroff(bottomwin, interface_color_pair[FUNCTION_TAG]);
Chris Allegretta's avatar
Chris Allegretta committed
2266
2267
2268
    }
}

2269
/* Redetermine current_y from the position of current relative to edittop,
2270
 * and put the cursor in the edit window at (current_y, "current_x"). */
2271
void place_the_cursor(bool forreal)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2272
{
2273
    ssize_t row = 0;
2274
    size_t col, xpt = xplustabs();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2275

2276
#ifndef NANO_TINY
2277
    if (ISSET(SOFTWRAP)) {
2278
	filestruct *line = openfile->edittop;
2279

2280
2281
	row -= (openfile->firstcolumn / editwincols);

2282
	/* Calculate how many rows the lines from edittop to current use. */
2283
	while (line != NULL && line != openfile->current) {
2284
	    row += strlenpt(line->data) / editwincols + 1;
2285
2286
2287
	    line = line->next;
	}

2288
2289
2290
	/* Add the number of wraps in the current line before the cursor. */
	row += xpt / editwincols;
	col = xpt % editwincols;
2291
2292

	/* If the cursor ought to be in column zero, nudge it there. */
2293
	if (forreal && openfile->placewewant % editwincols == 0 && col != 0) {
2294
2295
2296
	    row++;
	    col = 0;
	}
2297
2298
2299
    } else
#endif
    {
2300
2301
	row = openfile->current->lineno - openfile->edittop->lineno;
	col = xpt - get_page_start(xpt);
2302
    }
2303
2304
2305
2306

    if (row < editwinrows)
	wmove(edit, row, margin + col);

2307
2308
    if (forreal)
	openfile->current_y = row;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2309
}
Chris Allegretta's avatar
Chris Allegretta committed
2310

2311
/* edit_draw() takes care of the job of actually painting a line into
2312
 * the edit window.  fileptr is the line to be painted, at row row of
2313
2314
 * the window.  converted is the actual string to be written to the
 * window, with tabs and control characters replaced by strings of
2315
 * regular characters.  from_col is the column number of the first
2316
 * character of this page.  That is, the first character of converted
2317
 * corresponds to character number actual_x(fileptr->data, from_col) of the
2318
 * line. */
2319
void edit_draw(filestruct *fileptr, const char *converted,
2320
	int row, size_t from_col)
Chris Allegretta's avatar
Chris Allegretta committed
2321
{
2322
#if !defined(NANO_TINY) || !defined(DISABLE_COLOR)
2323
    size_t from_x = actual_x(fileptr->data, from_col);
2324
2325
	/* The position in fileptr->data of the leftmost character
	 * that displays at least partially on the window. */
2326
    size_t till_x = actual_x(fileptr->data, from_col + editwincols - 1) + 1;
2327
	/* The position in fileptr->data of the first character that is
2328
2329
	 * completely off the window to the right.  Note that till_x
	 * might be beyond the null terminator of the string. */
2330
2331
#endif

2332
    assert(openfile != NULL && fileptr != NULL && converted != NULL);
2333
2334
2335
    assert(strlenpt(converted) <= editwincols);

#ifdef ENABLE_LINENUMBERS
2336
    /* If line numbering is switched on, put a line number in front of
2337
2338
     * the text -- but only for the parts that are not softwrapped. */
    if (margin > 0) {
2339
	wattron(edit, interface_color_pair[LINE_NUMBER]);
2340
#ifndef NANO_TINY
2341
	if (ISSET(SOFTWRAP) && from_x != 0)
2342
	    mvwprintw(edit, row, 0, "%*s", margin - 1, " ");
2343
2344
	else
#endif
2345
	    mvwprintw(edit, row, 0, "%*ld", margin - 1, (long)fileptr->lineno);
2346
	wattroff(edit, interface_color_pair[LINE_NUMBER]);
2347
2348
    }
#endif
2349

2350
2351
2352
    /* First simply write the converted line -- afterward we'll add colors
     * and the marking highlight on just the pieces that need it. */
    mvwaddstr(edit, row, margin, converted);
2353

2354
#ifdef USING_OLD_NCURSES
2355
2356
2357
    /* Tell ncurses to really redraw the line without trying to optimize
     * for what it thinks is already there, because it gets it wrong in
     * the case of a wide character in column zero.  See bug #31743. */
2358
    if (seen_wide)
2359
	wredrawln(edit, row, 1);
2360
#endif
2361

2362
#ifndef DISABLE_COLOR
2363
    /* If color syntaxes are available and turned on, apply them. */
2364
    if (openfile->colorstrings != NULL && !ISSET(NO_COLOR_SYNTAX)) {
2365
	const colortype *varnish = openfile->colorstrings;
2366

2367
2368
2369
2370
	/* If there are multiline regexes, make sure there is a cache. */
	if (openfile->syntax->nmultis > 0)
	    alloc_multidata_if_needed(fileptr);

2371
	/* Iterate through all the coloring regexes. */
2372
	for (; varnish != NULL; varnish = varnish->next) {
2373
2374
	    size_t index = 0;
		/* Where in the line we currently begin looking for a match. */
2375
	    int start_col;
2376
		/* The starting column of a piece to paint.  Zero-based. */
2377
	    int paintlen = 0;
2378
2379
2380
		/* The number of characters to paint. */
	    const char *thetext;
		/* The place in converted from where painting starts. */
2381
2382
	    regmatch_t match;
		/* The match positions of a single-line regex. */
2383

2384
2385
2386
	    /* Two notes about regexec().  A return value of zero means
	     * that there is a match.  Also, rm_eo is the first
	     * non-matching character after the match. */
2387

2388
2389
	    wattron(edit, varnish->attributes);

2390
2391
	    /* First case: varnish is a single-line expression. */
	    if (varnish->end == NULL) {
2392
		/* We increment index by rm_eo, to move past the end of the
2393
		 * last match.  Even though two matches may overlap, we
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2394
2395
		 * want to ignore them, so that we can highlight e.g. C
		 * strings correctly. */
2396
		while (index < till_x) {
2397
2398
		    /* Note the fifth parameter to regexec().  It says
		     * not to match the beginning-of-line character
2399
		     * unless index is zero.  If regexec() returns
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2400
2401
		     * REG_NOMATCH, there are no more matches in the
		     * line. */
2402
		    if (regexec(varnish->start, &fileptr->data[index], 1,
2403
				&match, (index == 0) ? 0 : REG_NOTBOL) != 0)
2404
			break;
2405

2406
		    /* If the match is of length zero, skip it. */
2407
		    if (match.rm_so == match.rm_eo) {
2408
			index = move_mbright(fileptr->data,
2409
						index + match.rm_eo);
2410
2411
2412
			continue;
		    }

2413
		    /* Translate the match to the beginning of the line. */
2414
2415
2416
		    match.rm_so += index;
		    match.rm_eo += index;
		    index = match.rm_eo;
2417

2418
2419
		    /* If the matching part is not visible, skip it. */
		    if (match.rm_eo <= from_x || match.rm_so >= till_x)
2420
2421
			continue;

2422
2423
2424
		    start_col = (match.rm_so <= from_x) ?
					0 : strnlenpt(fileptr->data,
					match.rm_so) - from_col;
2425

2426
		    thetext = converted + actual_x(converted, start_col);
2427

2428
		    paintlen = actual_x(thetext, strnlenpt(fileptr->data,
2429
					match.rm_eo) - from_col - start_col);
2430

2431
		    mvwaddnstr(edit, row, margin + start_col,
2432
						thetext, paintlen);
Chris Allegretta's avatar
Chris Allegretta committed
2433
		}
2434
2435
2436
2437
		goto tail_of_loop;
	    }

	    /* Second case: varnish is a multiline expression. */
2438
2439
2440
2441
	    const filestruct *start_line = fileptr->prev;
		/* The first line before fileptr that matches 'start'. */
	    const filestruct *end_line = fileptr;
		/* The line that matches 'end'. */
2442
2443
	    regmatch_t startmatch, endmatch;
		/* The match positions of the start and end regexes. */
2444

2445
2446
2447
	    /* Assume nothing gets painted until proven otherwise below. */
	    fileptr->multidata[varnish->id] = CNONE;

2448
2449
2450
2451
	    /* First check the multidata of the preceding line -- it tells
	     * us about the situation so far, and thus what to do here. */
	    if (start_line != NULL && start_line->multidata != NULL) {
		if (start_line->multidata[varnish->id] == CWHOLELINE ||
2452
2453
			start_line->multidata[varnish->id] == CENDAFTER ||
			start_line->multidata[varnish->id] == CWOULDBE)
2454
2455
2456
		    goto seek_an_end;
		if (start_line->multidata[varnish->id] == CNONE ||
			start_line->multidata[varnish->id] == CBEGINBEFORE ||
2457
			start_line->multidata[varnish->id] == CSTARTENDHERE)
2458
2459
2460
		    goto step_two;
	    }

2461
2462
	    /* The preceding line has no precalculated multidata.  So, do
	     * some backtracking to find out what to paint. */
2463

2464
2465
	    /* First step: see if there is a line before current that
	     * matches 'start' and is not complemented by an 'end'. */
2466
2467
	    while (start_line != NULL && regexec(varnish->start,
		    start_line->data, 1, &startmatch, 0) == REG_NOMATCH) {
2468
		/* There is no start on this line; but if there is an end,
2469
2470
		 * there is no need to look for starts on earlier lines. */
		if (regexec(varnish->end, start_line->data, 0, NULL, 0) == 0)
2471
		    goto step_two;
2472
2473
		start_line = start_line->prev;
	    }
2474

2475
2476
2477
2478
2479
2480
2481
	    /* If no start was found, skip to the next step. */
	    if (start_line == NULL)
		goto step_two;

	    /* If a found start has been qualified as an end earlier,
	     * believe it and skip to the next step. */
	    if (start_line->multidata != NULL &&
2482
2483
			(start_line->multidata[varnish->id] == CBEGINBEFORE ||
			start_line->multidata[varnish->id] == CSTARTENDHERE))
2484
		goto step_two;
2485

2486
	    /* Is there an uncomplemented start on the found line? */
2487
	    while (TRUE) {
2488
		/* Begin searching for an end after the start match. */
2489
		index += startmatch.rm_eo;
2490
		/* If there is no end after this last start, good. */
2491
2492
		if (regexec(varnish->end, start_line->data + index, 1, &endmatch,
				(index == 0) ? 0 : REG_NOTBOL) == REG_NOMATCH)
2493
		    break;
2494
2495
		/* Begin searching for a new start after the end match. */
		index += endmatch.rm_eo;
2496
2497
2498
		/* If both start and end match are mere anchors, advance. */
		if (startmatch.rm_so == startmatch.rm_eo &&
				endmatch.rm_so == endmatch.rm_eo) {
2499
		    if (start_line->data[index] == '\0')
2500
			break;
2501
		    index = move_mbright(start_line->data, index);
2502
		}
2503
		/* If there is no later start on this line, next step. */
2504
		if (regexec(varnish->start, start_line->data + index,
2505
				1, &startmatch, REG_NOTBOL) == REG_NOMATCH)
2506
		    goto step_two;
2507
2508
2509
	    }
	    /* Indeed, there is a start without an end on that line. */

2510
  seek_an_end:
2511
2512
	    /* We've already checked that there is no end between the start
	     * and the current line.  But is there an end after the start
2513
2514
2515
2516
2517
	     * at all?  We don't paint unterminated starts. */
	    while (end_line != NULL && regexec(varnish->end, end_line->data,
				 1, &endmatch, 0) == REG_NOMATCH)
		end_line = end_line->next;

2518
	    /* If there is no end, there is nothing to paint. */
2519
2520
	    if (end_line == NULL) {
		fileptr->multidata[varnish->id] = CWOULDBE;
2521
		goto tail_of_loop;
2522
	    }
2523

2524
	    /* If the end is on a later line, paint whole line, and be done. */
2525
	    if (end_line != fileptr) {
2526
		mvwaddnstr(edit, row, margin, converted, -1);
2527
2528
		fileptr->multidata[varnish->id] = CWHOLELINE;
		goto tail_of_loop;
2529
2530
2531
2532
	    }

	    /* Only if it is visible, paint the part to be coloured. */
	    if (endmatch.rm_eo > from_x) {
2533
		paintlen = actual_x(converted, strnlenpt(fileptr->data,
2534
						endmatch.rm_eo) - from_col);
2535
		mvwaddnstr(edit, row, margin, converted, paintlen);
2536
2537
	    }
	    fileptr->multidata[varnish->id] = CBEGINBEFORE;
2538

2539
  step_two:
2540
2541
2542
	    /* Second step: look for starts on this line, but begin
	     * looking only after an end match, if there is one. */
	    index = (paintlen == 0) ? 0 : endmatch.rm_eo;
2543

2544
	    while (regexec(varnish->start, fileptr->data + index,
2545
				1, &startmatch, (index == 0) ?
2546
				0 : REG_NOTBOL) == 0) {
2547
2548
2549
2550
		/* Translate the match to be relative to the
		 * beginning of the line. */
		startmatch.rm_so += index;
		startmatch.rm_eo += index;
2551

2552
		start_col = (startmatch.rm_so <= from_x) ?
2553
				0 : strnlenpt(fileptr->data,
2554
				startmatch.rm_so) - from_col;
2555

2556
		thetext = converted + actual_x(converted, start_col);
2557

2558
2559
		if (regexec(varnish->end, fileptr->data + startmatch.rm_eo,
				1, &endmatch, (startmatch.rm_eo == 0) ?
2560
				0 : REG_NOTBOL) == 0) {
2561
2562
2563
2564
		    /* Translate the end match to be relative to
		     * the beginning of the line. */
		    endmatch.rm_so += startmatch.rm_eo;
		    endmatch.rm_eo += startmatch.rm_eo;
2565
2566
		    /* Only paint the match if it is visible on screen and
		     * it is more than zero characters long. */
2567
		    if (endmatch.rm_eo > from_x &&
2568
					endmatch.rm_eo > startmatch.rm_so) {
2569
			paintlen = actual_x(thetext, strnlenpt(fileptr->data,
2570
					endmatch.rm_eo) - from_col - start_col);
2571

2572
			mvwaddnstr(edit, row, margin + start_col,
2573
						thetext, paintlen);
2574

2575
			fileptr->multidata[varnish->id] = CSTARTENDHERE;
2576
		    }
2577
		    index = endmatch.rm_eo;
2578
2579
2580
		    /* If both start and end match are anchors, advance. */
		    if (startmatch.rm_so == startmatch.rm_eo &&
				endmatch.rm_so == endmatch.rm_eo) {
2581
			if (fileptr->data[index] == '\0')
2582
			    break;
2583
			index = move_mbright(fileptr->data, index);
2584
2585
		    }
		    continue;
2586
		}
2587

2588
2589
		/* There is no end on this line.  But maybe on later lines? */
		end_line = fileptr->next;
2590

2591
2592
2593
		while (end_line != NULL && regexec(varnish->end, end_line->data,
					0, NULL, 0) == REG_NOMATCH)
		    end_line = end_line->next;
2594

2595
		/* If there is no end, we're done with this regex. */
2596
2597
		if (end_line == NULL) {
		    fileptr->multidata[varnish->id] = CWOULDBE;
2598
		    break;
2599
		}
2600

2601
		/* Paint the rest of the line, and we're done. */
2602
		mvwaddnstr(edit, row, margin + start_col, thetext, -1);
2603
2604
		fileptr->multidata[varnish->id] = CENDAFTER;
		break;
2605
	    }
2606
  tail_of_loop:
2607
	    wattroff(edit, varnish->attributes);
2608
	}
2609
    }
2610
#endif /* !DISABLE_COLOR */
2611

2612
#ifndef NANO_TINY
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
    /* If the mark is on, and fileptr is at least partially selected, we
     * need to paint it. */
    if (openfile->mark_set &&
		(fileptr->lineno <= openfile->mark_begin->lineno ||
		fileptr->lineno <= openfile->current->lineno) &&
		(fileptr->lineno >= openfile->mark_begin->lineno ||
		fileptr->lineno >= openfile->current->lineno)) {
	const filestruct *top, *bot;
	    /* The lines where the marked region begins and ends. */
	size_t top_x, bot_x;
	    /* The x positions where the marked region begins and ends. */
2624
	int start_col;
2625
2626
2627
2628
2629
	    /* The column where painting starts.  Zero-based. */
	const char *thetext;
	    /* The place in converted from where painting starts. */
	int paintlen = -1;
	    /* The number of characters to paint.  Negative means "all". */
2630

2631
	mark_order(&top, &top_x, &bot, &bot_x, NULL);
2632

2633
2634
2635
2636
	if (top->lineno < fileptr->lineno || top_x < from_x)
	    top_x = from_x;
	if (bot->lineno > fileptr->lineno || bot_x > till_x)
	    bot_x = till_x;
Chris Allegretta's avatar
Chris Allegretta committed
2637

2638
	/* Only paint if the marked part of the line is on this page. */
2639
	if (top_x < till_x && bot_x > from_x) {
2640
2641
	    /* Compute on which screen column to start painting. */
	    start_col = strnlenpt(fileptr->data, top_x) - from_col;
2642

2643
2644
2645
	    if (start_col < 0)
		start_col = 0;

2646
	    thetext = converted + actual_x(converted, start_col);
2647

2648
2649
2650
2651
2652
2653
	    /* If the end of the mark is onscreen, compute how many
	     * characters to paint.  Otherwise, just paint all. */
	    if (bot_x < till_x) {
		size_t end_col = strnlenpt(fileptr->data, bot_x) - from_col;
		paintlen = actual_x(thetext, end_col - start_col);
	    }
2654

2655
	    wattron(edit, hilite_attribute);
2656
	    mvwaddnstr(edit, row, margin + start_col, thetext, paintlen);
2657
	    wattroff(edit, hilite_attribute);
Chris Allegretta's avatar
Chris Allegretta committed
2658
	}
2659
    }
2660
#endif /* !NANO_TINY */
Chris Allegretta's avatar
Chris Allegretta committed
2661
2662
}

2663
2664
2665
/* Redraw the line at fileptr.  The line will be displayed so that the
 * character with the given index is visible -- if necessary, the line
 * will be horizontally scrolled.  In softwrap mode, however, the entire
2666
2667
2668
 * line will be passed to update_softwrapped_line().  Likely values of
 * index are current_x or zero.  Return the number of additional rows
 * consumed (when softwrapping). */
2669
int update_line(filestruct *fileptr, size_t index)
Chris Allegretta's avatar
Chris Allegretta committed
2670
{
2671
    int row = 0;
2672
	/* The row in the edit window we will be updating. */
2673
    char *converted;
2674
	/* The data of the line with tabs and control characters expanded. */
2675
2676
    size_t from_col = 0;
	/* From which column a horizontally scrolled line is displayed. */
Chris Allegretta's avatar
Chris Allegretta committed
2677

2678
#ifndef NANO_TINY
2679
2680
    if (ISSET(SOFTWRAP))
	return update_softwrapped_line(fileptr);
2681
#endif
2682
2683

    row = fileptr->lineno - openfile->edittop->lineno;
2684

2685
    /* If the line is offscreen, don't even try to display it. */
2686
    if (row < 0 || row >= editwinrows) {
2687
#ifndef NANO_TINY
2688
	statusline(ALERT, "Badness: tried to display a line on row %i"
2689
				" -- please report a bug", row);
2690
#endif
2691
	return 0;
2692
    }
2693

2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
    /* First, blank out the row. */
    blank_row(edit, row, 0, COLS);

    /* Next, find out from which column to start displaying the line. */
    from_col = get_page_start(strnlenpt(fileptr->data, index));

    /* Expand the line, replacing tabs with spaces, and control
     * characters with their displayed forms. */
    converted = display_string(fileptr->data, from_col, editwincols, TRUE);

    /* Draw the line. */
    edit_draw(fileptr, converted, row, from_col);
    free(converted);

    if (from_col > 0)
	mvwaddch(edit, row, margin, '$');
    if (strlenpt(fileptr->data) > from_col + editwincols)
	mvwaddch(edit, row, COLS - 1, '$');

2713
    return 1;
Chris Allegretta's avatar
Chris Allegretta committed
2714
2715
}

2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
#ifndef NANO_TINY
/* Redraw all the chunks of the given line (as far as they fit onscreen),
 * unless it's edittop, which will be displayed from column firstcolumn.
 * Return the number of additional rows consumed. */
int update_softwrapped_line(filestruct *fileptr)
{
    int row = 0;
	/* The row in the edit window we will write to. */
    filestruct *line = openfile->edittop;
	/* An iterator needed to find the relevant row. */
    int starting_row;
	/* The first row in the edit window that gets updated. */
    size_t from_col = 0;
	/* The starting column of the current chunk. */
    char *converted;
	/* The data of the chunk with tabs and control characters expanded. */
    size_t full_length;
	/* The length of the expanded line. */

    if (fileptr == openfile->edittop)
	from_col = openfile->firstcolumn;
    else
	row -= (openfile->firstcolumn / editwincols);

    /* Find out on which screen row the target line should be shown. */
    while (line != fileptr && line != NULL) {
	row += (strlenpt(line->data) / editwincols) + 1;
	line = line->next;
    }

2746
2747
2748
2749
    /* If the first chunk is offscreen, don't even try to display it. */
    if (row < 0 || row >= editwinrows) {
	statusline(ALERT, "Badness: tried to display a chunk on row %i"
				" -- please report a bug", row);
2750
	return 0;
2751
    }
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770

    full_length = strlenpt(fileptr->data);
    starting_row = row;

    while (from_col <= full_length && row < editwinrows) {
	blank_row(edit, row, 0, COLS);

	/* Convert the chunk to its displayable form and draw it. */
	converted = display_string(fileptr->data, from_col, editwincols, TRUE);
	edit_draw(fileptr, converted, row++, from_col);
	free(converted);

	from_col += editwincols;
    }

    return (row - starting_row);
}
#endif

2771
2772
2773
2774
/* Check whether the mark is on, or whether old_column and new_column are on
 * different "pages" (in softwrap mode, only the former applies), which means
 * that the relevant line needs to be redrawn. */
bool line_needs_update(const size_t old_column, const size_t new_column)
2775
{
2776
#ifndef NANO_TINY
2777
2778
2779
    if (openfile->mark_set)
	return TRUE;
    else
2780
#endif
2781
	return (get_page_start(old_column) != get_page_start(new_column));
2782
2783
}

2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
/* Try to move up nrows softwrapped chunks from the given line and the
 * given column (leftedge).  After moving, leftedge will be set to the
 * starting column of the current chunk.  Return the number of chunks we
 * couldn't move up, which will be zero if we completely succeeded. */
int go_back_chunks(int nrows, filestruct **line, size_t *leftedge)
{
    int i;

    /* Don't move more chunks than the window can hold. */
    if (nrows > editwinrows - 1)
	nrows = (editwinrows < 2) ? 1 : editwinrows - 1;

#ifndef NANO_TINY
    if (ISSET(SOFTWRAP)) {
	size_t current_chunk = (*leftedge) / editwincols;

2800
	/* Recede through the requested number of chunks. */
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
	for (i = nrows; i > 0; i--) {
	    if (current_chunk > 0) {
		current_chunk--;
		continue;
	    }

	    if (*line == openfile->fileage)
		break;

	    *line = (*line)->prev;
	    current_chunk = strlenpt((*line)->data) / editwincols;
	}

	/* Only change leftedge when we actually could move. */
	if (i < nrows)
	    *leftedge = current_chunk * editwincols;
    } else
#endif
	for (i = nrows; i > 0 && (*line)->prev != NULL; i--)
	    *line = (*line)->prev;

    return i;
}

/* Try to move down nrows softwrapped chunks from the given line and the
 * given column (leftedge).  After moving, leftedge will be set to the
 * starting column of the current chunk.  Return the number of chunks we
 * couldn't move down, which will be zero if we completely succeeded. */
int go_forward_chunks(int nrows, filestruct **line, size_t *leftedge)
{
    int i;

    /* Don't move more chunks than the window can hold. */
    if (nrows > editwinrows - 1)
	nrows = (editwinrows < 2) ? 1 : editwinrows - 1;

#ifndef NANO_TINY
    if (ISSET(SOFTWRAP)) {
	size_t current_chunk = (*leftedge) / editwincols;
	size_t last_chunk = strlenpt((*line)->data) / editwincols;

2842
	/* Advance through the requested number of chunks. */
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
	for (i = nrows; i > 0; i--) {
	    if (current_chunk < last_chunk) {
		current_chunk++;
		continue;
	    }

	    if (*line == openfile->filebot)
		break;

	    *line = (*line)->next;
	    current_chunk = 0;
	    last_chunk = strlenpt((*line)->data) / editwincols;
	}

	/* Only change leftedge when we actually could move. */
	if (i < nrows)
	    *leftedge = current_chunk * editwincols;
    } else
#endif
	for (i = nrows; i > 0 && (*line)->next != NULL; i--)
	    *line = (*line)->next;

    return i;
}

2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
/* Return TRUE if there are fewer than a screen's worth of lines between
 * the line at line number was_lineno (and column was_leftedge, if we're
 * in softwrap mode) and the line at current[current_x]. */
bool less_than_a_screenful(size_t was_lineno, size_t was_leftedge)
{
#ifndef NANO_TINY
    if (ISSET(SOFTWRAP)) {
	filestruct *line = openfile->current;
	size_t leftedge = (xplustabs() / editwincols) * editwincols;
	int rows_left = go_back_chunks(editwinrows - 1, &line, &leftedge);

	return (rows_left > 0 || line->lineno < was_lineno ||
		(line->lineno == was_lineno && leftedge <= was_leftedge));
    } else
#endif
	return (openfile->current->lineno - was_lineno < editwinrows);
}

2886
/* Scroll the edit window in the given direction and the given number of rows,
2887
 * and draw new lines on the blank lines left after the scrolling. */
2888
void edit_scroll(scroll_dir direction, int nrows)
2889
{
2890
    filestruct *line;
2891
2892
    size_t leftedge;

2893
2894
    /* Part 1: nrows is the number of rows we're going to scroll the text of
     * the edit window. */
2895

2896
2897
    /* Move the top line of the edit window the requested number of rows up or
     * down, and reduce the number of rows with the amount we couldn't move. */
2898
    if (direction == UPWARD)
2899
	nrows -= go_back_chunks(nrows, &openfile->edittop, &openfile->firstcolumn);
2900
    else
2901
	nrows -= go_forward_chunks(nrows, &openfile->edittop, &openfile->firstcolumn);
2902

2903
    /* Don't bother scrolling zero rows, nor more than the window can hold. */
2904
    if (nrows == 0) {
2905
#ifndef NANO_TINY
2906
	statusline(ALERT, "Underscrolling -- please report a bug");
2907
#endif
2908
	return;
2909
    }
2910
    if (nrows >= editwinrows) {
2911
#ifndef NANO_TINY
2912
2913
	if (editwinrows > 1)
	    statusline(ALERT, "Overscrolling -- please report a bug");
2914
#endif
2915
	refresh_needed = TRUE;
2916
	return;
2917
    }
2918

2919
    /* Scroll the text of the edit window a number of rows up or down. */
2920
    scrollok(edit, TRUE);
2921
    wscrl(edit, (direction == UPWARD) ? -nrows : nrows);
2922
2923
    scrollok(edit, FALSE);

2924
2925
    /* Part 2: nrows is now the number of rows in the scrolled region of the
     * edit window that we need to draw. */
2926

2927
2928
2929
2930
    /* If we're not on the first "page" (when not softwrapping), or the mark
     * is on, the row next to the scrolled region needs to be redrawn too. */
    if (line_needs_update(openfile->placewewant, 0) && nrows < editwinrows)
	nrows++;
2931

2932
    /* If we scrolled backward, start on the first line of the blank region. */
2933
    line = openfile->edittop;
2934
    leftedge = openfile->firstcolumn;
2935

2936
    /* If we scrolled forward, move down to the start of the blank region. */
2937
2938
    if (direction == DOWNWARD)
	go_forward_chunks(editwinrows - nrows, &line, &leftedge);
2939

2940
#ifndef NANO_TINY
2941
2942
2943
2944
2945
2946
    /* Compensate for the earlier chunks of a softwrapped line. */
    nrows += leftedge / editwincols;

    /* Don't compensate for the chunks that are offscreen. */
    if (line == openfile->edittop)
	nrows -= openfile->firstcolumn / editwincols;
2947
#endif
2948
2949
2950

    /* Draw new content on the blank rows inside the scrolled region
     * (and on the bordering row too when it was deemed necessary). */
2951
2952
2953
    while (nrows > 0 && line != NULL) {
	nrows -= update_line(line, (line == openfile->current) ?
					openfile->current_x : 0);
2954
	line = line->next;
2955
2956
2957
    }
}

2958
#ifndef NANO_TINY
2959
/* Ensure that firstcolumn is at the starting column of the softwrapped chunk
2960
2961
2962
2963
2964
2965
 * it's on.  We need to do this when the number of columns of the edit window
 * has changed, because then the width of softwrapped chunks has changed. */
void ensure_firstcolumn_is_aligned(void)
{
    if (openfile->firstcolumn % editwincols != 0)
	openfile->firstcolumn -= (openfile->firstcolumn % editwincols);
2966
2967
2968

    /* If smooth scrolling is on, make sure the viewport doesn't center. */
    focusing = FALSE;
2969
}
2970
#endif
2971

2972
2973
2974
2975
/* Return TRUE if current[current_x] is above the top of the screen, and FALSE
 * otherwise. */
bool current_is_above_screen(void)
{
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
#ifndef NANO_TINY
    if (ISSET(SOFTWRAP))
	/* The cursor is above screen when current[current_x] is before edittop
	 * at column firstcolumn. */
	return (openfile->current->lineno < openfile->edittop->lineno ||
		(openfile->current->lineno == openfile->edittop->lineno &&
		xplustabs() < openfile->firstcolumn));
    else
#endif
	return (openfile->current->lineno < openfile->edittop->lineno);
2986
2987
2988
2989
2990
2991
2992
2993
2994
}

/* Return TRUE if current[current_x] is below the bottom of the screen, and
 * FALSE otherwise. */
bool current_is_below_screen(void)
{
#ifndef NANO_TINY
    if (ISSET(SOFTWRAP)) {
	filestruct *line = openfile->edittop;
2995
	size_t leftedge = openfile->firstcolumn;
2996
2997

	/* If current[current_x] is more than a screen's worth of lines after
2998
	 * edittop at column firstcolumn, it's below the screen. */
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
	return (go_forward_chunks(editwinrows - 1, &line, &leftedge) == 0 &&
			(line->lineno < openfile->current->lineno ||
			(line->lineno == openfile->current->lineno &&
			leftedge < (xplustabs() / editwincols) * editwincols)));
    } else
#endif
	return (openfile->current->lineno >=
			openfile->edittop->lineno + editwinrows);
}

/* Return TRUE if current[current_x] is offscreen relative to edittop, and
 * FALSE otherwise. */
bool current_is_offscreen(void)
{
    return (current_is_above_screen() || current_is_below_screen());
}

3016
/* Update any lines between old_current and current that need to be
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3017
 * updated.  Use this if we've moved without changing any text. */
3018
void edit_redraw(filestruct *old_current)
3019
{
3020
3021
3022
3023
    size_t was_pww = openfile->placewewant;

    openfile->placewewant = xplustabs();

3024
    /* If the current line is offscreen, scroll until it's onscreen. */
3025
    if (current_is_offscreen()) {
3026
	adjust_viewport((focusing || !ISSET(SMOOTH_SCROLL)) ? CENTERING : FLOWING);
3027
	refresh_needed = TRUE;
3028
	return;
3029
    }
3030

3031
#ifndef NANO_TINY
3032
3033
    /* If the mark is on, update all lines between old_current and current. */
    if (openfile->mark_set) {
3034
	filestruct *line = old_current;
3035

3036
3037
	while (line != openfile->current) {
	    update_line(line, 0);
3038

3039
3040
	    line = (line->lineno > openfile->current->lineno) ?
			line->prev : line->next;
3041
	}
3042
3043
3044
3045
3046
3047
    } else
#endif
	/* Otherwise, update old_current only if it differs from current
	 * and was horizontally scrolled. */
	if (old_current != openfile->current && get_page_start(was_pww) > 0)
	    update_line(old_current, 0);
3048

3049
3050
    /* Update current if the mark is on or it has changed "page", or if it
     * differs from old_current and needs to be horizontally scrolled. */
3051
    if (line_needs_update(was_pww, openfile->placewewant) ||
3052
			(old_current != openfile->current &&
3053
			get_page_start(openfile->placewewant) > 0))
3054
	update_line(openfile->current, openfile->current_x);
3055
3056
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3057
3058
/* Refresh the screen without changing the position of lines.  Use this
 * if we've moved and changed text. */
Chris Allegretta's avatar
Chris Allegretta committed
3059
3060
void edit_refresh(void)
{
3061
3062
    filestruct *line;
    int row = 0;
3063

3064
3065
3066
3067
3068
3069
#ifndef DISABLE_COLOR
    /* When needed, initialize the colors for the current syntax. */
    if (!have_palette)
	color_init();
#endif

3070
    /* If the current line is out of view, get it back on screen. */
3071
    if (current_is_offscreen()) {
3072
#ifdef DEBUG
3073
3074
	fprintf(stderr, "edit-refresh: line = %ld, edittop = %ld and editwinrows = %d\n",
		(long)openfile->current->lineno, (long)openfile->edittop->lineno, editwinrows);
3075
#endif
3076
	adjust_viewport((focusing || !ISSET(SMOOTH_SCROLL)) ? CENTERING : STATIONARY);
3077
    }
Chris Allegretta's avatar
Chris Allegretta committed
3078

3079
#ifdef DEBUG
3080
    fprintf(stderr, "edit-refresh: now edittop = %ld\n", (long)openfile->edittop->lineno);
3081
#endif
3082

3083
3084
3085
3086
    line = openfile->edittop;

    while (row < editwinrows && line != NULL) {
	if (line == openfile->current)
3087
	    row += update_line(line, openfile->current_x);
3088
	else
3089
	    row += update_line(line, 0);
3090
	line = line->next;
3091
3092
    }

3093
    while (row < editwinrows)
3094
	blank_row(edit, row++, 0, COLS);
3095

3096
    place_the_cursor(TRUE);
3097
    wnoutrefresh(edit);
3098
3099

    refresh_needed = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
3100
3101
}

3102
3103
3104
3105
3106
/* Move edittop so that current is on the screen.  manner says how:
 * STATIONARY means that the cursor should stay on the same screen row,
 * CENTERING means that current should end up in the middle of the screen,
 * and FLOWING means that it should scroll no more than needed to bring
 * current into view. */
3107
void adjust_viewport(update_type manner)
Chris Allegretta's avatar
Chris Allegretta committed
3108
{
3109
    int goal = 0;
3110

3111
    if (manner == STATIONARY)
3112
	goal = openfile->current_y;
3113
3114
3115
3116
    else if (manner == CENTERING)
	goal = editwinrows / 2;
    else if (!current_is_above_screen())
	goal = editwinrows - 1;
3117

3118
    openfile->edittop = openfile->current;
3119
#ifndef NANO_TINY
3120
    if (ISSET(SOFTWRAP))
3121
	openfile->firstcolumn = (xplustabs() / editwincols) * editwincols;
3122
#endif
3123
3124

    /* Move edittop back goal rows, starting at current[current_x]. */
3125
    go_back_chunks(goal, &openfile->edittop, &openfile->firstcolumn);
3126

3127
#ifdef DEBUG
3128
    fprintf(stderr, "adjust_viewport(): setting edittop to lineno %ld\n", (long)openfile->edittop->lineno);
3129
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3130
3131
}

3132
/* Unconditionally redraw the entire screen. */
3133
void total_redraw(void)
3134
{
3135
3136
3137
3138
3139
3140
#ifdef USE_SLANG
    /* Slang curses emulation brain damage, part 4: Slang doesn't define
     * curscr. */
    SLsmg_touch_screen();
    SLsmg_refresh();
#else
3141
    wrefresh(curscr);
3142
#endif
3143
3144
}

3145
3146
/* Unconditionally redraw the entire screen, and then refresh it using
 * the current file. */
3147
3148
void total_refresh(void)
{
3149
    total_redraw();
3150
    titlebar(title);
3151
#ifdef ENABLE_HELP
3152
    if (inhelp)
3153
	wrap_the_help_text(TRUE);
3154
3155
3156
    else
#endif
	edit_refresh();
3157
    bottombars(currmenu);
3158
3159
}

3160
3161
/* Display the main shortcut list on the last two rows of the bottom
 * portion of the window. */
3162
3163
void display_main_list(void)
{
3164
#ifndef DISABLE_COLOR
3165
3166
    if (openfile->syntax &&
		(openfile->syntax->formatter || openfile->syntax->linter))
3167
	set_lint_or_format_shortcuts();
3168
3169
3170
3171
    else
	set_spell_shortcuts();
#endif

3172
    bottombars(MMAIN);
3173
3174
}

3175
3176
3177
3178
/* Show info about the current cursor position on the statusbar.
 * Do this unconditionally when force is TRUE; otherwise, only if
 * suppress_cursorpos is FALSE.  In any case, reset the latter. */
void do_cursorpos(bool force)
Chris Allegretta's avatar
Chris Allegretta committed
3179
{
3180
3181
    char saved_byte;
    size_t sum, cur_xpt = xplustabs() + 1;
3182
    size_t cur_lenpt = strlenpt(openfile->current->data) + 1;
3183
    int linepct, colpct, charpct;
Chris Allegretta's avatar
Chris Allegretta committed
3184

3185
    assert(openfile->fileage != NULL && openfile->current != NULL);
3186

3187
    /* Determine the size of the file up to the cursor. */
3188
    saved_byte = openfile->current->data[openfile->current_x];
3189
    openfile->current->data[openfile->current_x] = '\0';
3190

3191
    sum = get_totsize(openfile->fileage, openfile->current);
3192

3193
    openfile->current->data[openfile->current_x] = saved_byte;
3194
3195
3196

    /* When not at EOF, subtract 1 for an overcounted newline. */
    if (openfile->current != openfile->filebot)
3197
	sum--;
3198

3199
3200
    /* If the showing needs to be suppressed, don't suppress it next time. */
    if (suppress_cursorpos && !force) {
3201
	suppress_cursorpos = FALSE;
3202
	return;
3203
    }
Chris Allegretta's avatar
Chris Allegretta committed
3204

3205
    /* Display the current cursor position on the statusbar. */
3206
    linepct = 100 * openfile->current->lineno / openfile->filebot->lineno;
3207
    colpct = 100 * cur_xpt / cur_lenpt;
3208
    charpct = (openfile->totsize == 0) ? 0 : 100 * sum / openfile->totsize;
3209

3210
    statusline(HUSH,
3211
	_("line %ld/%ld (%d%%), col %lu/%lu (%d%%), char %lu/%lu (%d%%)"),
3212
	(long)openfile->current->lineno,
3213
	(long)openfile->filebot->lineno, linepct,
3214
	(unsigned long)cur_xpt, (unsigned long)cur_lenpt, colpct,
3215
	(unsigned long)sum, (unsigned long)openfile->totsize, charpct);
3216
3217
3218

    /* Displaying the cursor position should not suppress it next time. */
    suppress_cursorpos = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
3219
3220
}

3221
/* Unconditionally display the current cursor position. */
3222
void do_cursorpos_void(void)
3223
{
3224
    do_cursorpos(TRUE);
3225
3226
}

3227
3228
void enable_nodelay(void)
{
3229
3230
    nodelay_mode = TRUE;
    nodelay(edit, TRUE);
3231
3232
3233
3234
}

void disable_nodelay(void)
{
3235
3236
    nodelay_mode = FALSE;
    nodelay(edit, FALSE);
3237
3238
}

3239
3240
/* Highlight the current word being replaced or spell checked.  We
 * expect word to have tabs and control characters expanded. */
3241
void spotlight(bool active, const char *word)
Chris Allegretta's avatar
Chris Allegretta committed
3242
{
3243
3244
    size_t word_span = strlenpt(word);
    size_t room = word_span;
Chris Allegretta's avatar
Chris Allegretta committed
3245

3246
    /* Compute the number of columns that are available for the word. */
3247
    if (!ISSET(SOFTWRAP)) {
3248
	room = editwincols + get_page_start(xplustabs()) - xplustabs();
Chris Allegretta's avatar
Chris Allegretta committed
3249

3250
3251
3252
3253
	/* If the word is partially offscreen, reserve space for the "$". */
	if (word_span > room)
	    room--;
    }
3254

3255
    place_the_cursor(FALSE);
Chris Allegretta's avatar
Chris Allegretta committed
3256

3257
    if (active)
3258
	wattron(edit, hilite_attribute);
Chris Allegretta's avatar
Chris Allegretta committed
3259

3260
    /* This is so we can show zero-length matches. */
3261
    if (word_span == 0)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3262
	waddch(edit, ' ');
3263
    else
3264
	waddnstr(edit, word, actual_x(word, room));
3265

3266
    if (word_span > room)
3267
	waddch(edit, '$');
Chris Allegretta's avatar
Chris Allegretta committed
3268

3269
    if (active)
3270
	wattroff(edit, hilite_attribute);
3271
3272

    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
3273
3274
}

3275
#ifndef DISABLE_EXTRA
3276
3277
#define CREDIT_LEN 54
#define XLCREDIT_LEN 9
3278

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3279
3280
/* Easter egg: Display credits.  Assume nodelay(edit) and scrollok(edit)
 * are FALSE. */
3281
3282
void do_credits(void)
{
3283
3284
    bool old_more_space = ISSET(MORE_SPACE);
    bool old_no_help = ISSET(NO_HELP);
3285
    int kbinput = ERR, crpos = 0, xlpos = 0;
3286
3287
3288
    const char *credits[CREDIT_LEN] = {
	NULL,				/* "The nano text editor" */
	NULL,				/* "version" */
Chris Allegretta's avatar
Chris Allegretta committed
3289
3290
	VERSION,
	"",
3291
	NULL,				/* "Brought to you by:" */
Chris Allegretta's avatar
Chris Allegretta committed
3292
3293
3294
3295
3296
	"Chris Allegretta",
	"Jordi Mallach",
	"Adam Rogoyski",
	"Rob Siemborski",
	"Rocco Corsi",
3297
	"David Lawrence Ramsey",
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3298
	"David Benbennick",
Benno Schulenberg's avatar
Benno Schulenberg committed
3299
	"Mark Majeres",
3300
	"Mike Frysinger",
3301
	"Benno Schulenberg",
Chris Allegretta's avatar
Chris Allegretta committed
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
	"Ken Tyler",
	"Sven Guckes",
	"Bill Soudan",
	"Christian Weisgerber",
	"Erik Andersen",
	"Big Gaute",
	"Joshua Jensen",
	"Ryan Krebs",
	"Albert Chin",
	"",
3312
	NULL,				/* "Special thanks to:" */
3313
	"Monique, Brielle & Joseph",
Chris Allegretta's avatar
Chris Allegretta committed
3314
3315
3316
3317
3318
3319
	"Plattsburgh State University",
	"Benet Laboratories",
	"Amy Allegretta",
	"Linda Young",
	"Jeremy Robichaud",
	"Richard Kolb II",
3320
	NULL,				/* "The Free Software Foundation" */
Chris Allegretta's avatar
Chris Allegretta committed
3321
	"Linus Torvalds",
3322
	NULL,				/* "the many translators and the TP" */
3323
	NULL,				/* "For ncurses:" */
3324
3325
3326
3327
	"Thomas Dickey",
	"Pavel Curtis",
	"Zeyd Ben-Halim",
	"Eric S. Raymond",
3328
3329
3330
3331
3332
3333
	NULL,				/* "and anyone else we forgot..." */
	NULL,				/* "Thank you for using nano!" */
	"",
	"",
	"",
	"",
3334
	"(C) 2017",
3335
	"Free Software Foundation, Inc.",
3336
3337
3338
3339
	"",
	"",
	"",
	"",
3340
	"https://nano-editor.org/"
3341
3342
    };

3343
    const char *xlcredits[XLCREDIT_LEN] = {
3344
3345
3346
3347
3348
	N_("The nano text editor"),
	N_("version"),
	N_("Brought to you by:"),
	N_("Special thanks to:"),
	N_("The Free Software Foundation"),
3349
	N_("the many translators and the TP"),
3350
3351
3352
	N_("For ncurses:"),
	N_("and anyone else we forgot..."),
	N_("Thank you for using nano!")
3353
    };
3354

3355
3356
3357
3358
3359
3360
    if (!old_more_space || !old_no_help) {
	SET(MORE_SPACE);
	SET(NO_HELP);
	window_init();
    }

3361
3362
    curs_set(0);
    nodelay(edit, TRUE);
3363

3364
    blank_titlebar();
Chris Allegretta's avatar
Chris Allegretta committed
3365
    blank_edit();
3366
    blank_statusbar();
3367

3368
    wrefresh(topwin);
Chris Allegretta's avatar
Chris Allegretta committed
3369
    wrefresh(edit);
3370
    wrefresh(bottomwin);
3371
    napms(700);
3372

3373
    for (crpos = 0; crpos < CREDIT_LEN + editwinrows / 2; crpos++) {
3374
	if ((kbinput = wgetch(edit)) != ERR)
3375
	    break;
3376

3377
	if (crpos < CREDIT_LEN) {
3378
	    const char *what;
3379
	    size_t start_col;
3380

3381
3382
3383
	    if (credits[crpos] == NULL)
		what = _(xlcredits[xlpos++]);
	    else
3384
		what = credits[crpos];
3385

3386
	    start_col = COLS / 2 - strlenpt(what) / 2 - 1;
3387
	    mvwaddstr(edit, editwinrows - 1 - (editwinrows % 2),
3388
						start_col, what);
3389
	}
3390

3391
3392
3393
3394
	wrefresh(edit);

	if ((kbinput = wgetch(edit)) != ERR)
	    break;
3395
	napms(700);
3396

3397
	scrollok(edit, TRUE);
3398
	wscrl(edit, 1);
3399
	scrollok(edit, FALSE);
3400
	wrefresh(edit);
3401

3402
	if ((kbinput = wgetch(edit)) != ERR)
3403
	    break;
3404
	napms(700);
3405

3406
	scrollok(edit, TRUE);
3407
	wscrl(edit, 1);
3408
	scrollok(edit, FALSE);
3409
	wrefresh(edit);
3410
3411
    }

3412
3413
3414
    if (kbinput != ERR)
	ungetch(kbinput);

3415
    if (!old_more_space)
3416
	UNSET(MORE_SPACE);
3417
    if (!old_no_help)
3418
	UNSET(NO_HELP);
3419
    window_init();
3420

3421
    nodelay(edit, FALSE);
3422

3423
    total_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
3424
}
3425
#endif /* !DISABLE_EXTRA */