winio.c 111 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
#include <ctype.h>
26
#ifdef __linux__
27
#include <sys/ioctl.h>
28
#endif
Chris Allegretta's avatar
Chris Allegretta committed
29
30
#include <string.h>

31
32
33
34
35
36
#ifdef REVISION
#define BRANDING REVISION
#else
#define BRANDING PACKAGE_STRING
#endif

37
static int *key_buffer = NULL;
38
39
	/* The keystroke buffer, containing all the keystrokes we
	 * haven't handled yet at a given point. */
40
static size_t key_buffer_len = 0;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
41
	/* The length of the keystroke buffer. */
42
43
static bool solitary = FALSE;
	/* Whether an Esc arrived by itself -- not as leader of a sequence. */
44
static bool waiting_mode = TRUE;
45
	/* Whether getting a character will wait for a key to be pressed. */
46
static int statusblank = 0;
47
	/* The number of keystrokes left before we blank the statusbar. */
48
#ifdef USING_OLD_NCURSES
49
50
static bool seen_wide = FALSE;
	/* Whether we've seen a multicolumn character in the current line. */
51
#endif
52

53
54
/* Control character compatibility:
 *
55
56
57
58
59
 * - 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.
60
61
 * - Ctrl-8 (Ctrl-?) is Delete under ASCII, ANSI, VT100, and VT220,
 *          but is Backspace under VT320.
62
 *
63
 * Note: VT220 and VT320 also generate Esc [ 3 ~ for Delete.  By
64
65
 * default, xterm assumes it's running on a VT320 and generates Ctrl-8
 * (Ctrl-?) for Backspace and Esc [ 3 ~ for Delete.  This causes
66
 * problems for VT100-derived terminals such as the FreeBSD console,
67
 * which expect Ctrl-H for Backspace and Ctrl-8 (Ctrl-?) for Delete, and
68
69
70
71
72
73
74
75
76
 * 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
77
 * console, the FreeBSD console, the Mach console, xterm, rxvt, Eterm,
78
79
 * and Terminal, and some for iTerm2.  Among these, there are several
 * conflicts and omissions, outlined as follows:
80
81
82
83
84
85
 *
 * - 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
86
 *   keypad key, because the latter has no value when NumLock is off.)
87
88
89
90
 * - 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.)
91
 * - F9 on FreeBSD console == PageDown on Mach console; the former is
92
93
94
 *   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.)
95
 * - F10 on FreeBSD console == PageUp on Mach console; the former is
96
 *   omitted.  (Same as above.)
97
 * - F13 on FreeBSD console == End on Mach console; the former is
98
 *   omitted.  (Same as above.)
99
100
101
102
103
104
 * - 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
105
 *   omitted.  (Same as above.) */
106

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

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

119
120
121
122
    /* Just before reading in the first character, display any pending
     * screen updates. */
    doupdate();

123
    /* Read in the first character using whatever mode we're in. */
124
125
    input = wgetch(win);

126
#ifndef NANO_TINY
127
    if (the_window_resized) {
128
	ungetch(input);
129
	regenerate_screen();
130
	input = KEY_WINCH;
131
    }
132
133
#endif

134
    if (input == ERR && !waiting_mode)
135
136
137
138
139
140
141
142
	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)
143
	    die(_("Too many errors from stdin"));
144

145
#ifndef NANO_TINY
146
147
	if (the_window_resized) {
	    regenerate_screen();
148
149
	    input = KEY_WINCH;
	    break;
150
	}
151
152
#endif
	input = wgetch(win);
153
    }
154

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

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

168
169
170
171
    /* Read in the remaining characters using non-blocking input. */
    nodelay(win, TRUE);

    while (TRUE) {
172
	input = wgetch(win);
173

174
	/* If there aren't any more characters, stop reading. */
175
	if (input == ERR)
176
177
	    break;

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

186
    /* Restore waiting mode if it was on. */
187
    if (waiting_mode)
188
	nodelay(win, FALSE);
189
190

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
201
/* Return the length of the keystroke buffer. */
202
size_t get_key_buffer_len(void)
203
204
205
206
{
    return key_buffer_len;
}

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

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
220
221
222
    /* 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. */
223
224
225
    key_buffer_len += input_len;
    key_buffer = (int *)nrealloc(key_buffer, key_buffer_len *
	sizeof(int));
226

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
233
    /* Copy input to the beginning of the keystroke buffer. */
234
    memcpy(key_buffer, input, input_len * sizeof(int));
235
236
}

237
238
239
/* 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)
240
{
241
    unget_input(&kbinput, 1);
242

243
    if (metakey) {
244
	kbinput = ESC_CODE;
245
	unget_input(&kbinput, 1);
246
247
248
    }
}

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

257
258
    if (key_buffer_len == 0 && win != NULL)
	get_key_buffer(win);
259

260
261
    if (key_buffer_len == 0)
	return NULL;
262

263
    /* Limit the request to the number of available codes in the buffer. */
264
265
266
    if (input_len > key_buffer_len)
	input_len = key_buffer_len;

267
    /* Copy input_len codes from the head of the keystroke buffer. */
268
269
    input = (int *)nmalloc(input_len * sizeof(int));
    memcpy(input, key_buffer, input_len * sizeof(int));
270
    key_buffer_len -= input_len;
271

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

    return input;
285
286
}

287
/* Read in a single keystroke, ignoring any that are invalid. */
288
int get_kbinput(WINDOW *win)
289
{
290
    int kbinput = ERR;
291

292
    /* Extract one keystroke from the input stream. */
293
294
    while (kbinput == ERR)
	kbinput = parse_kbinput(win);
295

296
297
298
299
300
#ifdef DEBUG
    fprintf(stderr, "after parsing:  kbinput = %d, meta_key = %s\n",
	kbinput, meta_key ? "TRUE" : "FALSE");
#endif

301
    /* If we read from the edit window, blank the statusbar if needed. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
302
    if (win == edit)
303
304
	check_statusblank();

305
306
307
    return kbinput;
}

308
309
/* Extract a single keystroke from the input stream.  Translate escape
 * sequences and extended keypad codes into their corresponding values.
310
 * Set meta_key to TRUE when appropriate.  Supported extended keypad values
311
312
313
 * 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. */
314
int parse_kbinput(WINDOW *win)
315
{
316
    static int escapes = 0, byte_digits = 0;
317
    static bool double_esc = FALSE;
318
    int *kbinput, keycode, retval = ERR;
319

320
    meta_key = FALSE;
321
    shift_held = FALSE;
322

323
    /* Read in a character. */
324
325
    kbinput = get_input(win, 1);

326
    if (kbinput == NULL && !waiting_mode)
327
328
329
	return 0;

    while (kbinput == NULL)
330
	kbinput = get_input(win, 1);
331

332
333
334
    keycode = *kbinput;
    free(kbinput);

335
336
337
338
339
#ifdef DEBUG
    fprintf(stderr, "before parsing:  keycode = %d, escapes = %d, byte_digits = %d\n",
	keycode, escapes, byte_digits);
#endif

340
341
342
    if (keycode == ERR)
	return ERR;

343
    if (keycode == ESC_CODE) {
344
345
346
347
348
349
350
	/* 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;
351
352
    }

353
354
355
356
357
358
    switch (escapes) {
	case 0:
	    /* One non-escape: normal input mode. */
	    retval = keycode;
	    break;
	case 1:
359
360
361
	    if (keycode >= 0x80)
		retval = keycode;
	    else if ((keycode != 'O' && keycode != 'o' && keycode != '[') ||
362
			key_buffer_len == 0 || *key_buffer == ESC_CODE) {
363
364
365
366
367
368
369
370
371
		/* 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);
372
	    escapes = 0;
373
374
375
	    break;
	case 2:
	    if (double_esc) {
376
377
		/* An "ESC ESC [ X" sequence from Option+arrow, or
		 * an "ESC ESC [ x" sequence from Shift+Alt+arrow. */
378
379
380
381
382
383
384
385
		switch (keycode) {
		    case 'A':
			retval = KEY_HOME;
			break;
		    case 'B':
			retval = KEY_END;
			break;
		    case 'C':
386
			retval = CONTROL_RIGHT;
387
388
			break;
		    case 'D':
389
			retval = CONTROL_LEFT;
390
			break;
391
#ifndef NANO_TINY
392
393
394
395
396
397
398
399
400
401
402
403
		    case 'a':
			retval = shiftaltup;
			break;
		    case 'b':
			retval = shiftaltdown;
			break;
		    case 'c':
			retval = shiftaltright;
			break;
		    case 'd':
			retval = shiftaltleft;
			break;
404
#endif
405
406
407
		}
		double_esc = FALSE;
		escapes = 0;
408
	    } else if (key_buffer_len == 0) {
409
410
411
412
413
414
415
416
417
418
419
420
421
422
		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);

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

429
430
			/* Convert the decimal code to one or two bytes. */
			byte_mb = make_mbchar((long)byte, &byte_mb_len);
431

432
			seq = (int *)nmalloc(byte_mb_len * sizeof(int));
433
434
435
436

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

437
			/* Insert the byte(s) into the input buffer. */
438
439
440
441
			unget_input(seq, byte_mb_len);

			free(byte_mb);
			free(seq);
442
443
444

			byte_digits = 0;
			escapes = 0;
445
		    }
446
447
448
449
450
451
452
453
454
455
456
457
458
		} 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;
459
			retval = keycode;
460
		    }
461
		    escapes = 0;
462
463
		}
	    } else if (keycode == '[' && key_buffer_len > 0 &&
464
465
466
			(('A' <= *key_buffer && *key_buffer <= 'D') ||
			('a' <= *key_buffer && *key_buffer <= 'd'))) {
		/* An iTerm2/Eterm/rxvt sequence: ^[ ^[ [ X. */
467
468
469
470
471
		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);
472
473
		meta_key = TRUE;
		escapes = 0;
474
	    }
475
476
	    break;
	case 3:
477
	    if (key_buffer_len == 0)
478
479
480
481
482
483
484
485
486
487
		/* 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));
488
	    escapes = 0;
489
490
	    break;
    }
491

492
493
494
    if (retval == ERR)
	return ERR;

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

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

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

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

718
719
720
    return retval;
}

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

734
735
	switch (seq[3]) {
	    case '2':
736
737
738
739
740
		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. */
741
			shift_held = TRUE;
742
743
744
745
746
747
748
749
750
751
			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);
		}
752
753
		break;
	    case '5':
754
755
756
757
758
759
760
761
762
763
		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;
		}
764
765
		break;
	}
766

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

953
	switch (seq[3]) {
954
	    case '2':
955
956
957
958
959
		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. */
960
			shift_held = TRUE;
961
962
			return arrow_from_abcd(seq[4]);
		}
963
		break;
964
#ifndef NANO_TINY
965
966
967
968
969
970
971
972
973
974
975
976
	    case '3':
		switch (seq[4]) {
		    case 'A': /* Esc [ 1 ; 3 A == Alt-Up on xterm. */
			return ALT_UP;
		    case 'B': /* Esc [ 1 ; 3 B == Alt-Down on xterm. */
			return ALT_DOWN;
		    case 'C': /* Esc [ 1 ; 3 C == Alt-Right on xterm. */
			return ALT_RIGHT;
		    case 'D': /* Esc [ 1 ; 3 D == Alt-Left on xterm. */
			return ALT_LEFT;
		}
		break;
977
978
979
980
981
982
983
984
985
	    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. */
986
			return SHIFT_END;
987
		    case 'D': /* Esc [ 1 ; 4 D == Shift-Alt-Left on xterm. */
988
			return SHIFT_HOME;
989
990
991
		}
		break;
#endif
992
	    case '5':
993
994
995
996
997
998
999
1000
1001
		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;
1002
1003
1004
1005
		    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;
1006
		}
1007
		break;
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
#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;
1019
1020
1021
1022
		    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;
1023
1024
1025
		}
		break;
#endif
1026
	}
1027
1028
1029
1030

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

1223
    return ERR;
1224
1225
}

1226
1227
/* Return the equivalent arrow-key value for the first four letters
 * in the alphabet, common to many escape sequences. */
1228
int arrow_from_abcd(int kbinput)
1229
1230
1231
{
    switch (tolower(kbinput)) {
	case 'a':
1232
	    return KEY_UP;
1233
	case 'b':
1234
	    return KEY_DOWN;
1235
	case 'c':
1236
	    return KEY_RIGHT;
1237
	case 'd':
1238
	    return KEY_LEFT;
1239
1240
1241
1242
1243
	default:
	    return ERR;
    }
}

1244
/* Interpret the escape sequence in the keystroke buffer, the first
1245
1246
 * character of which is kbinput.  Assume that the keystroke buffer
 * isn't empty, and that the initial escape has already been read in. */
1247
int parse_escape_sequence(WINDOW *win, int kbinput)
1248
1249
1250
1251
1252
1253
1254
1255
{
    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);
1256
    seq_len = key_buffer_len;
1257
    seq = get_input(NULL, seq_len);
1258
    retval = convert_sequence(seq, seq_len);
1259
1260
1261

    free(seq);

1262
    /* If we got an unrecognized escape sequence, notify the user. */
1263
    if (retval == ERR) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1264
	if (win == edit) {
1265
1266
	    /* TRANSLATORS: This refers to a sequence of escape codes
	     * (from the keyboard) that nano does not know about. */
1267
	    statusline(ALERT, _("Unknown sequence"));
1268
	    suppress_cursorpos = FALSE;
1269
	    lastmessage = HUSH;
1270
	    if (currmenu == MMAIN) {
1271
		place_the_cursor();
1272
1273
		curs_set(1);
	    }
1274
1275
1276
	}
    }

1277
#ifdef DEBUG
1278
1279
    fprintf(stderr, "parse_escape_sequence(): kbinput = %d, seq_len = %lu, retval = %d\n",
		kbinput, (unsigned long)seq_len, retval);
1280
1281
1282
1283
1284
#endif

    return retval;
}

1285
1286
/* Translate a byte sequence: turn a three-digit decimal number (from
 * 000 to 255) into its corresponding byte value. */
1287
int get_byte_kbinput(int kbinput)
1288
{
1289
    static int byte_digits = 0, byte = 0;
1290
    int retval = ERR;
1291

1292
1293
    /* Increment the byte digit counter. */
    byte_digits++;
1294

1295
    switch (byte_digits) {
1296
	case 1:
1297
1298
	    /* First digit: This must be from zero to two.  Put it in
	     * the 100's position of the byte sequence holder. */
1299
	    if ('0' <= kbinput && kbinput <= '2')
1300
		byte = (kbinput - '0') * 100;
1301
	    else
1302
1303
		/* This isn't the start of a byte sequence.  Return this
		 * character as the result. */
1304
1305
1306
		retval = kbinput;
	    break;
	case 2:
1307
1308
1309
1310
	    /* 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. */
1311
1312
1313
	    if (('0' <= kbinput && kbinput <= '5') || (byte < 200 &&
		'6' <= kbinput && kbinput <= '9'))
		byte += (kbinput - '0') * 10;
1314
	    else
1315
1316
		/* This isn't the second digit of a byte sequence.
		 * Return this character as the result. */
1317
1318
1319
		retval = kbinput;
	    break;
	case 3:
1320
	    /* Third digit: This must be from zero to five if the first
1321
1322
1323
	     * 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. */
1324
1325
	    if (('0' <= kbinput && kbinput <= '5') || (byte < 250 &&
		'6' <= kbinput && kbinput <= '9')) {
1326
		byte += kbinput - '0';
1327
		/* The byte sequence is complete. */
1328
		retval = byte;
1329
	    } else
1330
1331
		/* This isn't the third digit of a byte sequence.
		 * Return this character as the result. */
1332
1333
		retval = kbinput;
	    break;
1334
	default:
1335
1336
1337
	    /* If there are more than three digits, return this
	     * character as the result.  (Maybe we should produce an
	     * error instead?) */
1338
1339
1340
1341
1342
1343
1344
1345
	    retval = kbinput;
	    break;
    }

    /* If we have a result, reset the byte digit counter and the byte
     * sequence holder. */
    if (retval != ERR) {
	byte_digits = 0;
1346
	byte = 0;
1347
1348
1349
    }

#ifdef DEBUG
1350
    fprintf(stderr, "get_byte_kbinput(): kbinput = %d, byte_digits = %d, byte = %d, retval = %d\n", kbinput, byte_digits, byte, retval);
1351
1352
1353
1354
1355
#endif

    return retval;
}

1356
#ifdef ENABLE_UTF8
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1357
/* If the character in kbinput is a valid hexadecimal digit, multiply it
1358
 * by factor and add the result to uni, and return ERR to signify okay. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1359
1360
1361
1362
1363
1364
1365
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
1366
1367
	/* The character isn't hexadecimal; give it as the result. */
	return (long)kbinput;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1368

1369
    return ERR;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1370
1371
}

1372
/* Translate a Unicode sequence: turn a six-digit hexadecimal number
1373
 * (from 000000 to 10FFFF, case-insensitive) into its corresponding
1374
 * multibyte value. */
1375
long get_unicode_kbinput(WINDOW *win, int kbinput)
1376
{
1377
1378
1379
    static int uni_digits = 0;
    static long uni = 0;
    long retval = ERR;
1380

1381
    /* Increment the Unicode digit counter. */
1382
    uni_digits++;
1383

1384
    switch (uni_digits) {
1385
	case 1:
1386
1387
1388
1389
	    /* 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')
1390
		uni = (kbinput - '0') * 0x100000;
1391
1392
1393
1394
	    else
		retval = kbinput;
	    break;
	case 2:
1395
1396
1397
	    /* 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)
1398
		retval = add_unicode_digit(kbinput, 0x10000, &uni);
1399
1400
1401
1402
	    else
		retval = kbinput;
	    break;
	case 3:
1403
	    /* Later digits may be any hexadecimal value. */
1404
	    retval = add_unicode_digit(kbinput, 0x1000, &uni);
1405
	    break;
1406
	case 4:
1407
	    retval = add_unicode_digit(kbinput, 0x100, &uni);
1408
	    break;
1409
	case 5:
1410
	    retval = add_unicode_digit(kbinput, 0x10, &uni);
1411
	    break;
1412
	case 6:
1413
	    retval = add_unicode_digit(kbinput, 0x1, &uni);
1414
1415
	    /* If also the sixth digit was a valid hexadecimal value, then
	     * the Unicode sequence is complete, so return it. */
1416
	    if (retval == ERR)
1417
		retval = uni;
1418
1419
	    break;
    }
1420

1421
    /* Show feedback only when editing, not when at a prompt. */
1422
    if (retval == ERR && win == edit) {
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
	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);
    }
1433

1434
#ifdef DEBUG
1435
1436
    fprintf(stderr, "get_unicode_kbinput(): kbinput = %d, uni_digits = %d, uni = %ld, retval = %ld\n",
						kbinput, uni_digits, uni, retval);
1437
1438
#endif

1439
1440
1441
1442
    /* If we have an end result, reset the Unicode digit counter. */
    if (retval != ERR)
	uni_digits = 0;

1443
1444
    return retval;
}
1445
#endif /* ENABLE_UTF8 */
1446

1447
1448
1449
1450
1451
1452
/* Translate a control character sequence: turn an ASCII non-control
 * character into its corresponding control character. */
int get_control_kbinput(int kbinput)
{
    int retval;

1453
    /* Ctrl-Space (Ctrl-2, Ctrl-@, Ctrl-`) */
1454
    if (kbinput == ' ' || kbinput == '2')
1455
	retval = 0;
1456
1457
    /* Ctrl-/ (Ctrl-7, Ctrl-_) */
    else if (kbinput == '/')
1458
	retval = 31;
1459
    /* Ctrl-3 (Ctrl-[, Esc) to Ctrl-7 (Ctrl-/, Ctrl-_) */
1460
1461
1462
    else if ('3' <= kbinput && kbinput <= '7')
	retval = kbinput - 24;
    /* Ctrl-8 (Ctrl-?) */
1463
    else if (kbinput == '8' || kbinput == '?')
1464
	retval = DEL_CODE;
1465
1466
    /* Ctrl-@ (Ctrl-Space, Ctrl-2, Ctrl-`) to Ctrl-_ (Ctrl-/, Ctrl-7) */
    else if ('@' <= kbinput && kbinput <= '_')
1467
	retval = kbinput - '@';
1468
1469
    /* Ctrl-` (Ctrl-2, Ctrl-Space, Ctrl-@) to Ctrl-~ (Ctrl-6, Ctrl-^) */
    else if ('`' <= kbinput && kbinput <= '~')
1470
	retval = kbinput - '`';
1471
1472
1473
    else
	retval = kbinput;

1474
#ifdef DEBUG
1475
    fprintf(stderr, "get_control_kbinput(): kbinput = %d, retval = %d\n", kbinput, retval);
1476
1477
#endif

1478
1479
    return retval;
}
1480

1481
/* Read in a stream of characters verbatim, and return the length of the
1482
1483
1484
1485
 * string in kbinput_len.  Assume nodelay(win) is FALSE. */
int *get_verbatim_kbinput(WINDOW *win, size_t *kbinput_len)
{
    int *retval;
1486

1487
    /* Turn off flow control characters if necessary so that we can type
1488
1489
     * them in verbatim, and turn the keypad off if necessary so that we
     * don't get extended keypad values. */
1490
1491
    if (ISSET(PRESERVE))
	disable_flow_control();
1492
1493
    if (!ISSET(REBIND_KEYPAD))
	keypad(win, FALSE);
1494

1495
    /* Read in one keycode, or one or two escapes. */
1496
    retval = parse_verbatim_kbinput(win, kbinput_len);
1497

1498
    /* If the code is invalid in the current mode, discard it. */
1499
1500
    if (retval != NULL && ((*retval == '\n' && as_an_at) ||
				(*retval == '\0' && !as_an_at))) {
1501
1502
1503
1504
	*kbinput_len = 0;
	beep();
    }

1505
    /* Turn flow control characters back on if necessary and turn the
1506
     * keypad back on if necessary now that we're done. */
1507
1508
    if (ISSET(PRESERVE))
	enable_flow_control();
1509
1510
1511
1512
1513
1514
    /* 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);
    }
1515

1516
    return retval;
1517
1518
}

1519
1520
1521
1522
/* 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). */
1523
int *parse_verbatim_kbinput(WINDOW *win, size_t *count)
1524
{
1525
    int *kbinput;
1526

1527
    /* Read in the first code. */
1528
1529
    while ((kbinput = get_input(win, 1)) == NULL)
	;
1530

1531
#ifndef NANO_TINY
1532
1533
1534
    /* When the window was resized, abort and return nothing. */
    if (*kbinput == KEY_WINCH) {
	free(kbinput);
1535
	*count = 0;
1536
1537
	return NULL;
    }
1538
#endif
1539

1540
1541
    *count = 1;

1542
#ifdef ENABLE_UTF8
1543
    if (using_utf8()) {
1544
	/* Check whether the first code is a valid starter digit: 0 or 1. */
1545
	long unicode = get_unicode_kbinput(win, *kbinput);
1546

1547
	/* If the first code isn't the digit 0 nor 1, put it back. */
1548
	if (unicode != ERR)
1549
	    unget_input(kbinput, 1);
1550
1551
	/* Otherwise, continue reading in digits until we have a complete
	 * Unicode value, and put back the corresponding byte(s). */
1552
	else {
1553
	    char *multibyte;
1554
	    int onebyte, i;
1555

1556
	    while (unicode == ERR) {
1557
		free(kbinput);
1558
1559
		while ((kbinput = get_input(win, 1)) == NULL)
		    ;
1560
		unicode = get_unicode_kbinput(win, *kbinput);
1561
	    }
1562

1563
	    /* Convert the Unicode value to a multibyte sequence. */
1564
	    multibyte = make_mbchar(unicode, (int *)count);
1565

1566
	    /* Insert the multibyte sequence into the input buffer. */
1567
	    for (i = *count; i > 0 ; i--) {
1568
		onebyte = (unsigned char)multibyte[i - 1];
1569
1570
		unget_input(&onebyte, 1);
	    }
1571

1572
	    free(multibyte);
1573
	}
1574
    } else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1575
#endif /* ENABLE_UTF8 */
1576
	/* Put back the first code. */
1577
	unget_input(kbinput, 1);
1578

1579
1580
    free(kbinput);

1581
    /* If this is an iTerm/Eterm/rxvt double escape, take both Escapes. */
1582
1583
1584
1585
1586
    if (key_buffer_len > 3 && *key_buffer == ESC_CODE &&
		key_buffer[1] == ESC_CODE && key_buffer[2] == '[')
	*count = 2;

    return get_input(NULL, *count);
1587
1588
}

1589
#ifdef ENABLE_MOUSE
1590
/* Handle any mouse event that may have occurred.  We currently handle
1591
1592
1593
1594
1595
1596
1597
 * 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
1598
1599
1600
1601
1602
1603
 * 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. */
1604
int get_mouseinput(int *mouse_x, int *mouse_y, bool allow_shortcuts)
1605
1606
{
    MEVENT mevent;
1607
    bool in_bottomwin;
1608
    subnfunc *f;
1609
1610
1611
1612
1613
1614

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

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

1617
    /* Save the screen coordinates where the mouse event took place. */
1618
    *mouse_x = mevent.x - margin;
1619
    *mouse_y = mevent.y;
1620

1621
1622
    in_bottomwin = wenclose(bottomwin, *mouse_y, *mouse_x);

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1623
    /* Handle releases/clicks of the first mouse button. */
1624
    if (mevent.bstate & (BUTTON1_RELEASED | BUTTON1_CLICKED)) {
1625
1626
	/* If we're allowing shortcuts, the current shortcut list is
	 * being displayed on the last two lines of the screen, and the
1627
1628
1629
	 * 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. */
1630
	if (allow_shortcuts && !ISSET(NO_HELP) && in_bottomwin) {
1631
1632
1633
1634
	    int i;
		/* The width of all the shortcuts, except for the last
		 * two, in the shortcut list in bottomwin. */
	    int j;
1635
		/* The calculated index number of the clicked item. */
1636
1637
	    size_t number;
		/* The number of available shortcuts in the current menu. */
1638

1639
1640
1641
1642
1643
1644
1645
1646
1647
	    /* 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. */
1648
		*mouse_x = mevent.x - margin;
1649
1650
1651
1652
		*mouse_y = mevent.y;

		return 0;
	    }
1653

1654
	    /* Determine how many shortcuts are being shown. */
1655
	    number = length_of_list(currmenu);
1656

1657
1658
	    if (number > MAIN_VISIBLE)
		number = MAIN_VISIBLE;
1659

1660
1661
1662
	    /* 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. */
1663
	    if (number < 2)
1664
1665
		i = COLS / (MAIN_VISIBLE / 2);
	    else
1666
		i = COLS / ((number / 2) + (number % 2));
1667

1668
1669
	    /* Calculate the one-based index in the shortcut list. */
	    j = (*mouse_x / i) * 2 + *mouse_y;
1670

1671
	    /* Adjust the index if we hit the last two wider ones. */
1672
	    if ((j > number) && (*mouse_x % i < COLS % i))
1673
		j -= 2;
1674

1675
1676
	    /* Ignore releases/clicks of the first mouse button beyond
	     * the last shortcut. */
1677
	    if (j > number)
1678
		return 2;
1679

1680
1681
	    /* Go through the list of functions to determine which
	     * shortcut in the current menu we released/clicked on. */
1682
	    for (f = allfuncs; f != NULL; f = f->next) {
1683
		if ((f->menus & currmenu) == 0)
1684
1685
		    continue;
		if (first_sc_for(currmenu, f->scfunc) == NULL)
1686
		    continue;
1687
1688
		/* Tick off an actually shown shortcut. */
		j -= 1;
1689
1690
		if (j == 0)
		    break;
1691
1692
	    }

1693
	    /* And put the corresponding key into the keyboard buffer. */
1694
	    if (f != NULL) {
1695
		const sc *s = first_sc_for(currmenu, f->scfunc);
1696
		unget_kbinput(s->keycode, s->meta);
1697
	    }
1698
	    return 1;
1699
	} else
1700
1701
	    /* Handle releases/clicks of the first mouse button that
	     * aren't on the current shortcut list elsewhere. */
1702
	    return 0;
1703
    }
1704
1705
#if NCURSES_MOUSE_VERSION >= 2
    /* Handle presses of the fourth mouse button (upward rolls of the
1706
1707
1708
     * 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
1709
	bool in_edit = wenclose(edit, *mouse_y, *mouse_x);
1710

1711
1712
1713
1714
	if (in_bottomwin)
	    /* Translate the mouse event coordinates so that they're
	     * relative to bottomwin. */
	    wmouse_trafo(bottomwin, mouse_y, mouse_x, FALSE);
1715

1716
	if (in_edit || (in_bottomwin && *mouse_y == 0)) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1717
1718
	    int i;

1719
1720
1721
1722
1723
	    /* 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) ?
1724
				KEY_PPAGE : KEY_NPAGE, FALSE);
1725
1726
1727
1728
1729
1730
1731

	    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;
1732
1733
    }
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1734
1735
1736

    /* Ignore all other mouse events. */
    return 2;
1737
}
1738
#endif /* ENABLE_MOUSE */
1739

1740
1741
1742
1743
/* 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. */
1744
const sc *get_shortcut(int *kbinput)
1745
{
1746
    sc *s;
1747

1748
#ifdef DEBUG
1749
1750
    fprintf(stderr, "get_shortcut(): kbinput = %d, meta_key = %s -- ",
				*kbinput, meta_key ? "TRUE" : "FALSE");
1751
1752
#endif

1753
    for (s = sclist; s != NULL; s = s->next) {
1754
	if ((s->menus & currmenu) && *kbinput == s->keycode &&
1755
					meta_key == s->meta) {
1756
#ifdef DEBUG
1757
1758
	    fprintf (stderr, "matched seq '%s'  (menu is %x from %x)\n",
				s->keystr, currmenu, s->menus);
1759
#endif
1760
	    return s;
1761
1762
	}
    }
1763
#ifdef DEBUG
1764
    fprintf (stderr, "matched nothing\n");
1765
#endif
1766
1767
1768
1769

    return NULL;
}

1770
1771
/* Move to (x, y) in win, and display a line of n spaces with the
 * current attributes. */
1772
void blank_row(WINDOW *win, int y, int x, int n)
1773
1774
{
    wmove(win, y, x);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1775

1776
1777
1778
1779
    for (; n > 0; n--)
	waddch(win, ' ');
}

1780
/* Blank the first line of the top portion of the window. */
1781
void blank_titlebar(void)
Chris Allegretta's avatar
Chris Allegretta committed
1782
{
1783
    blank_row(topwin, 0, 0, COLS);
1784
1785
}

1786
/* Blank all the lines of the middle portion of the window, i.e. the
1787
 * edit window. */
Chris Allegretta's avatar
Chris Allegretta committed
1788
1789
void blank_edit(void)
{
1790
    int row;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1791

1792
1793
    for (row = 0; row < editwinrows; row++)
	blank_row(edit, row, 0, COLS);
Chris Allegretta's avatar
Chris Allegretta committed
1794
1795
}

1796
/* Blank the first line of the bottom portion of the window. */
Chris Allegretta's avatar
Chris Allegretta committed
1797
1798
void blank_statusbar(void)
{
1799
    blank_row(bottomwin, 0, 0, COLS);
Chris Allegretta's avatar
Chris Allegretta committed
1800
1801
}

1802
1803
/* If the NO_HELP flag isn't set, blank the last two lines of the bottom
 * portion of the window. */
1804
1805
void blank_bottombars(void)
{
1806
    if (!ISSET(NO_HELP) && LINES > 4) {
1807
1808
	blank_row(bottomwin, 1, 0, COLS);
	blank_row(bottomwin, 2, 0, COLS);
1809
1810
1811
    }
}

1812
1813
/* Check if the number of keystrokes needed to blank the statusbar has
 * been pressed.  If so, blank the statusbar, unless constant cursor
1814
 * position display is on and we are in the editing screen. */
1815
void check_statusblank(void)
Chris Allegretta's avatar
Chris Allegretta committed
1816
{
1817
1818
1819
1820
1821
1822
    if (statusblank == 0)
	return;

    statusblank--;

    /* When editing and 'constantshow' is active, skip the blanking. */
1823
    if (currmenu == MMAIN && ISSET(CONSTANT_SHOW))
1824
1825
1826
1827
1828
	return;

    if (statusblank == 0) {
	blank_statusbar();
	wnoutrefresh(bottomwin);
Chris Allegretta's avatar
Chris Allegretta committed
1829
    }
1830
1831
1832
1833

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

1836
1837
1838
1839
1840
1841
1842
/* 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)
1843
{
1844
1845
1846
1847
    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. */
1848
    char *converted;
1849
	/* The expanded string we will return. */
1850
    size_t index = 0;
1851
	/* Current position in converted. */
1852
    size_t beyond = column + span;
1853
	/* The column number just beyond the last shown character. */
1854

1855
#ifdef USING_OLD_NCURSES
1856
    seen_wide = FALSE;
1857
#endif
1858
    buf += start_index;
1859

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

1863
    /* If the first character starts before the left edge, or would be
1864
     * overwritten by a "$" token, then show placeholders instead. */
1865
1866
    if (*buf != '\0' && *buf != '\t' && (start_col < column ||
			(start_col > 0 && isdata && !ISSET(SOFTWRAP)))) {
1867
	if (is_cntrl_mbchar(buf)) {
1868
	    if (start_col < column) {
1869
		converted[index++] = control_mbrep(buf, isdata);
1870
		column++;
1871
		buf += parse_mbchar(buf, NULL, NULL);
1872
	    }
1873
	}
1874
#ifdef ENABLE_UTF8
1875
	else if (mbwidth(buf) == 2) {
1876
	    if (start_col == column) {
1877
		converted[index++] = ' ';
1878
		column++;
1879
1880
	    }

1881
1882
	    /* Display the right half of a two-column character as '<'. */
	    converted[index++] = '<';
1883
	    column++;
1884
	    buf += parse_mbchar(buf, NULL, NULL);
1885
	}
1886
#endif
1887
1888
    }

1889
    while (*buf != '\0' && column < beyond) {
1890
	int charlength, charwidth = 1;
1891

1892
	if (*buf == ' ') {
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
	    /* 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++] = ' ';
1903
	    column++;
1904
1905
	    buf++;
	    continue;
1906
	} else if (*buf == '\t') {
1907
	    /* Show a tab as a visible character, or as as a space. */
1908
#ifndef NANO_TINY
1909
1910
	    if (ISSET(WHITESPACE_DISPLAY) && (index > 0 || !isdata ||
			!ISSET(SOFTWRAP) || column % tabsize == 0)) {
1911
		int i = 0;
1912

1913
1914
		while (i < whitespace_len[0])
		    converted[index++] = whitespace[i++];
1915
	    } else
1916
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1917
		converted[index++] = ' ';
1918
	    column++;
1919
	    /* Fill the tab up with the required number of spaces. */
1920
	    while (column % tabsize != 0 && column < beyond) {
1921
		converted[index++] = ' ';
1922
		column++;
1923
	    }
1924
1925
1926
1927
	    buf++;
	    continue;
	}

1928
	charlength = length_of_char(buf, &charwidth);
1929

1930
	/* If buf contains a control character, represent it. */
1931
	if (is_cntrl_mbchar(buf)) {
1932
	    converted[index++] = '^';
1933
	    converted[index++] = control_mbrep(buf, isdata);
1934
	    column += 2;
1935
1936
1937
	    buf += charlength;
	    continue;
	}
1938

1939
1940
1941
1942
	/* If buf contains a valid non-control character, simply copy it. */
	if (charlength > 0) {
	    for (; charlength > 0; charlength--)
		converted[index++] = *(buf++);
1943

1944
	    column += charwidth;
1945
#ifdef USING_OLD_NCURSES
1946
	    if (charwidth > 1)
1947
		seen_wide = TRUE;
1948
#endif
1949
	    continue;
1950
1951
	}

1952
1953
1954
1955
	/* Represent an invalid sequence with the Replacement Character. */
	converted[index++] = '\xEF';
	converted[index++] = '\xBF';
	converted[index++] = '\xBD';
1956
	column++;
1957
1958
1959
1960
1961
	buf++;

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

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

1968
1969
#ifdef ENABLE_UTF8
	/* Display the left half of a two-column character as '>'. */
1970
	if (mbwidth(converted + index) == 2)
1971
1972
1973
1974
	    converted[index++] = '>';
#endif
    }

1975
1976
    /* Null-terminate the converted string. */
    converted[index] = '\0';
1977

1978
    return converted;
1979
1980
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1981
1982
/* 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
1983
1984
1985
 * 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. */
1986
void titlebar(const char *path)
Chris Allegretta's avatar
Chris Allegretta committed
1987
{
1988
1989
1990
1991
1992
1993
    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. */
1994
1995
    const char *branding = BRANDING;
	/* What is shown in the top left corner. */
1996
1997
1998
1999
    const char *prefix = "";
	/* What is shown before the path -- "File:", "DIR:", or "". */
    const char *state = "";
	/* The state of the current buffer -- "Modified", "View", or "". */
2000
2001
    char *caption;
	/* The presentable form of the pathname. */
2002

2003
2004
2005
2006
    /* If the screen is too small, there is no titlebar. */
    if (topwin == NULL)
	return;

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

2009
    wattron(topwin, interface_color_pair[TITLE_BAR]);
2010

2011
    blank_titlebar();
2012
    as_an_at = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
2013

2014
2015
2016
    /* 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
2017

2018
    /* Figure out the path, prefix and state strings. */
2019
    if (inhelp)
2020
	branding = "";
2021
#ifdef ENABLE_BROWSER
2022
    else if (path != NULL)
2023
2024
	prefix = _("DIR:");
#endif
2025
    else {
2026
2027
2028
2029
2030
2031
	if (openfile->filename[0] == '\0')
	    path = _("New Buffer");
	else {
	    path = openfile->filename;
	    prefix = _("File:");
	}
2032

2033
2034
2035
2036
	if (openfile->modified)
	    state = _("Modified");
	else if (ISSET(VIEW_MODE))
	    state = _("View");
2037

2038
2039
	pluglen = strlenpt(_("Modified")) + 1;
    }
2040

2041
    /* Determine the widths of the four elements, including their padding. */
2042
    verlen = strlenpt(branding) + 3;
2043
2044
2045
    prefixlen = strlenpt(prefix);
    if (prefixlen > 0)
	prefixlen++;
2046
    pathlen = strlenpt(path);
2047
2048
2049
2050
    statelen = strlenpt(state) + 2;
    if (statelen > 2) {
	pathlen++;
	pluglen = 0;
2051
2052
    }

2053
2054
    /* Only print the version message when there is room for it. */
    if (verlen + prefixlen + pathlen + pluglen + statelen <= COLS)
2055
	mvwaddstr(topwin, 0, 2, branding);
2056
2057
2058
2059
2060
2061
2062
2063
2064
    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;
2065
2066
2067
	}
    }

2068
2069
2070
2071
    /* If we have side spaces left, center the path name. */
    if (verlen > 0)
	offset = verlen + (COLS - (verlen + pluglen + statelen) -
					(prefixlen + pathlen)) / 2;
2072

2073
2074
2075
2076
2077
2078
2079
2080
2081
    /* 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. */
2082
2083
2084
2085
2086
    if (pathlen + pluglen + statelen <= COLS) {
	caption = display_string(path, 0, pathlen, FALSE);
	waddstr(topwin, caption);
	free(caption);
    } else if (5 + statelen <= COLS) {
2087
	waddstr(topwin, "...");
2088
	caption = display_string(path, 3 + pathlen - COLS + statelen,
2089
					COLS - statelen, FALSE);
2090
2091
	waddstr(topwin, caption);
	free(caption);
2092
    }
2093

2094
2095
2096
2097
2098
2099
    /* 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));

2100
    wattroff(topwin, interface_color_pair[TITLE_BAR]);
2101

2102
    wrefresh(topwin);
Chris Allegretta's avatar
Chris Allegretta committed
2103
2104
}

2105
2106
2107
2108
2109
2110
/* Display a normal message on the statusbar, quietly. */
void statusbar(const char *msg)
{
    statusline(HUSH, msg);
}

2111
2112
2113
2114
2115
2116
2117
2118
2119
/* 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);
}

2120
/* Display a message on the statusbar, and set suppress_cursorpos to
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2121
2122
 * TRUE, so that the message won't be immediately overwritten if
 * constant cursor position display is on. */
2123
void statusline(message_type importance, const char *msg, ...)
2124
2125
{
    va_list ap;
2126
    static int alerts = 0;
2127
    char *compound, *message;
2128
    size_t start_col;
2129
    bool bracketed;
2130
2131
2132
2133
#ifndef NANO_TINY
    bool old_whitespace = ISSET(WHITESPACE_DISPLAY);

    UNSET(WHITESPACE_DISPLAY);
2134
#endif
2135
2136
2137
2138
2139

    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(). */
2140
    if (isendwin()) {
2141
2142
2143
2144
2145
	vfprintf(stderr, msg, ap);
	va_end(ap);
	return;
    }

2146
2147
2148
2149
2150
    /* If there already was an alert message, ignore lesser ones. */
    if ((lastmessage == ALERT && importance != ALERT) ||
		(lastmessage == MILD && importance == HUSH))
	return;

2151
2152
2153
2154
2155
2156
    /* 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. */
2157
    if (lastmessage == ALERT && alerts < 4 && !ISSET(NO_PAUSES))
2158
2159
	napms(1200);

2160
    if (importance == ALERT) {
2161
	if (++alerts > 3 && !ISSET(NO_PAUSES))
2162
	    msg = _("Further warnings were suppressed");
2163
	beep();
2164
    }
2165
2166

    lastmessage = importance;
2167

2168
2169
2170
    /* Turn the cursor off while fiddling in the statusbar. */
    curs_set(0);

2171
2172
    blank_statusbar();

2173
    /* Construct the message out of all the arguments. */
2174
2175
    compound = charalloc(MAXCHARLEN * (COLS + 1));
    vsnprintf(compound, MAXCHARLEN * (COLS + 1), msg, ap);
2176
    va_end(ap);
2177
2178
    message = display_string(compound, 0, COLS, FALSE);
    free(compound);
2179

2180
2181
    start_col = (COLS - strlenpt(message)) / 2;
    bracketed = (start_col > 1);
2182

2183
    wmove(bottomwin, 0, (bracketed ? start_col - 2 : start_col));
2184
    wattron(bottomwin, interface_color_pair[STATUS_BAR]);
2185
2186
    if (bracketed)
	waddstr(bottomwin, "[ ");
2187
2188
    waddstr(bottomwin, message);
    free(message);
2189
2190
    if (bracketed)
	waddstr(bottomwin, " ]");
2191
    wattroff(bottomwin, interface_color_pair[STATUS_BAR]);
2192

2193
2194
2195
2196
    /* Defeat a VTE/Konsole bug, where the cursor can go off-limits. */
    if (ISSET(CONSTANT_SHOW) && ISSET(NO_HELP))
	wmove(bottomwin, 0, 0);

2197
    /* Push the message to the screen straightaway. */
2198
    wrefresh(bottomwin);
2199

2200
    suppress_cursorpos = TRUE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2201

2202
#ifndef NANO_TINY
2203
2204
    if (old_whitespace)
	SET(WHITESPACE_DISPLAY);
2205
#endif
2206
2207
2208

    /* If doing quick blanking, blank the statusbar after just one keystroke.
     * Otherwise, blank it after twenty-six keystrokes, as Pico does. */
2209
2210
2211
2212
    if (ISSET(QUICK_BLANK))
	statusblank = 1;
    else
	statusblank = 26;
2213
2214
}

2215
2216
/* Display the shortcut list corresponding to menu on the last two rows
 * of the bottom portion of the window. */
2217
void bottombars(int menu)
Chris Allegretta's avatar
Chris Allegretta committed
2218
{
2219
    size_t number, itemwidth, i;
2220
2221
    subnfunc *f;
    const sc *s;
2222

2223
2224
2225
    /* Set the global variable to the given menu. */
    currmenu = menu;

2226
    if (ISSET(NO_HELP) || LINES < 5)
Chris Allegretta's avatar
Chris Allegretta committed
2227
2228
	return;

2229
    /* Determine how many shortcuts there are to show. */
2230
    number = length_of_list(menu);
2231

2232
2233
    if (number > MAIN_VISIBLE)
	number = MAIN_VISIBLE;
2234

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

2238
    /* If there is no room, don't print anything. */
2239
    if (itemwidth == 0)
2240
2241
	return;

2242
    blank_bottombars();
2243

2244
2245
    /* Display the first number of shortcuts in the given menu that
     * have a key combination assigned to them. */
2246
    for (f = allfuncs, i = 0; i < number && f != NULL; f = f->next) {
2247
	if ((f->menus & menu) == 0)
2248
	    continue;
2249

2250
	s = first_sc_for(menu, f->scfunc);
2251
	if (s == NULL)
2252
	    continue;
2253
2254

	wmove(bottomwin, 1 + i % 2, (i / 2) * itemwidth);
2255

2256
	onekey(s->keystr, _(f->desc), itemwidth + (COLS % itemwidth));
2257
	i++;
Chris Allegretta's avatar
Chris Allegretta committed
2258
    }
2259

2260
    /* Defeat a VTE bug by homing the cursor and forcing a screen update. */
2261
    wmove(bottomwin, 0, 0);
2262
    wrefresh(bottomwin);
Chris Allegretta's avatar
Chris Allegretta committed
2263
2264
}

2265
2266
/* 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
2267
 * to write at most length characters, even if length is very small and
2268
2269
 * keystroke and desc are long.  Note that waddnstr(,,(size_t)-1) adds
 * the whole string!  We do not bother padding the entry with blanks. */
2270
void onekey(const char *keystroke, const char *desc, int length)
Chris Allegretta's avatar
Chris Allegretta committed
2271
{
2272
2273
    assert(keystroke != NULL && desc != NULL);

2274
    wattron(bottomwin, interface_color_pair[KEY_COMBO]);
2275
    waddnstr(bottomwin, keystroke, actual_x(keystroke, length));
2276
    wattroff(bottomwin, interface_color_pair[KEY_COMBO]);
2277

2278
    length -= strlenpt(keystroke) + 1;
2279

2280
    if (length > 0) {
2281
	waddch(bottomwin, ' ');
2282
	wattron(bottomwin, interface_color_pair[FUNCTION_TAG]);
2283
	waddnstr(bottomwin, desc, actual_x(desc, length));
2284
	wattroff(bottomwin, interface_color_pair[FUNCTION_TAG]);
Chris Allegretta's avatar
Chris Allegretta committed
2285
2286
2287
    }
}

2288
/* Redetermine current_y from the position of current relative to edittop,
2289
 * and put the cursor in the edit window at (current_y, "current_x"). */
2290
void place_the_cursor(void)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2291
{
2292
    ssize_t row = 0;
2293
    size_t col, xpt = xplustabs();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2294

2295
#ifndef NANO_TINY
2296
    if (ISSET(SOFTWRAP)) {
2297
	filestruct *line = openfile->edittop;
2298
	size_t leftedge;
2299

2300
	row -= chunk_for(openfile->firstcolumn, openfile->edittop);
2301

2302
	/* Calculate how many rows the lines from edittop to current use. */
2303
	while (line != NULL && line != openfile->current) {
2304
	    row += number_of_chunks_in(line) + 1;
2305
2306
2307
	    line = line->next;
	}

2308
	/* Add the number of wraps in the current line before the cursor. */
2309
	row += get_chunk_and_edge(xpt, openfile->current, &leftedge);
2310
	col = xpt - leftedge;
2311
2312
2313
    } else
#endif
    {
2314
2315
	row = openfile->current->lineno - openfile->edittop->lineno;
	col = xpt - get_page_start(xpt);
2316
    }
2317
2318
2319
2320

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

2321
    openfile->current_y = row;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2322
}
Chris Allegretta's avatar
Chris Allegretta committed
2323

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

2345
    assert(openfile != NULL && fileptr != NULL && converted != NULL);
2346
2347
2348
    assert(strlenpt(converted) <= editwincols);

#ifdef ENABLE_LINENUMBERS
2349
    /* If line numbering is switched on, put a line number in front of
2350
2351
     * the text -- but only for the parts that are not softwrapped. */
    if (margin > 0) {
2352
	wattron(edit, interface_color_pair[LINE_NUMBER]);
2353
#ifndef NANO_TINY
2354
	if (ISSET(SOFTWRAP) && from_x != 0)
2355
	    mvwprintw(edit, row, 0, "%*s", margin - 1, " ");
2356
2357
	else
#endif
2358
	    mvwprintw(edit, row, 0, "%*ld", margin - 1, (long)fileptr->lineno);
2359
	wattroff(edit, interface_color_pair[LINE_NUMBER]);
2360
2361
    }
#endif
2362

2363
2364
2365
    /* 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);
2366

2367
#ifdef USING_OLD_NCURSES
2368
2369
2370
    /* 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. */
2371
    if (seen_wide)
2372
	wredrawln(edit, row, 1);
2373
#endif
2374

2375
#ifndef DISABLE_COLOR
2376
    /* If color syntaxes are available and turned on, apply them. */
2377
    if (openfile->colorstrings != NULL && !ISSET(NO_COLOR_SYNTAX)) {
2378
	const colortype *varnish = openfile->colorstrings;
2379

2380
2381
2382
2383
	/* If there are multiline regexes, make sure there is a cache. */
	if (openfile->syntax->nmultis > 0)
	    alloc_multidata_if_needed(fileptr);

2384
	/* Iterate through all the coloring regexes. */
2385
	for (; varnish != NULL; varnish = varnish->next) {
2386
2387
	    size_t index = 0;
		/* Where in the line we currently begin looking for a match. */
2388
	    int start_col;
2389
		/* The starting column of a piece to paint.  Zero-based. */
2390
	    int paintlen = 0;
2391
2392
2393
		/* The number of characters to paint. */
	    const char *thetext;
		/* The place in converted from where painting starts. */
2394
2395
	    regmatch_t match;
		/* The match positions of a single-line regex. */
2396
2397
2398
2399
2400
2401
	    const filestruct *start_line = fileptr->prev;
		/* The first line before fileptr that matches 'start'. */
	    const filestruct *end_line = fileptr;
		/* The line that matches 'end'. */
	    regmatch_t startmatch, endmatch;
		/* The match positions of the start and end regexes. */
2402

2403
2404
2405
	    /* 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. */
2406

2407
2408
	    wattron(edit, varnish->attributes);

2409
2410
	    /* First case: varnish is a single-line expression. */
	    if (varnish->end == NULL) {
2411
		/* We increment index by rm_eo, to move past the end of the
2412
		 * last match.  Even though two matches may overlap, we
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2413
2414
		 * want to ignore them, so that we can highlight e.g. C
		 * strings correctly. */
2415
		while (index < till_x) {
2416
2417
		    /* Note the fifth parameter to regexec().  It says
		     * not to match the beginning-of-line character
2418
		     * unless index is zero.  If regexec() returns
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2419
2420
		     * REG_NOMATCH, there are no more matches in the
		     * line. */
2421
		    if (regexec(varnish->start, &fileptr->data[index], 1,
2422
				&match, (index == 0) ? 0 : REG_NOTBOL) != 0)
2423
			break;
2424

2425
		    /* If the match is of length zero, skip it. */
2426
		    if (match.rm_so == match.rm_eo) {
2427
			index = move_mbright(fileptr->data,
2428
						index + match.rm_eo);
2429
2430
2431
			continue;
		    }

2432
		    /* Translate the match to the beginning of the line. */
2433
2434
2435
		    match.rm_so += index;
		    match.rm_eo += index;
		    index = match.rm_eo;
2436

2437
2438
		    /* If the matching part is not visible, skip it. */
		    if (match.rm_eo <= from_x || match.rm_so >= till_x)
2439
2440
			continue;

2441
2442
2443
		    start_col = (match.rm_so <= from_x) ?
					0 : strnlenpt(fileptr->data,
					match.rm_so) - from_col;
2444

2445
		    thetext = converted + actual_x(converted, start_col);
2446

2447
		    paintlen = actual_x(thetext, strnlenpt(fileptr->data,
2448
					match.rm_eo) - from_col - start_col);
2449

2450
		    mvwaddnstr(edit, row, margin + start_col,
2451
						thetext, paintlen);
Chris Allegretta's avatar
Chris Allegretta committed
2452
		}
2453
2454
2455
2456
		goto tail_of_loop;
	    }

	    /* Second case: varnish is a multiline expression. */
2457

2458
2459
2460
	    /* Assume nothing gets painted until proven otherwise below. */
	    fileptr->multidata[varnish->id] = CNONE;

2461
2462
2463
2464
	    /* 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 ||
2465
2466
			start_line->multidata[varnish->id] == CENDAFTER ||
			start_line->multidata[varnish->id] == CWOULDBE)
2467
2468
2469
		    goto seek_an_end;
		if (start_line->multidata[varnish->id] == CNONE ||
			start_line->multidata[varnish->id] == CBEGINBEFORE ||
2470
			start_line->multidata[varnish->id] == CSTARTENDHERE)
2471
2472
2473
		    goto step_two;
	    }

2474
2475
	    /* The preceding line has no precalculated multidata.  So, do
	     * some backtracking to find out what to paint. */
2476

2477
2478
	    /* First step: see if there is a line before current that
	     * matches 'start' and is not complemented by an 'end'. */
2479
2480
	    while (start_line != NULL && regexec(varnish->start,
		    start_line->data, 1, &startmatch, 0) == REG_NOMATCH) {
2481
		/* There is no start on this line; but if there is an end,
2482
2483
		 * there is no need to look for starts on earlier lines. */
		if (regexec(varnish->end, start_line->data, 0, NULL, 0) == 0)
2484
		    goto step_two;
2485
2486
		start_line = start_line->prev;
	    }
2487

2488
2489
2490
2491
2492
2493
2494
	    /* 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 &&
2495
2496
			(start_line->multidata[varnish->id] == CBEGINBEFORE ||
			start_line->multidata[varnish->id] == CSTARTENDHERE))
2497
		goto step_two;
2498

2499
	    /* Is there an uncomplemented start on the found line? */
2500
	    while (TRUE) {
2501
		/* Begin searching for an end after the start match. */
2502
		index += startmatch.rm_eo;
2503
		/* If there is no end after this last start, good. */
2504
2505
		if (regexec(varnish->end, start_line->data + index, 1, &endmatch,
				(index == 0) ? 0 : REG_NOTBOL) == REG_NOMATCH)
2506
		    break;
2507
2508
		/* Begin searching for a new start after the end match. */
		index += endmatch.rm_eo;
2509
2510
2511
		/* If both start and end match are mere anchors, advance. */
		if (startmatch.rm_so == startmatch.rm_eo &&
				endmatch.rm_so == endmatch.rm_eo) {
2512
		    if (start_line->data[index] == '\0')
2513
			break;
2514
		    index = move_mbright(start_line->data, index);
2515
		}
2516
		/* If there is no later start on this line, next step. */
2517
		if (regexec(varnish->start, start_line->data + index,
2518
				1, &startmatch, REG_NOTBOL) == REG_NOMATCH)
2519
		    goto step_two;
2520
2521
2522
	    }
	    /* Indeed, there is a start without an end on that line. */

2523
  seek_an_end:
2524
2525
	    /* We've already checked that there is no end between the start
	     * and the current line.  But is there an end after the start
2526
2527
2528
2529
2530
	     * 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;

2531
	    /* If there is no end, there is nothing to paint. */
2532
2533
	    if (end_line == NULL) {
		fileptr->multidata[varnish->id] = CWOULDBE;
2534
		goto tail_of_loop;
2535
	    }
2536

2537
	    /* If the end is on a later line, paint whole line, and be done. */
2538
	    if (end_line != fileptr) {
2539
		mvwaddnstr(edit, row, margin, converted, -1);
2540
2541
		fileptr->multidata[varnish->id] = CWHOLELINE;
		goto tail_of_loop;
2542
2543
2544
2545
	    }

	    /* Only if it is visible, paint the part to be coloured. */
	    if (endmatch.rm_eo > from_x) {
2546
		paintlen = actual_x(converted, strnlenpt(fileptr->data,
2547
						endmatch.rm_eo) - from_col);
2548
		mvwaddnstr(edit, row, margin, converted, paintlen);
2549
2550
	    }
	    fileptr->multidata[varnish->id] = CBEGINBEFORE;
2551

2552
  step_two:
2553
2554
2555
	    /* 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;
2556

2557
	    while (regexec(varnish->start, fileptr->data + index,
2558
				1, &startmatch, (index == 0) ?
2559
				0 : REG_NOTBOL) == 0) {
2560
2561
2562
2563
		/* Translate the match to be relative to the
		 * beginning of the line. */
		startmatch.rm_so += index;
		startmatch.rm_eo += index;
2564

2565
		start_col = (startmatch.rm_so <= from_x) ?
2566
				0 : strnlenpt(fileptr->data,
2567
				startmatch.rm_so) - from_col;
2568

2569
		thetext = converted + actual_x(converted, start_col);
2570

2571
2572
		if (regexec(varnish->end, fileptr->data + startmatch.rm_eo,
				1, &endmatch, (startmatch.rm_eo == 0) ?
2573
				0 : REG_NOTBOL) == 0) {
2574
2575
2576
2577
		    /* 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;
2578
2579
		    /* Only paint the match if it is visible on screen and
		     * it is more than zero characters long. */
2580
		    if (endmatch.rm_eo > from_x &&
2581
					endmatch.rm_eo > startmatch.rm_so) {
2582
			paintlen = actual_x(thetext, strnlenpt(fileptr->data,
2583
					endmatch.rm_eo) - from_col - start_col);
2584

2585
			mvwaddnstr(edit, row, margin + start_col,
2586
						thetext, paintlen);
2587

2588
			fileptr->multidata[varnish->id] = CSTARTENDHERE;
2589
		    }
2590
		    index = endmatch.rm_eo;
2591
2592
2593
		    /* If both start and end match are anchors, advance. */
		    if (startmatch.rm_so == startmatch.rm_eo &&
				endmatch.rm_so == endmatch.rm_eo) {
2594
			if (fileptr->data[index] == '\0')
2595
			    break;
2596
			index = move_mbright(fileptr->data, index);
2597
2598
		    }
		    continue;
2599
		}
2600

2601
2602
		/* There is no end on this line.  But maybe on later lines? */
		end_line = fileptr->next;
2603

2604
2605
2606
		while (end_line != NULL && regexec(varnish->end, end_line->data,
					0, NULL, 0) == REG_NOMATCH)
		    end_line = end_line->next;
2607

2608
		/* If there is no end, we're done with this regex. */
2609
2610
		if (end_line == NULL) {
		    fileptr->multidata[varnish->id] = CWOULDBE;
2611
		    break;
2612
		}
2613

2614
		/* Paint the rest of the line, and we're done. */
2615
		mvwaddnstr(edit, row, margin + start_col, thetext, -1);
2616
2617
		fileptr->multidata[varnish->id] = CENDAFTER;
		break;
2618
	    }
2619
  tail_of_loop:
2620
	    wattroff(edit, varnish->attributes);
2621
	}
2622
    }
2623
#endif /* !DISABLE_COLOR */
2624

2625
#ifndef NANO_TINY
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
    /* 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. */
2637
	int start_col;
2638
2639
2640
2641
2642
	    /* 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". */
2643

2644
	mark_order(&top, &top_x, &bot, &bot_x, NULL);
2645

2646
2647
2648
2649
	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
2650

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

2656
2657
2658
	    if (start_col < 0)
		start_col = 0;

2659
	    thetext = converted + actual_x(converted, start_col);
2660

2661
2662
2663
2664
2665
2666
	    /* 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);
	    }
2667

2668
	    wattron(edit, interface_color_pair[SELECTED_TEXT]);
2669
	    mvwaddnstr(edit, row, margin + start_col, thetext, paintlen);
2670
	    wattroff(edit, interface_color_pair[SELECTED_TEXT]);
Chris Allegretta's avatar
Chris Allegretta committed
2671
	}
2672
    }
2673
#endif /* !NANO_TINY */
Chris Allegretta's avatar
Chris Allegretta committed
2674
2675
}

2676
2677
2678
/* 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
2679
2680
2681
 * 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). */
2682
int update_line(filestruct *fileptr, size_t index)
Chris Allegretta's avatar
Chris Allegretta committed
2683
{
2684
    int row = 0;
2685
	/* The row in the edit window we will be updating. */
2686
    char *converted;
2687
	/* The data of the line with tabs and control characters expanded. */
2688
2689
    size_t from_col = 0;
	/* From which column a horizontally scrolled line is displayed. */
Chris Allegretta's avatar
Chris Allegretta committed
2690

2691
#ifndef NANO_TINY
2692
2693
    if (ISSET(SOFTWRAP))
	return update_softwrapped_line(fileptr);
2694
#endif
2695
2696

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

2698
    /* If the line is offscreen, don't even try to display it. */
2699
    if (row < 0 || row >= editwinrows) {
2700
#ifndef NANO_TINY
2701
	statusline(ALERT, "Badness: tried to display a line on row %i"
2702
				" -- please report a bug", row);
2703
#endif
2704
	return 0;
2705
    }
2706

2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
    /* 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, '$');

2726
    return 1;
Chris Allegretta's avatar
Chris Allegretta committed
2727
2728
}

2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
#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. */
2743
2744
    size_t to_col = 0;
	/* To which column a line is displayed. */
2745
2746
2747
2748
2749
2750
    char *converted;
	/* The data of the chunk with tabs and control characters expanded. */

    if (fileptr == openfile->edittop)
	from_col = openfile->firstcolumn;
    else
2751
	row -= chunk_for(openfile->firstcolumn, openfile->edittop);
2752
2753
2754

    /* Find out on which screen row the target line should be shown. */
    while (line != fileptr && line != NULL) {
2755
	row += number_of_chunks_in(line) + 1;
2756
2757
2758
	line = line->next;
    }

2759
2760
2761
2762
    /* 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);
2763
	return 0;
2764
    }
2765
2766
2767

    starting_row = row;

2768
    while (row < editwinrows) {
2769
	bool end_of_line = FALSE;
2770
2771
2772

	to_col = get_softwrap_breakpoint(fileptr->data, from_col, &end_of_line);

2773
2774
2775
	blank_row(edit, row, 0, COLS);

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

2780
2781
2782
2783
	if (end_of_line)
	    break;

	/* If the line is softwrapped before its last column, add a ">" just
2784
2785
2786
	 * after its softwrap breakpoint, unless we're softwrapping at blanks
	 * and not in the middle of a word. */
	if (!ISSET(AT_BLANKS) && to_col - from_col < editwincols)
2787
2788
2789
	    mvwaddch(edit, row - 1, to_col - from_col, '>');

	from_col = to_col;
2790
2791
2792
2793
2794
2795
    }

    return (row - starting_row);
}
#endif

2796
2797
2798
2799
/* 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)
2800
{
2801
#ifndef NANO_TINY
2802
2803
2804
    if (openfile->mark_set)
	return TRUE;
    else
2805
#endif
2806
	return (get_page_start(old_column) != get_page_start(new_column));
2807
2808
}

2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
/* 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;

#ifndef NANO_TINY
    if (ISSET(SOFTWRAP)) {
2819
	/* Recede through the requested number of chunks. */
2820
	for (i = nrows; i > 0; i--) {
2821
2822
2823
2824
2825
2826
	    size_t chunk = chunk_for(*leftedge, *line);

	    *leftedge = 0;

	    if (chunk >= i)
		return go_forward_chunks(chunk - i, line, leftedge);
2827
2828
2829
2830

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

2831
	    i -= chunk;
2832
	    *line = (*line)->prev;
2833
	    *leftedge = HIGHEST_POSITIVE;
2834
2835
	}

2836
2837
	if (*leftedge == HIGHEST_POSITIVE)
	    *leftedge = leftedge_for(*leftedge, *line);
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
    } 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;

#ifndef NANO_TINY
    if (ISSET(SOFTWRAP)) {
2856
	size_t current_leftedge = *leftedge;
2857

2858
	/* Advance through the requested number of chunks. */
2859
	for (i = nrows; i > 0; i--) {
2860
	    bool end_of_line = FALSE;
2861

2862
2863
2864
2865
	    current_leftedge = get_softwrap_breakpoint((*line)->data,
					current_leftedge, &end_of_line);

	    if (!end_of_line)
2866
2867
2868
2869
2870
2871
		continue;

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

	    *line = (*line)->next;
2872
	    current_leftedge = 0;
2873
2874
2875
2876
	}

	/* Only change leftedge when we actually could move. */
	if (i < nrows)
2877
	    *leftedge = current_leftedge;
2878
2879
2880
2881
2882
2883
2884
2885
    } else
#endif
	for (i = nrows; i > 0 && (*line)->next != NULL; i--)
	    *line = (*line)->next;

    return i;
}

2886
2887
2888
2889
2890
2891
2892
2893
/* 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;
2894
	size_t leftedge = leftedge_for(xplustabs(), openfile->current);
2895
2896
2897
2898
2899
2900
2901
2902
2903
	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);
}

2904
/* Scroll the edit window in the given direction and the given number of rows,
2905
 * and draw new lines on the blank lines left after the scrolling. */
2906
void edit_scroll(scroll_dir direction, int nrows)
2907
{
2908
    filestruct *line;
2909
2910
    size_t leftedge;

2911
2912
    /* Part 1: nrows is the number of rows we're going to scroll the text of
     * the edit window. */
2913

2914
2915
    /* 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. */
2916
    if (direction == UPWARD)
2917
	nrows -= go_back_chunks(nrows, &openfile->edittop, &openfile->firstcolumn);
2918
    else
2919
	nrows -= go_forward_chunks(nrows, &openfile->edittop, &openfile->firstcolumn);
2920

2921
    /* Don't bother scrolling zero rows, nor more than the window can hold. */
2922
    if (nrows == 0) {
2923
#ifndef NANO_TINY
2924
	statusline(ALERT, "Underscrolling -- please report a bug");
2925
#endif
2926
	return;
2927
    }
2928
    if (nrows >= editwinrows) {
2929
#ifndef NANO_TINY
2930
2931
	if (editwinrows > 1)
	    statusline(ALERT, "Overscrolling -- please report a bug");
2932
#endif
2933
	refresh_needed = TRUE;
2934
	return;
2935
    }
2936

2937
    /* Scroll the text of the edit window a number of rows up or down. */
2938
    scrollok(edit, TRUE);
2939
    wscrl(edit, (direction == UPWARD) ? -nrows : nrows);
2940
2941
    scrollok(edit, FALSE);

2942
2943
    /* Part 2: nrows is now the number of rows in the scrolled region of the
     * edit window that we need to draw. */
2944

2945
2946
2947
2948
    /* 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++;
2949

2950
    /* If we scrolled backward, start on the first line of the blank region. */
2951
    line = openfile->edittop;
2952
    leftedge = openfile->firstcolumn;
2953

2954
    /* If we scrolled forward, move down to the start of the blank region. */
2955
2956
    if (direction == DOWNWARD)
	go_forward_chunks(editwinrows - nrows, &line, &leftedge);
2957

2958
#ifndef NANO_TINY
2959
2960
2961
    if (ISSET(SOFTWRAP)) {
	/* Compensate for the earlier chunks of a softwrapped line. */
	nrows += chunk_for(leftedge, line);
2962

2963
2964
2965
2966
	/* Don't compensate for the chunks that are offscreen. */
	if (line == openfile->edittop)
	    nrows -= chunk_for(openfile->firstcolumn, line);
    }
2967
#endif
2968
2969
2970

    /* Draw new content on the blank rows inside the scrolled region
     * (and on the bordering row too when it was deemed necessary). */
2971
2972
2973
    while (nrows > 0 && line != NULL) {
	nrows -= update_line(line, (line == openfile->current) ?
					openfile->current_x : 0);
2974
	line = line->next;
2975
2976
2977
    }
}

2978
#ifndef NANO_TINY
2979
2980
2981
2982
2983
2984
2985
/* Get the column number after leftedge where we can break the given text, and
 * return it.  This will always be editwincols or less after leftedge.  Set
 * end_of_line to TRUE if we reach the end of the line while searching the
 * text.  Assume leftedge is the leftmost column of a softwrapped chunk. */
size_t get_softwrap_breakpoint(const char *text, size_t leftedge,
				bool *end_of_line)
{
2986
2987
    size_t index = 0;
	/* Current index in text. */
2988
2989
2990
2991
2992
2993
    size_t column = 0;
	/* Current column position in text. */
    size_t prev_column = 0;
	/* Previous column position in text. */
    size_t goal_column;
	/* Column of the last character where we can break the text. */
2994
2995
2996
2997
2998
2999
    bool found_blank = FALSE;
	/* Did we find at least one blank? */
    size_t lastblank_index = 0;
	/* Current index of the last blank in text. */
    size_t lastblank_column = 0;
	/* Current column position of the last blank in text. */
3000
3001
3002
3003
3004
3005
    int char_len = 0;
	/* Length of current character, in bytes. */

    while (*text != '\0' && column < leftedge)
	text += parse_mbchar(text, NULL, &column);

3006
3007
    /* The intention is to use the entire available width. */
    goal_column = leftedge + editwincols;
3008
3009

    while (*text != '\0' && column <= goal_column) {
3010
3011
	/* When breaking at blanks, do it *before* the target column. */
	if (ISSET(AT_BLANKS) && is_blank_mbchar(text) && column < goal_column) {
3012
3013
3014
3015
3016
	    found_blank = TRUE;
	    lastblank_index = index;
	    lastblank_column = column;
	}

3017
3018
3019
	prev_column = column;
	char_len = parse_mbchar(text, NULL, &column);
	text += char_len;
3020
	index += char_len;
3021
3022
    }

3023
    /* If we didn't overshoot the target, we've found a breaking point. */
3024
    if (column <= goal_column) {
3025
3026
	/* We've reached EOL if we didn't even reach the target. */
	*end_of_line = (column < goal_column);
3027
3028
3029
	return column;
    }

3030
3031
3032
3033
3034
    /* If we're softwrapping at blanks and we found at least one blank, move
     * the pointer back to the last blank, step beyond it, and we're done. */
    if (found_blank) {
	text = text - index + lastblank_index;
	parse_mbchar(text, NULL, &lastblank_column);
3035
3036
3037
3038
3039

	/* If we've now overshot the screen's edge, then break there. */
	if (lastblank_column > goal_column)
	    return goal_column;

3040
3041
3042
	return lastblank_column;
    }

3043
3044
3045
3046
    /* If a tab is split over two chunks, break at the screen's edge. */
    if (*(text - char_len) == '\t')
	prev_column = goal_column;

3047
3048
3049
    /* Otherwise, return the column of the last character that doesn't
     * overshoot the target, since we can't break the text anywhere else. */
    return (editwincols > 1) ? prev_column : column - 1;
3050
3051
3052
3053
3054
}

/* Get the row of the softwrapped chunk of the given line that column is on,
 * relative to the first row (zero-based), and return it.  If leftedge isn't
 * NULL, return the leftmost column of the chunk in it. */
3055
size_t get_chunk_and_edge(size_t column, filestruct *line, size_t *leftedge)
3056
3057
{
    size_t current_chunk = 0, start_col = 0, end_col;
3058
    bool end_of_line = FALSE;
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074

    while (TRUE) {
	end_col = get_softwrap_breakpoint(line->data, start_col, &end_of_line);

	/* We reached the end of the line and/or found column, so get out. */
	if (end_of_line || (column >= start_col && column < end_col)) {
	    if (leftedge != NULL)
		*leftedge = start_col;
	    return current_chunk;
	}

	current_chunk++;
	start_col = end_col;
    }
}

3075
3076
/* Return the row of the softwrapped chunk of the given line that column is on,
 * relative to the first row (zero-based). */
3077
size_t chunk_for(size_t column, filestruct *line)
3078
{
3079
    return get_chunk_and_edge(column, line, NULL);
3080
3081
3082
3083
}

/* Return the leftmost column of the softwrapped chunk of the given line that
 * column is on. */
3084
size_t leftedge_for(size_t column, filestruct *line)
3085
{
3086
3087
    size_t leftedge;

3088
    get_chunk_and_edge(column, line, &leftedge);
3089
3090

    return leftedge;
3091
3092
3093
3094
}

/* Return the row of the last softwrapped chunk of the given line, relative to
 * the first row (zero-based). */
3095
size_t number_of_chunks_in(filestruct *line)
3096
{
3097
    return chunk_for((size_t)-1, line);
3098
3099
}

3100
/* Ensure that firstcolumn is at the starting column of the softwrapped chunk
3101
3102
3103
3104
 * 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)
{
3105
3106
    openfile->firstcolumn = leftedge_for(openfile->firstcolumn,
						openfile->edittop);
3107
3108
3109

    /* If smooth scrolling is on, make sure the viewport doesn't center. */
    focusing = FALSE;
3110
}
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
#endif /* !NANO_TINY */

/* When in softwrap mode, and the given column is on or after the breakpoint of
 * a softwrapped chunk, shift it back to the last column before the breakpoint.
 * The given column is relative to the given leftedge in current.  The returned
 * column is relative to the start of the text. */
size_t actual_last_column(size_t leftedge, size_t column)
{
#ifndef NANO_TINY
    if (ISSET(SOFTWRAP)) {
	bool last_chunk;
	size_t end_col = get_softwrap_breakpoint(openfile->current->data,
					leftedge, &last_chunk) - leftedge;

	/* If we're not on the last chunk, we're one column past the end of
	 * the row.  Shifting back one column might put us in the middle of
3127
	 * a multi-column character, but actual_x() will fix that later. */
3128
3129
3130
3131
3132
3133
	if (!last_chunk)
	    end_col--;

	if (column > end_col)
	    column = end_col;
    }
3134
#endif
3135

3136
3137
3138
    return leftedge + column;
}

3139
3140
3141
3142
/* Return TRUE if current[current_x] is above the top of the screen, and FALSE
 * otherwise. */
bool current_is_above_screen(void)
{
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
#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);
3153
3154
3155
3156
3157
3158
3159
3160
3161
}

/* 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;
3162
	size_t leftedge = openfile->firstcolumn;
3163
3164

	/* If current[current_x] is more than a screen's worth of lines after
3165
	 * edittop at column firstcolumn, it's below the screen. */
3166
3167
3168
	return (go_forward_chunks(editwinrows - 1, &line, &leftedge) == 0 &&
			(line->lineno < openfile->current->lineno ||
			(line->lineno == openfile->current->lineno &&
3169
3170
			leftedge < leftedge_for(xplustabs(),
						openfile->current))));
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
    } 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());
}

3184
/* Update any lines between old_current and current that need to be
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3185
 * updated.  Use this if we've moved without changing any text. */
3186
void edit_redraw(filestruct *old_current)
3187
{
3188
3189
3190
3191
    size_t was_pww = openfile->placewewant;

    openfile->placewewant = xplustabs();

3192
    /* If the current line is offscreen, scroll until it's onscreen. */
3193
    if (current_is_offscreen()) {
3194
	adjust_viewport((focusing || !ISSET(SMOOTH_SCROLL)) ? CENTERING : FLOWING);
3195
	refresh_needed = TRUE;
3196
	return;
3197
    }
3198

3199
#ifndef NANO_TINY
3200
3201
    /* If the mark is on, update all lines between old_current and current. */
    if (openfile->mark_set) {
3202
	filestruct *line = old_current;
3203

3204
3205
	while (line != openfile->current) {
	    update_line(line, 0);
3206

3207
3208
	    line = (line->lineno > openfile->current->lineno) ?
			line->prev : line->next;
3209
	}
3210
3211
3212
3213
3214
3215
    } 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);
3216

3217
3218
    /* 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. */
3219
    if (line_needs_update(was_pww, openfile->placewewant) ||
3220
			(old_current != openfile->current &&
3221
			get_page_start(openfile->placewewant) > 0))
3222
	update_line(openfile->current, openfile->current_x);
3223
3224
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3225
3226
/* 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
3227
3228
void edit_refresh(void)
{
3229
3230
    filestruct *line;
    int row = 0;
3231

3232
3233
3234
3235
3236
3237
#ifndef DISABLE_COLOR
    /* When needed, initialize the colors for the current syntax. */
    if (!have_palette)
	color_init();
#endif

3238
    /* If the current line is out of view, get it back on screen. */
3239
3240
    if (current_is_offscreen())
	adjust_viewport((focusing || !ISSET(SMOOTH_SCROLL)) ? CENTERING : FLOWING);
Chris Allegretta's avatar
Chris Allegretta committed
3241

3242
3243
3244
3245
    line = openfile->edittop;

    while (row < editwinrows && line != NULL) {
	if (line == openfile->current)
3246
	    row += update_line(line, openfile->current_x);
3247
	else
3248
	    row += update_line(line, 0);
3249
	line = line->next;
3250
3251
    }

3252
    while (row < editwinrows)
3253
	blank_row(edit, row++, 0, COLS);
3254

3255
    place_the_cursor();
3256
    wnoutrefresh(edit);
3257
3258

    refresh_needed = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
3259
3260
}

3261
3262
3263
3264
3265
/* 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. */
3266
void adjust_viewport(update_type manner)
Chris Allegretta's avatar
Chris Allegretta committed
3267
{
3268
    int goal = 0;
3269

3270
    if (manner == STATIONARY)
3271
	goal = openfile->current_y;
3272
3273
3274
3275
    else if (manner == CENTERING)
	goal = editwinrows / 2;
    else if (!current_is_above_screen())
	goal = editwinrows - 1;
3276

3277
    openfile->edittop = openfile->current;
3278
#ifndef NANO_TINY
3279
    if (ISSET(SOFTWRAP))
3280
	openfile->firstcolumn = leftedge_for(xplustabs(), openfile->current);
3281
#endif
3282
3283

    /* Move edittop back goal rows, starting at current[current_x]. */
3284
    go_back_chunks(goal, &openfile->edittop, &openfile->firstcolumn);
Chris Allegretta's avatar
Chris Allegretta committed
3285
3286
}

3287
/* Unconditionally redraw the entire screen. */
3288
void total_redraw(void)
3289
{
3290
3291
3292
3293
3294
3295
#ifdef USE_SLANG
    /* Slang curses emulation brain damage, part 4: Slang doesn't define
     * curscr. */
    SLsmg_touch_screen();
    SLsmg_refresh();
#else
3296
    wrefresh(curscr);
3297
#endif
3298
3299
}

3300
3301
/* Redraw the entire screen, then refresh the title bar and the content of
 * the edit window (when not in the file browser), and the bottom bars. */
3302
3303
void total_refresh(void)
{
3304
    total_redraw();
3305
3306
    if (currmenu != MBROWSER && currmenu != MWHEREISFILE && currmenu != MGOTODIR)
	titlebar(title);
3307
#ifdef ENABLE_HELP
3308
    if (inhelp)
3309
	wrap_the_help_text(TRUE);
3310
3311
    else
#endif
3312
    if (currmenu != MBROWSER && currmenu != MWHEREISFILE && currmenu != MGOTODIR)
3313
	edit_refresh();
3314
    bottombars(currmenu);
3315
3316
}

3317
3318
/* Display the main shortcut list on the last two rows of the bottom
 * portion of the window. */
3319
3320
void display_main_list(void)
{
3321
#ifndef DISABLE_COLOR
3322
3323
    if (openfile->syntax &&
		(openfile->syntax->formatter || openfile->syntax->linter))
3324
	set_lint_or_format_shortcuts();
3325
3326
3327
3328
    else
	set_spell_shortcuts();
#endif

3329
    bottombars(MMAIN);
3330
3331
}

3332
3333
3334
3335
/* 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
3336
{
3337
3338
    char saved_byte;
    size_t sum, cur_xpt = xplustabs() + 1;
3339
    size_t cur_lenpt = strlenpt(openfile->current->data) + 1;
3340
    int linepct, colpct, charpct;
Chris Allegretta's avatar
Chris Allegretta committed
3341

3342
3343
3344
3345
3346
    /* If the showing needs to be suppressed, don't suppress it next time. */
    if (suppress_cursorpos && !force) {
	suppress_cursorpos = FALSE;
	return;
    }
3347

3348
3349
3350
    /* Hide the cursor while we are calculating. */
    curs_set(0);

3351
    /* Determine the size of the file up to the cursor. */
3352
    saved_byte = openfile->current->data[openfile->current_x];
3353
    openfile->current->data[openfile->current_x] = '\0';
3354

3355
    sum = get_totsize(openfile->fileage, openfile->current);
3356

3357
    openfile->current->data[openfile->current_x] = saved_byte;
3358
3359
3360

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

3363
    /* Display the current cursor position on the statusbar. */
3364
    linepct = 100 * openfile->current->lineno / openfile->filebot->lineno;
3365
    colpct = 100 * cur_xpt / cur_lenpt;
3366
    charpct = (openfile->totsize == 0) ? 0 : 100 * sum / openfile->totsize;
3367

3368
    statusline(HUSH,
3369
	_("line %ld/%ld (%d%%), col %lu/%lu (%d%%), char %lu/%lu (%d%%)"),
3370
	(long)openfile->current->lineno,
3371
	(long)openfile->filebot->lineno, linepct,
3372
	(unsigned long)cur_xpt, (unsigned long)cur_lenpt, colpct,
3373
	(unsigned long)sum, (unsigned long)openfile->totsize, charpct);
3374
3375
3376

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

3379
/* Unconditionally display the current cursor position. */
3380
void do_cursorpos_void(void)
3381
{
3382
    do_cursorpos(TRUE);
3383
3384
}

3385
void disable_waiting(void)
3386
{
3387
    waiting_mode = FALSE;
3388
    nodelay(edit, TRUE);
3389
3390
}

3391
void enable_waiting(void)
3392
{
3393
    waiting_mode = TRUE;
3394
    nodelay(edit, FALSE);
3395
3396
}

3397
3398
3399
/* Highlight the text between from_col and to_col when active is TRUE.
 * Remove the highlight when active is FALSE. */
void spotlight(bool active, size_t from_col, size_t to_col)
Chris Allegretta's avatar
Chris Allegretta committed
3400
{
3401
3402
    char *word;
    size_t word_span, room;
Chris Allegretta's avatar
Chris Allegretta committed
3403

3404
    place_the_cursor();
3405

3406
3407
3408
3409
#ifndef NANO_TINY
    if (ISSET(SOFTWRAP)) {
	spotlight_softwrapped(active, from_col, to_col);
	return;
3410
    }
3411
#endif
3412

3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
    /* This is so we can show zero-length matches. */
    if (to_col == from_col) {
	word = mallocstrcpy(NULL, " ");
	to_col++;
    } else
	word = display_string(openfile->current->data, from_col,
				to_col - from_col, FALSE);

    word_span = strlenpt(word);

    /* Compute the number of columns that are available for the word. */
    room = editwincols + get_page_start(from_col) - from_col;

    /* If the word is partially offscreen, reserve space for the "$". */
    if (word_span > room)
	room--;

3430
    if (active)
3431
	wattron(edit, interface_color_pair[SELECTED_TEXT]);
Chris Allegretta's avatar
Chris Allegretta committed
3432

3433
    waddnstr(edit, word, actual_x(word, room));
3434

3435
    if (word_span > room)
3436
	waddch(edit, '$');
Chris Allegretta's avatar
Chris Allegretta committed
3437

3438
    if (active)
3439
	wattroff(edit, interface_color_pair[SELECTED_TEXT]);
3440

3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
    free(word);

    wnoutrefresh(edit);
}

#ifndef NANO_TINY
/* Highlight the text between from_col and to_col when active is TRUE; remove
 * the highlight when active is FALSE.  This will not highlight softwrapped
 * line breaks, since they're not actually part of the spotlighted text. */
void spotlight_softwrapped(bool active, size_t from_col, size_t to_col)
{
3452
    ssize_t row = openfile->current_y;
3453
    size_t leftedge = leftedge_for(from_col, openfile->current);
3454
    size_t break_col;
3455
    bool end_of_line = FALSE;
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
    char *word;

    while (row < editwinrows) {
	break_col = get_softwrap_breakpoint(openfile->current->data,
						leftedge, &end_of_line);

	/* Stop after the end of the word, by pretending the end of the word is
	 * the end of the line. */
	if (break_col >= to_col) {
	    end_of_line = TRUE;
	    break_col = to_col;
	}

	/* This is so we can show zero-length matches. */
	if (break_col == from_col) {
	    word = mallocstrcpy(NULL, " ");
	    break_col++;
	} else
	    word = display_string(openfile->current->data, from_col,
					break_col - from_col, FALSE);

	if (active)
3478
	    wattron(edit, interface_color_pair[SELECTED_TEXT]);
3479
3480
3481
3482

	waddnstr(edit, word, actual_x(word, break_col));

	if (active)
3483
	    wattroff(edit, interface_color_pair[SELECTED_TEXT]);
3484
3485
3486
3487
3488
3489

	free(word);

	if (end_of_line)
	    break;

3490
	wmove(edit, ++row, 0);
3491
3492
3493
3494
3495

	leftedge = break_col;
	from_col = break_col;
    }

3496
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
3497
}
3498
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3499

3500
#ifndef DISABLE_EXTRA
3501
3502
#define CREDIT_LEN 54
#define XLCREDIT_LEN 9
3503

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3504
3505
/* Easter egg: Display credits.  Assume nodelay(edit) and scrollok(edit)
 * are FALSE. */
3506
3507
void do_credits(void)
{
3508
3509
    bool old_more_space = ISSET(MORE_SPACE);
    bool old_no_help = ISSET(NO_HELP);
3510
    int kbinput = ERR, crpos = 0, xlpos = 0;
3511
3512
3513
    const char *credits[CREDIT_LEN] = {
	NULL,				/* "The nano text editor" */
	NULL,				/* "version" */
Chris Allegretta's avatar
Chris Allegretta committed
3514
3515
	VERSION,
	"",
3516
	NULL,				/* "Brought to you by:" */
Chris Allegretta's avatar
Chris Allegretta committed
3517
3518
3519
3520
3521
	"Chris Allegretta",
	"Jordi Mallach",
	"Adam Rogoyski",
	"Rob Siemborski",
	"Rocco Corsi",
3522
	"David Lawrence Ramsey",
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3523
	"David Benbennick",
Benno Schulenberg's avatar
Benno Schulenberg committed
3524
	"Mark Majeres",
3525
	"Mike Frysinger",
3526
	"Benno Schulenberg",
Chris Allegretta's avatar
Chris Allegretta committed
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
	"Ken Tyler",
	"Sven Guckes",
	"Bill Soudan",
	"Christian Weisgerber",
	"Erik Andersen",
	"Big Gaute",
	"Joshua Jensen",
	"Ryan Krebs",
	"Albert Chin",
	"",
3537
	NULL,				/* "Special thanks to:" */
3538
	"Monique, Brielle & Joseph",
Chris Allegretta's avatar
Chris Allegretta committed
3539
3540
3541
3542
3543
3544
	"Plattsburgh State University",
	"Benet Laboratories",
	"Amy Allegretta",
	"Linda Young",
	"Jeremy Robichaud",
	"Richard Kolb II",
3545
	NULL,				/* "The Free Software Foundation" */
Chris Allegretta's avatar
Chris Allegretta committed
3546
	"Linus Torvalds",
3547
	NULL,				/* "the many translators and the TP" */
3548
	NULL,				/* "For ncurses:" */
3549
3550
3551
3552
	"Thomas Dickey",
	"Pavel Curtis",
	"Zeyd Ben-Halim",
	"Eric S. Raymond",
3553
3554
3555
3556
3557
3558
	NULL,				/* "and anyone else we forgot..." */
	NULL,				/* "Thank you for using nano!" */
	"",
	"",
	"",
	"",
3559
	"(C) 2017",
3560
	"Free Software Foundation, Inc.",
3561
3562
3563
3564
	"",
	"",
	"",
	"",
3565
	"https://nano-editor.org/"
3566
3567
    };

3568
    const char *xlcredits[XLCREDIT_LEN] = {
3569
3570
3571
3572
3573
	N_("The nano text editor"),
	N_("version"),
	N_("Brought to you by:"),
	N_("Special thanks to:"),
	N_("The Free Software Foundation"),
3574
	N_("the many translators and the TP"),
3575
3576
3577
	N_("For ncurses:"),
	N_("and anyone else we forgot..."),
	N_("Thank you for using nano!")
3578
    };
3579

3580
3581
3582
3583
3584
3585
    if (!old_more_space || !old_no_help) {
	SET(MORE_SPACE);
	SET(NO_HELP);
	window_init();
    }

3586
3587
    curs_set(0);
    nodelay(edit, TRUE);
3588

3589
    blank_titlebar();
Chris Allegretta's avatar
Chris Allegretta committed
3590
    blank_edit();
3591
    blank_statusbar();
3592

3593
    wrefresh(topwin);
Chris Allegretta's avatar
Chris Allegretta committed
3594
    wrefresh(edit);
3595
    wrefresh(bottomwin);
3596
    napms(700);
3597

3598
    for (crpos = 0; crpos < CREDIT_LEN + editwinrows / 2; crpos++) {
3599
	if ((kbinput = wgetch(edit)) != ERR)
3600
	    break;
3601

3602
	if (crpos < CREDIT_LEN) {
3603
	    const char *what;
3604
	    size_t start_col;
3605

3606
3607
3608
	    if (credits[crpos] == NULL)
		what = _(xlcredits[xlpos++]);
	    else
3609
		what = credits[crpos];
3610

3611
	    start_col = COLS / 2 - strlenpt(what) / 2 - 1;
3612
	    mvwaddstr(edit, editwinrows - 1 - (editwinrows % 2),
3613
						start_col, what);
3614
	}
3615

3616
3617
3618
3619
	wrefresh(edit);

	if ((kbinput = wgetch(edit)) != ERR)
	    break;
3620
	napms(700);
3621

3622
	scrollok(edit, TRUE);
3623
	wscrl(edit, 1);
3624
	scrollok(edit, FALSE);
3625
	wrefresh(edit);
3626

3627
	if ((kbinput = wgetch(edit)) != ERR)
3628
	    break;
3629
	napms(700);
3630

3631
	scrollok(edit, TRUE);
3632
	wscrl(edit, 1);
3633
	scrollok(edit, FALSE);
3634
	wrefresh(edit);
3635
3636
    }

3637
3638
3639
    if (kbinput != ERR)
	ungetch(kbinput);

3640
    if (!old_more_space)
3641
	UNSET(MORE_SPACE);
3642
    if (!old_no_help)
3643
	UNSET(NO_HELP);
3644
    window_init();
3645

3646
    nodelay(edit, FALSE);
3647

3648
    total_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
3649
}
3650
#endif /* !DISABLE_EXTRA */