winio.c 110 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
527
    } else if (retval == shiftaltleft) {
	shift_held = TRUE;
528
	return KEY_HOME;
529
530
    } else if (retval == shiftaltright) {
	shift_held = TRUE;
531
	return KEY_END;
532
533
    } else if (retval == shiftaltup) {
	shift_held = TRUE;
534
	return KEY_PPAGE;
535
536
    } else if (retval == shiftaltdown) {
	shift_held = TRUE;
537
	return KEY_NPAGE;
538
    }
539
540
#endif

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

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

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

710
711
712
    return retval;
}

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

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

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

945
	switch (seq[3]) {
946
	    case '2':
947
948
949
950
951
		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. */
952
			shift_held = TRUE;
953
954
			return arrow_from_abcd(seq[4]);
		}
955
		break;
956
957
958
959
960
961
962
963
964
965
#ifndef NANO_TINY
	    case '4':
		/* When the arrow keys are held together with Shift+Meta,
		 * act as if they are Home/End/PgUp/PgDown with Shift. */
		switch (seq[4]) {
		    case 'A': /* Esc [ 1 ; 4 A == Shift-Alt-Up on xterm. */
			return SHIFT_PAGEUP;
		    case 'B': /* Esc [ 1 ; 4 B == Shift-Alt-Down on xterm. */
			return SHIFT_PAGEDOWN;
		    case 'C': /* Esc [ 1 ; 4 C == Shift-Alt-Right on xterm. */
966
			return SHIFT_END;
967
		    case 'D': /* Esc [ 1 ; 4 D == Shift-Alt-Left on xterm. */
968
			return SHIFT_HOME;
969
970
971
		}
		break;
#endif
972
	    case '5':
973
974
975
976
977
978
979
980
981
		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;
982
983
984
985
		    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;
986
		}
987
		break;
988
989
990
991
992
993
994
995
996
997
998
#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;
999
1000
1001
1002
		    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;
1003
1004
1005
		}
		break;
#endif
1006
	}
1007
1008
1009
1010

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

1203
    return ERR;
1204
1205
}

1206
1207
/* Return the equivalent arrow-key value for the first four letters
 * in the alphabet, common to many escape sequences. */
1208
int arrow_from_abcd(int kbinput)
1209
1210
1211
{
    switch (tolower(kbinput)) {
	case 'a':
1212
	    return KEY_UP;
1213
	case 'b':
1214
	    return KEY_DOWN;
1215
	case 'c':
1216
	    return KEY_RIGHT;
1217
	case 'd':
1218
	    return KEY_LEFT;
1219
1220
1221
1222
1223
	default:
	    return ERR;
    }
}

1224
/* Interpret the escape sequence in the keystroke buffer, the first
1225
1226
 * character of which is kbinput.  Assume that the keystroke buffer
 * isn't empty, and that the initial escape has already been read in. */
1227
int parse_escape_sequence(WINDOW *win, int kbinput)
1228
1229
1230
1231
1232
1233
1234
1235
{
    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);
1236
    seq_len = key_buffer_len;
1237
    seq = get_input(NULL, seq_len);
1238
    retval = convert_sequence(seq, seq_len);
1239
1240
1241

    free(seq);

1242
    /* If we got an unrecognized escape sequence, notify the user. */
1243
    if (retval == ERR) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1244
	if (win == edit) {
1245
1246
	    /* TRANSLATORS: This refers to a sequence of escape codes
	     * (from the keyboard) that nano does not know about. */
1247
	    statusline(ALERT, _("Unknown sequence"));
1248
	    suppress_cursorpos = FALSE;
1249
	    lastmessage = HUSH;
1250
	    if (currmenu == MMAIN) {
1251
		place_the_cursor();
1252
1253
		curs_set(1);
	    }
1254
1255
1256
	}
    }

1257
#ifdef DEBUG
1258
1259
    fprintf(stderr, "parse_escape_sequence(): kbinput = %d, seq_len = %lu, retval = %d\n",
		kbinput, (unsigned long)seq_len, retval);
1260
1261
1262
1263
1264
#endif

    return retval;
}

1265
1266
/* Translate a byte sequence: turn a three-digit decimal number (from
 * 000 to 255) into its corresponding byte value. */
1267
int get_byte_kbinput(int kbinput)
1268
{
1269
    static int byte_digits = 0, byte = 0;
1270
    int retval = ERR;
1271

1272
1273
    /* Increment the byte digit counter. */
    byte_digits++;
1274

1275
    switch (byte_digits) {
1276
	case 1:
1277
1278
	    /* First digit: This must be from zero to two.  Put it in
	     * the 100's position of the byte sequence holder. */
1279
	    if ('0' <= kbinput && kbinput <= '2')
1280
		byte = (kbinput - '0') * 100;
1281
	    else
1282
1283
		/* This isn't the start of a byte sequence.  Return this
		 * character as the result. */
1284
1285
1286
		retval = kbinput;
	    break;
	case 2:
1287
1288
1289
1290
	    /* 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. */
1291
1292
1293
	    if (('0' <= kbinput && kbinput <= '5') || (byte < 200 &&
		'6' <= kbinput && kbinput <= '9'))
		byte += (kbinput - '0') * 10;
1294
	    else
1295
1296
		/* This isn't the second digit of a byte sequence.
		 * Return this character as the result. */
1297
1298
1299
		retval = kbinput;
	    break;
	case 3:
1300
	    /* Third digit: This must be from zero to five if the first
1301
1302
1303
	     * 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. */
1304
1305
	    if (('0' <= kbinput && kbinput <= '5') || (byte < 250 &&
		'6' <= kbinput && kbinput <= '9')) {
1306
		byte += kbinput - '0';
1307
		/* The byte sequence is complete. */
1308
		retval = byte;
1309
	    } else
1310
1311
		/* This isn't the third digit of a byte sequence.
		 * Return this character as the result. */
1312
1313
		retval = kbinput;
	    break;
1314
	default:
1315
1316
1317
	    /* If there are more than three digits, return this
	     * character as the result.  (Maybe we should produce an
	     * error instead?) */
1318
1319
1320
1321
1322
1323
1324
1325
	    retval = kbinput;
	    break;
    }

    /* If we have a result, reset the byte digit counter and the byte
     * sequence holder. */
    if (retval != ERR) {
	byte_digits = 0;
1326
	byte = 0;
1327
1328
1329
    }

#ifdef DEBUG
1330
    fprintf(stderr, "get_byte_kbinput(): kbinput = %d, byte_digits = %d, byte = %d, retval = %d\n", kbinput, byte_digits, byte, retval);
1331
1332
1333
1334
1335
#endif

    return retval;
}

1336
#ifdef ENABLE_UTF8
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1337
/* If the character in kbinput is a valid hexadecimal digit, multiply it
1338
 * by factor and add the result to uni, and return ERR to signify okay. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1339
1340
1341
1342
1343
1344
1345
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
1346
1347
	/* The character isn't hexadecimal; give it as the result. */
	return (long)kbinput;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1348

1349
    return ERR;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1350
1351
}

1352
/* Translate a Unicode sequence: turn a six-digit hexadecimal number
1353
 * (from 000000 to 10FFFF, case-insensitive) into its corresponding
1354
 * multibyte value. */
1355
long get_unicode_kbinput(WINDOW *win, int kbinput)
1356
{
1357
1358
1359
    static int uni_digits = 0;
    static long uni = 0;
    long retval = ERR;
1360

1361
    /* Increment the Unicode digit counter. */
1362
    uni_digits++;
1363

1364
    switch (uni_digits) {
1365
	case 1:
1366
1367
1368
1369
	    /* 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')
1370
		uni = (kbinput - '0') * 0x100000;
1371
1372
1373
1374
	    else
		retval = kbinput;
	    break;
	case 2:
1375
1376
1377
	    /* 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)
1378
		retval = add_unicode_digit(kbinput, 0x10000, &uni);
1379
1380
1381
1382
	    else
		retval = kbinput;
	    break;
	case 3:
1383
	    /* Later digits may be any hexadecimal value. */
1384
	    retval = add_unicode_digit(kbinput, 0x1000, &uni);
1385
	    break;
1386
	case 4:
1387
	    retval = add_unicode_digit(kbinput, 0x100, &uni);
1388
	    break;
1389
	case 5:
1390
	    retval = add_unicode_digit(kbinput, 0x10, &uni);
1391
	    break;
1392
	case 6:
1393
	    retval = add_unicode_digit(kbinput, 0x1, &uni);
1394
1395
	    /* If also the sixth digit was a valid hexadecimal value, then
	     * the Unicode sequence is complete, so return it. */
1396
	    if (retval == ERR)
1397
		retval = uni;
1398
1399
	    break;
    }
1400

1401
    /* Show feedback only when editing, not when at a prompt. */
1402
    if (retval == ERR && win == edit) {
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
	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);
    }
1413

1414
#ifdef DEBUG
1415
1416
    fprintf(stderr, "get_unicode_kbinput(): kbinput = %d, uni_digits = %d, uni = %ld, retval = %ld\n",
						kbinput, uni_digits, uni, retval);
1417
1418
#endif

1419
1420
1421
1422
    /* If we have an end result, reset the Unicode digit counter. */
    if (retval != ERR)
	uni_digits = 0;

1423
1424
    return retval;
}
1425
#endif /* ENABLE_UTF8 */
1426

1427
1428
1429
1430
1431
1432
/* Translate a control character sequence: turn an ASCII non-control
 * character into its corresponding control character. */
int get_control_kbinput(int kbinput)
{
    int retval;

1433
    /* Ctrl-Space (Ctrl-2, Ctrl-@, Ctrl-`) */
1434
    if (kbinput == ' ' || kbinput == '2')
1435
	retval = 0;
1436
1437
    /* Ctrl-/ (Ctrl-7, Ctrl-_) */
    else if (kbinput == '/')
1438
	retval = 31;
1439
    /* Ctrl-3 (Ctrl-[, Esc) to Ctrl-7 (Ctrl-/, Ctrl-_) */
1440
1441
1442
    else if ('3' <= kbinput && kbinput <= '7')
	retval = kbinput - 24;
    /* Ctrl-8 (Ctrl-?) */
1443
    else if (kbinput == '8' || kbinput == '?')
1444
	retval = DEL_CODE;
1445
1446
    /* Ctrl-@ (Ctrl-Space, Ctrl-2, Ctrl-`) to Ctrl-_ (Ctrl-/, Ctrl-7) */
    else if ('@' <= kbinput && kbinput <= '_')
1447
	retval = kbinput - '@';
1448
1449
    /* Ctrl-` (Ctrl-2, Ctrl-Space, Ctrl-@) to Ctrl-~ (Ctrl-6, Ctrl-^) */
    else if ('`' <= kbinput && kbinput <= '~')
1450
	retval = kbinput - '`';
1451
1452
1453
    else
	retval = kbinput;

1454
#ifdef DEBUG
1455
    fprintf(stderr, "get_control_kbinput(): kbinput = %d, retval = %d\n", kbinput, retval);
1456
1457
#endif

1458
1459
    return retval;
}
1460

1461
/* Read in a stream of characters verbatim, and return the length of the
1462
1463
1464
1465
 * string in kbinput_len.  Assume nodelay(win) is FALSE. */
int *get_verbatim_kbinput(WINDOW *win, size_t *kbinput_len)
{
    int *retval;
1466

1467
    /* Turn off flow control characters if necessary so that we can type
1468
1469
     * them in verbatim, and turn the keypad off if necessary so that we
     * don't get extended keypad values. */
1470
1471
    if (ISSET(PRESERVE))
	disable_flow_control();
1472
1473
    if (!ISSET(REBIND_KEYPAD))
	keypad(win, FALSE);
1474

1475
    /* Read in one keycode, or one or two escapes. */
1476
    retval = parse_verbatim_kbinput(win, kbinput_len);
1477

1478
    /* If the code is invalid in the current mode, discard it. */
1479
1480
    if (retval != NULL && ((*retval == '\n' && as_an_at) ||
				(*retval == '\0' && !as_an_at))) {
1481
1482
1483
1484
	*kbinput_len = 0;
	beep();
    }

1485
    /* Turn flow control characters back on if necessary and turn the
1486
     * keypad back on if necessary now that we're done. */
1487
1488
    if (ISSET(PRESERVE))
	enable_flow_control();
1489
1490
1491
1492
1493
1494
    /* 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);
    }
1495

1496
    return retval;
1497
1498
}

1499
1500
1501
1502
/* 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). */
1503
int *parse_verbatim_kbinput(WINDOW *win, size_t *count)
1504
{
1505
    int *kbinput;
1506

1507
    /* Read in the first code. */
1508
1509
    while ((kbinput = get_input(win, 1)) == NULL)
	;
1510

1511
#ifndef NANO_TINY
1512
1513
1514
    /* When the window was resized, abort and return nothing. */
    if (*kbinput == KEY_WINCH) {
	free(kbinput);
1515
	*count = 0;
1516
1517
	return NULL;
    }
1518
#endif
1519

1520
1521
    *count = 1;

1522
#ifdef ENABLE_UTF8
1523
    if (using_utf8()) {
1524
	/* Check whether the first code is a valid starter digit: 0 or 1. */
1525
	long unicode = get_unicode_kbinput(win, *kbinput);
1526

1527
	/* If the first code isn't the digit 0 nor 1, put it back. */
1528
	if (unicode != ERR)
1529
	    unget_input(kbinput, 1);
1530
1531
	/* Otherwise, continue reading in digits until we have a complete
	 * Unicode value, and put back the corresponding byte(s). */
1532
	else {
1533
	    char *multibyte;
1534
	    int onebyte, i;
1535

1536
	    while (unicode == ERR) {
1537
		free(kbinput);
1538
1539
		while ((kbinput = get_input(win, 1)) == NULL)
		    ;
1540
		unicode = get_unicode_kbinput(win, *kbinput);
1541
	    }
1542

1543
	    /* Convert the Unicode value to a multibyte sequence. */
1544
	    multibyte = make_mbchar(unicode, (int *)count);
1545

1546
	    /* Insert the multibyte sequence into the input buffer. */
1547
	    for (i = *count; i > 0 ; i--) {
1548
		onebyte = (unsigned char)multibyte[i - 1];
1549
1550
		unget_input(&onebyte, 1);
	    }
1551

1552
	    free(multibyte);
1553
	}
1554
    } else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1555
#endif /* ENABLE_UTF8 */
1556
	/* Put back the first code. */
1557
	unget_input(kbinput, 1);
1558

1559
1560
    free(kbinput);

1561
    /* If this is an iTerm/Eterm/rxvt double escape, take both Escapes. */
1562
1563
1564
1565
1566
    if (key_buffer_len > 3 && *key_buffer == ESC_CODE &&
		key_buffer[1] == ESC_CODE && key_buffer[2] == '[')
	*count = 2;

    return get_input(NULL, *count);
1567
1568
}

1569
#ifdef ENABLE_MOUSE
1570
/* Handle any mouse event that may have occurred.  We currently handle
1571
1572
1573
1574
1575
1576
1577
 * 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
1578
1579
1580
1581
1582
1583
 * 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. */
1584
int get_mouseinput(int *mouse_x, int *mouse_y, bool allow_shortcuts)
1585
1586
{
    MEVENT mevent;
1587
    bool in_bottomwin;
1588
    subnfunc *f;
1589
1590
1591
1592
1593
1594

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

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

1597
    /* Save the screen coordinates where the mouse event took place. */
1598
    *mouse_x = mevent.x - margin;
1599
    *mouse_y = mevent.y;
1600

1601
1602
    in_bottomwin = wenclose(bottomwin, *mouse_y, *mouse_x);

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1603
    /* Handle releases/clicks of the first mouse button. */
1604
    if (mevent.bstate & (BUTTON1_RELEASED | BUTTON1_CLICKED)) {
1605
1606
	/* If we're allowing shortcuts, the current shortcut list is
	 * being displayed on the last two lines of the screen, and the
1607
1608
1609
	 * 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. */
1610
	if (allow_shortcuts && !ISSET(NO_HELP) && in_bottomwin) {
1611
1612
1613
1614
	    int i;
		/* The width of all the shortcuts, except for the last
		 * two, in the shortcut list in bottomwin. */
	    int j;
1615
		/* The calculated index number of the clicked item. */
1616
1617
	    size_t number;
		/* The number of available shortcuts in the current menu. */
1618

1619
1620
1621
1622
1623
1624
1625
1626
1627
	    /* 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. */
1628
		*mouse_x = mevent.x - margin;
1629
1630
1631
1632
		*mouse_y = mevent.y;

		return 0;
	    }
1633

1634
	    /* Determine how many shortcuts are being shown. */
1635
	    number = length_of_list(currmenu);
1636

1637
1638
	    if (number > MAIN_VISIBLE)
		number = MAIN_VISIBLE;
1639

1640
1641
1642
	    /* 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. */
1643
	    if (number < 2)
1644
1645
		i = COLS / (MAIN_VISIBLE / 2);
	    else
1646
		i = COLS / ((number / 2) + (number % 2));
1647

1648
1649
	    /* Calculate the one-based index in the shortcut list. */
	    j = (*mouse_x / i) * 2 + *mouse_y;
1650

1651
	    /* Adjust the index if we hit the last two wider ones. */
1652
	    if ((j > number) && (*mouse_x % i < COLS % i))
1653
		j -= 2;
1654
1655
1656
#ifdef DEBUG
	    fprintf(stderr, "Calculated %i as index in shortcut list, currmenu = %x.\n", j, currmenu);
#endif
1657
1658
	    /* Ignore releases/clicks of the first mouse button beyond
	     * the last shortcut. */
1659
	    if (j > number)
1660
		return 2;
1661

1662
1663
	    /* Go through the list of functions to determine which
	     * shortcut in the current menu we released/clicked on. */
1664
	    for (f = allfuncs; f != NULL; f = f->next) {
1665
		if ((f->menus & currmenu) == 0)
1666
1667
		    continue;
		if (first_sc_for(currmenu, f->scfunc) == NULL)
1668
		    continue;
1669
1670
		/* Tick off an actually shown shortcut. */
		j -= 1;
1671
1672
		if (j == 0)
		    break;
1673
	    }
1674
#ifdef DEBUG
1675
	    fprintf(stderr, "Stopped on func %ld present in menus %x\n", (long)f->scfunc, f->menus);
1676
#endif
1677

1678
	    /* And put the corresponding key into the keyboard buffer. */
1679
	    if (f != NULL) {
1680
		const sc *s = first_sc_for(currmenu, f->scfunc);
1681
		unget_kbinput(s->keycode, s->meta);
1682
	    }
1683
	    return 1;
1684
	} else
1685
1686
	    /* Handle releases/clicks of the first mouse button that
	     * aren't on the current shortcut list elsewhere. */
1687
	    return 0;
1688
    }
1689
1690
#if NCURSES_MOUSE_VERSION >= 2
    /* Handle presses of the fourth mouse button (upward rolls of the
1691
1692
1693
     * 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
1694
	bool in_edit = wenclose(edit, *mouse_y, *mouse_x);
1695

1696
1697
1698
1699
	if (in_bottomwin)
	    /* Translate the mouse event coordinates so that they're
	     * relative to bottomwin. */
	    wmouse_trafo(bottomwin, mouse_y, mouse_x, FALSE);
1700

1701
	if (in_edit || (in_bottomwin && *mouse_y == 0)) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1702
1703
	    int i;

1704
1705
1706
1707
1708
	    /* 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) ?
1709
				KEY_PPAGE : KEY_NPAGE, FALSE);
1710
1711
1712
1713
1714
1715
1716

	    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;
1717
1718
    }
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1719
1720
1721

    /* Ignore all other mouse events. */
    return 2;
1722
}
1723
#endif /* ENABLE_MOUSE */
1724

1725
1726
1727
1728
/* 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. */
1729
const sc *get_shortcut(int *kbinput)
1730
{
1731
    sc *s;
1732

1733
#ifdef DEBUG
1734
1735
    fprintf(stderr, "get_shortcut(): kbinput = %d, meta_key = %s -- ",
				*kbinput, meta_key ? "TRUE" : "FALSE");
1736
1737
#endif

1738
    for (s = sclist; s != NULL; s = s->next) {
1739
	if ((s->menus & currmenu) && *kbinput == s->keycode &&
1740
					meta_key == s->meta) {
1741
#ifdef DEBUG
1742
1743
	    fprintf (stderr, "matched seq '%s'  (menu is %x from %x)\n",
				s->keystr, currmenu, s->menus);
1744
#endif
1745
	    return s;
1746
1747
	}
    }
1748
#ifdef DEBUG
1749
    fprintf (stderr, "matched nothing\n");
1750
#endif
1751
1752
1753
1754

    return NULL;
}

1755
1756
/* Move to (x, y) in win, and display a line of n spaces with the
 * current attributes. */
1757
void blank_row(WINDOW *win, int y, int x, int n)
1758
1759
{
    wmove(win, y, x);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1760

1761
1762
1763
1764
    for (; n > 0; n--)
	waddch(win, ' ');
}

1765
/* Blank the first line of the top portion of the window. */
1766
void blank_titlebar(void)
Chris Allegretta's avatar
Chris Allegretta committed
1767
{
1768
    blank_row(topwin, 0, 0, COLS);
1769
1770
}

1771
/* Blank all the lines of the middle portion of the window, i.e. the
1772
 * edit window. */
Chris Allegretta's avatar
Chris Allegretta committed
1773
1774
void blank_edit(void)
{
1775
    int row;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1776

1777
1778
    for (row = 0; row < editwinrows; row++)
	blank_row(edit, row, 0, COLS);
Chris Allegretta's avatar
Chris Allegretta committed
1779
1780
}

1781
/* Blank the first line of the bottom portion of the window. */
Chris Allegretta's avatar
Chris Allegretta committed
1782
1783
void blank_statusbar(void)
{
1784
    blank_row(bottomwin, 0, 0, COLS);
Chris Allegretta's avatar
Chris Allegretta committed
1785
1786
}

1787
1788
/* If the NO_HELP flag isn't set, blank the last two lines of the bottom
 * portion of the window. */
1789
1790
void blank_bottombars(void)
{
1791
    if (!ISSET(NO_HELP) && LINES > 4) {
1792
1793
	blank_row(bottomwin, 1, 0, COLS);
	blank_row(bottomwin, 2, 0, COLS);
1794
1795
1796
    }
}

1797
1798
/* Check if the number of keystrokes needed to blank the statusbar has
 * been pressed.  If so, blank the statusbar, unless constant cursor
1799
 * position display is on and we are in the editing screen. */
1800
void check_statusblank(void)
Chris Allegretta's avatar
Chris Allegretta committed
1801
{
1802
1803
1804
1805
1806
1807
    if (statusblank == 0)
	return;

    statusblank--;

    /* When editing and 'constantshow' is active, skip the blanking. */
1808
    if (currmenu == MMAIN && ISSET(CONSTANT_SHOW))
1809
1810
1811
1812
1813
	return;

    if (statusblank == 0) {
	blank_statusbar();
	wnoutrefresh(bottomwin);
Chris Allegretta's avatar
Chris Allegretta committed
1814
    }
1815
1816
1817
1818

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

1821
1822
1823
1824
1825
1826
1827
/* 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)
1828
{
1829
1830
1831
1832
    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. */
1833
    char *converted;
1834
	/* The expanded string we will return. */
1835
    size_t index = 0;
1836
	/* Current position in converted. */
1837
    size_t beyond = column + span;
1838
	/* The column number just beyond the last shown character. */
1839

1840
#ifdef USING_OLD_NCURSES
1841
    seen_wide = FALSE;
1842
#endif
1843
    buf += start_index;
1844

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

1848
    /* If the first character starts before the left edge, or would be
1849
     * overwritten by a "$" token, then show placeholders instead. */
1850
1851
    if (*buf != '\0' && *buf != '\t' && (start_col < column ||
			(start_col > 0 && isdata && !ISSET(SOFTWRAP)))) {
1852
	if (is_cntrl_mbchar(buf)) {
1853
	    if (start_col < column) {
1854
		converted[index++] = control_mbrep(buf, isdata);
1855
		column++;
1856
		buf += parse_mbchar(buf, NULL, NULL);
1857
	    }
1858
	}
1859
#ifdef ENABLE_UTF8
1860
	else if (mbwidth(buf) == 2) {
1861
	    if (start_col == column) {
1862
		converted[index++] = ' ';
1863
		column++;
1864
1865
	    }

1866
1867
	    /* Display the right half of a two-column character as '<'. */
	    converted[index++] = '<';
1868
	    column++;
1869
	    buf += parse_mbchar(buf, NULL, NULL);
1870
	}
1871
#endif
1872
1873
    }

1874
    while (*buf != '\0' && column < beyond) {
1875
	int charlength, charwidth = 1;
1876

1877
	if (*buf == ' ') {
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
	    /* 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++] = ' ';
1888
	    column++;
1889
1890
	    buf++;
	    continue;
1891
	} else if (*buf == '\t') {
1892
	    /* Show a tab as a visible character, or as as a space. */
1893
#ifndef NANO_TINY
1894
	    if (ISSET(WHITESPACE_DISPLAY)) {
1895
		int i = 0;
1896

1897
1898
		while (i < whitespace_len[0])
		    converted[index++] = whitespace[i++];
1899
	    } else
1900
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1901
		converted[index++] = ' ';
1902
	    column++;
1903
	    /* Fill the tab up with the required number of spaces. */
1904
	    while (column % tabsize != 0 && column < beyond) {
1905
		converted[index++] = ' ';
1906
		column++;
1907
	    }
1908
1909
1910
1911
	    buf++;
	    continue;
	}

1912
	charlength = length_of_char(buf, &charwidth);
1913

1914
	/* If buf contains a control character, represent it. */
1915
	if (is_cntrl_mbchar(buf)) {
1916
	    converted[index++] = '^';
1917
	    converted[index++] = control_mbrep(buf, isdata);
1918
	    column += 2;
1919
1920
1921
	    buf += charlength;
	    continue;
	}
1922

1923
1924
1925
1926
	/* If buf contains a valid non-control character, simply copy it. */
	if (charlength > 0) {
	    for (; charlength > 0; charlength--)
		converted[index++] = *(buf++);
1927

1928
	    column += charwidth;
1929
#ifdef USING_OLD_NCURSES
1930
	    if (charwidth > 1)
1931
		seen_wide = TRUE;
1932
#endif
1933
	    continue;
1934
1935
	}

1936
1937
1938
1939
	/* Represent an invalid sequence with the Replacement Character. */
	converted[index++] = '\xEF';
	converted[index++] = '\xBF';
	converted[index++] = '\xBD';
1940
	column++;
1941
1942
1943
1944
1945
	buf++;

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

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

1952
1953
#ifdef ENABLE_UTF8
	/* Display the left half of a two-column character as '>'. */
1954
	if (mbwidth(converted + index) == 2)
1955
1956
1957
1958
	    converted[index++] = '>';
#endif
    }

1959
1960
    /* Null-terminate the converted string. */
    converted[index] = '\0';
1961

1962
    return converted;
1963
1964
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1965
1966
/* 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
1967
1968
1969
 * 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. */
1970
void titlebar(const char *path)
Chris Allegretta's avatar
Chris Allegretta committed
1971
{
1972
1973
1974
1975
1976
1977
    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. */
1978
1979
    const char *branding = BRANDING;
	/* What is shown in the top left corner. */
1980
1981
1982
1983
    const char *prefix = "";
	/* What is shown before the path -- "File:", "DIR:", or "". */
    const char *state = "";
	/* The state of the current buffer -- "Modified", "View", or "". */
1984
1985
    char *caption;
	/* The presentable form of the pathname. */
1986

1987
1988
1989
1990
    /* If the screen is too small, there is no titlebar. */
    if (topwin == NULL)
	return;

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

1993
    wattron(topwin, interface_color_pair[TITLE_BAR]);
1994

1995
    blank_titlebar();
1996
    as_an_at = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
1997

1998
1999
2000
    /* 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
2001

2002
    /* Figure out the path, prefix and state strings. */
2003
    if (inhelp)
2004
	branding = "";
2005
#ifdef ENABLE_BROWSER
2006
    else if (path != NULL)
2007
2008
	prefix = _("DIR:");
#endif
2009
    else {
2010
2011
2012
2013
2014
2015
	if (openfile->filename[0] == '\0')
	    path = _("New Buffer");
	else {
	    path = openfile->filename;
	    prefix = _("File:");
	}
2016

2017
2018
2019
2020
	if (openfile->modified)
	    state = _("Modified");
	else if (ISSET(VIEW_MODE))
	    state = _("View");
2021

2022
2023
	pluglen = strlenpt(_("Modified")) + 1;
    }
2024

2025
    /* Determine the widths of the four elements, including their padding. */
2026
    verlen = strlenpt(branding) + 3;
2027
2028
2029
    prefixlen = strlenpt(prefix);
    if (prefixlen > 0)
	prefixlen++;
2030
    pathlen = strlenpt(path);
2031
2032
2033
2034
    statelen = strlenpt(state) + 2;
    if (statelen > 2) {
	pathlen++;
	pluglen = 0;
2035
2036
    }

2037
2038
    /* Only print the version message when there is room for it. */
    if (verlen + prefixlen + pathlen + pluglen + statelen <= COLS)
2039
	mvwaddstr(topwin, 0, 2, branding);
2040
2041
2042
2043
2044
2045
2046
2047
2048
    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;
2049
2050
2051
	}
    }

2052
2053
2054
2055
    /* If we have side spaces left, center the path name. */
    if (verlen > 0)
	offset = verlen + (COLS - (verlen + pluglen + statelen) -
					(prefixlen + pathlen)) / 2;
2056

2057
2058
2059
2060
2061
2062
2063
2064
2065
    /* 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. */
2066
2067
2068
2069
2070
    if (pathlen + pluglen + statelen <= COLS) {
	caption = display_string(path, 0, pathlen, FALSE);
	waddstr(topwin, caption);
	free(caption);
    } else if (5 + statelen <= COLS) {
2071
	waddstr(topwin, "...");
2072
	caption = display_string(path, 3 + pathlen - COLS + statelen,
2073
					COLS - statelen, FALSE);
2074
2075
	waddstr(topwin, caption);
	free(caption);
2076
    }
2077

2078
2079
2080
2081
2082
2083
    /* 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));

2084
    wattroff(topwin, interface_color_pair[TITLE_BAR]);
2085

2086
    wrefresh(topwin);
Chris Allegretta's avatar
Chris Allegretta committed
2087
2088
}

2089
2090
2091
2092
2093
2094
/* Display a normal message on the statusbar, quietly. */
void statusbar(const char *msg)
{
    statusline(HUSH, msg);
}

2095
2096
2097
2098
2099
2100
2101
2102
2103
/* 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);
}

2104
/* Display a message on the statusbar, and set suppress_cursorpos to
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2105
2106
 * TRUE, so that the message won't be immediately overwritten if
 * constant cursor position display is on. */
2107
void statusline(message_type importance, const char *msg, ...)
2108
2109
{
    va_list ap;
2110
    static int alerts = 0;
2111
    char *compound, *message;
2112
    size_t start_col;
2113
    bool bracketed;
2114
2115
2116
2117
#ifndef NANO_TINY
    bool old_whitespace = ISSET(WHITESPACE_DISPLAY);

    UNSET(WHITESPACE_DISPLAY);
2118
#endif
2119
2120
2121
2122
2123

    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(). */
2124
    if (isendwin()) {
2125
2126
2127
2128
2129
	vfprintf(stderr, msg, ap);
	va_end(ap);
	return;
    }

2130
2131
2132
2133
2134
    /* If there already was an alert message, ignore lesser ones. */
    if ((lastmessage == ALERT && importance != ALERT) ||
		(lastmessage == MILD && importance == HUSH))
	return;

2135
2136
2137
2138
2139
2140
    /* 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. */
2141
    if (lastmessage == ALERT && alerts < 4 && !ISSET(NO_PAUSES))
2142
2143
	napms(1200);

2144
    if (importance == ALERT) {
2145
	if (++alerts > 3 && !ISSET(NO_PAUSES))
2146
	    msg = _("Further warnings were suppressed");
2147
	beep();
2148
    }
2149
2150

    lastmessage = importance;
2151

2152
2153
2154
    /* Turn the cursor off while fiddling in the statusbar. */
    curs_set(0);

2155
2156
    blank_statusbar();

2157
    /* Construct the message out of all the arguments. */
2158
2159
    compound = charalloc(MAXCHARLEN * (COLS + 1));
    vsnprintf(compound, MAXCHARLEN * (COLS + 1), msg, ap);
2160
    va_end(ap);
2161
2162
    message = display_string(compound, 0, COLS, FALSE);
    free(compound);
2163

2164
2165
    start_col = (COLS - strlenpt(message)) / 2;
    bracketed = (start_col > 1);
2166

2167
    wmove(bottomwin, 0, (bracketed ? start_col - 2 : start_col));
2168
    wattron(bottomwin, interface_color_pair[STATUS_BAR]);
2169
2170
    if (bracketed)
	waddstr(bottomwin, "[ ");
2171
2172
    waddstr(bottomwin, message);
    free(message);
2173
2174
    if (bracketed)
	waddstr(bottomwin, " ]");
2175
    wattroff(bottomwin, interface_color_pair[STATUS_BAR]);
2176

2177
2178
2179
2180
    /* Defeat a VTE/Konsole bug, where the cursor can go off-limits. */
    if (ISSET(CONSTANT_SHOW) && ISSET(NO_HELP))
	wmove(bottomwin, 0, 0);

2181
    /* Push the message to the screen straightaway. */
2182
    wrefresh(bottomwin);
2183

2184
    suppress_cursorpos = TRUE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2185

2186
#ifndef NANO_TINY
2187
2188
    if (old_whitespace)
	SET(WHITESPACE_DISPLAY);
2189
#endif
2190
2191
2192

    /* If doing quick blanking, blank the statusbar after just one keystroke.
     * Otherwise, blank it after twenty-six keystrokes, as Pico does. */
2193
2194
2195
2196
    if (ISSET(QUICK_BLANK))
	statusblank = 1;
    else
	statusblank = 26;
2197
2198
}

2199
2200
/* Display the shortcut list corresponding to menu on the last two rows
 * of the bottom portion of the window. */
2201
void bottombars(int menu)
Chris Allegretta's avatar
Chris Allegretta committed
2202
{
2203
    size_t number, itemwidth, i;
2204
2205
    subnfunc *f;
    const sc *s;
2206

2207
2208
2209
    /* Set the global variable to the given menu. */
    currmenu = menu;

2210
    if (ISSET(NO_HELP) || LINES < 5)
Chris Allegretta's avatar
Chris Allegretta committed
2211
2212
	return;

2213
    /* Determine how many shortcuts there are to show. */
2214
    number = length_of_list(menu);
2215

2216
2217
    if (number > MAIN_VISIBLE)
	number = MAIN_VISIBLE;
2218

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

2222
    /* If there is no room, don't print anything. */
2223
    if (itemwidth == 0)
2224
2225
	return;

2226
    blank_bottombars();
2227

2228
2229
    /* Display the first number of shortcuts in the given menu that
     * have a key combination assigned to them. */
2230
    for (f = allfuncs, i = 0; i < number && f != NULL; f = f->next) {
2231
	if ((f->menus & menu) == 0)
2232
	    continue;
2233

2234
	s = first_sc_for(menu, f->scfunc);
2235
	if (s == NULL)
2236
	    continue;
2237
2238

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

2240
	onekey(s->keystr, _(f->desc), itemwidth + (COLS % itemwidth));
2241
	i++;
Chris Allegretta's avatar
Chris Allegretta committed
2242
    }
2243

2244
    /* Defeat a VTE bug by homing the cursor and forcing a screen update. */
2245
    wmove(bottomwin, 0, 0);
2246
    wrefresh(bottomwin);
Chris Allegretta's avatar
Chris Allegretta committed
2247
2248
}

2249
2250
/* 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
2251
 * to write at most length characters, even if length is very small and
2252
2253
 * keystroke and desc are long.  Note that waddnstr(,,(size_t)-1) adds
 * the whole string!  We do not bother padding the entry with blanks. */
2254
void onekey(const char *keystroke, const char *desc, int length)
Chris Allegretta's avatar
Chris Allegretta committed
2255
{
2256
2257
    assert(keystroke != NULL && desc != NULL);

2258
    wattron(bottomwin, interface_color_pair[KEY_COMBO]);
2259
    waddnstr(bottomwin, keystroke, actual_x(keystroke, length));
2260
    wattroff(bottomwin, interface_color_pair[KEY_COMBO]);
2261

2262
    length -= strlenpt(keystroke) + 1;
2263

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

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

2279
#ifndef NANO_TINY
2280
    if (ISSET(SOFTWRAP)) {
2281
	filestruct *line = openfile->edittop;
2282
	size_t leftedge;
2283

2284
	row -= chunk_for(openfile->firstcolumn, openfile->edittop);
2285

2286
	/* Calculate how many rows the lines from edittop to current use. */
2287
	while (line != NULL && line != openfile->current) {
2288
	    row += number_of_chunks_in(line) + 1;
2289
2290
2291
	    line = line->next;
	}

2292
	/* Add the number of wraps in the current line before the cursor. */
2293
	row += get_chunk_and_edge(xpt, openfile->current, &leftedge);
2294
	col = xpt - leftedge;
2295
2296
2297
    } else
#endif
    {
2298
2299
	row = openfile->current->lineno - openfile->edittop->lineno;
	col = xpt - get_page_start(xpt);
2300
    }
2301
2302
2303
2304

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

2305
    openfile->current_y = row;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2306
}
Chris Allegretta's avatar
Chris Allegretta committed
2307

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

2329
    assert(openfile != NULL && fileptr != NULL && converted != NULL);
2330
2331
2332
    assert(strlenpt(converted) <= editwincols);

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

2347
2348
2349
    /* 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);
2350

2351
#ifdef USING_OLD_NCURSES
2352
2353
2354
    /* 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. */
2355
    if (seen_wide)
2356
	wredrawln(edit, row, 1);
2357
#endif
2358

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

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

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

2387
2388
2389
	    /* 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. */
2390

2391
2392
	    wattron(edit, varnish->attributes);

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

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

2416
		    /* Translate the match to the beginning of the line. */
2417
2418
2419
		    match.rm_so += index;
		    match.rm_eo += index;
		    index = match.rm_eo;
2420

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

2425
2426
2427
		    start_col = (match.rm_so <= from_x) ?
					0 : strnlenpt(fileptr->data,
					match.rm_so) - from_col;
2428

2429
		    thetext = converted + actual_x(converted, start_col);
2430

2431
		    paintlen = actual_x(thetext, strnlenpt(fileptr->data,
2432
					match.rm_eo) - from_col - start_col);
2433

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

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

2442
2443
2444
	    /* Assume nothing gets painted until proven otherwise below. */
	    fileptr->multidata[varnish->id] = CNONE;

2445
2446
2447
2448
	    /* 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 ||
2449
2450
			start_line->multidata[varnish->id] == CENDAFTER ||
			start_line->multidata[varnish->id] == CWOULDBE)
2451
2452
2453
		    goto seek_an_end;
		if (start_line->multidata[varnish->id] == CNONE ||
			start_line->multidata[varnish->id] == CBEGINBEFORE ||
2454
			start_line->multidata[varnish->id] == CSTARTENDHERE)
2455
2456
2457
		    goto step_two;
	    }

2458
2459
	    /* The preceding line has no precalculated multidata.  So, do
	     * some backtracking to find out what to paint. */
2460

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

2472
2473
2474
2475
2476
2477
2478
	    /* 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 &&
2479
2480
			(start_line->multidata[varnish->id] == CBEGINBEFORE ||
			start_line->multidata[varnish->id] == CSTARTENDHERE))
2481
		goto step_two;
2482

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

2507
  seek_an_end:
2508
2509
	    /* We've already checked that there is no end between the start
	     * and the current line.  But is there an end after the start
2510
2511
2512
2513
2514
	     * 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;

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

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

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

2536
  step_two:
2537
2538
2539
	    /* 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;
2540

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

2549
		start_col = (startmatch.rm_so <= from_x) ?
2550
				0 : strnlenpt(fileptr->data,
2551
				startmatch.rm_so) - from_col;
2552

2553
		thetext = converted + actual_x(converted, start_col);
2554

2555
2556
		if (regexec(varnish->end, fileptr->data + startmatch.rm_eo,
				1, &endmatch, (startmatch.rm_eo == 0) ?
2557
				0 : REG_NOTBOL) == 0) {
2558
2559
2560
2561
		    /* 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;
2562
2563
		    /* Only paint the match if it is visible on screen and
		     * it is more than zero characters long. */
2564
		    if (endmatch.rm_eo > from_x &&
2565
					endmatch.rm_eo > startmatch.rm_so) {
2566
			paintlen = actual_x(thetext, strnlenpt(fileptr->data,
2567
					endmatch.rm_eo) - from_col - start_col);
2568

2569
			mvwaddnstr(edit, row, margin + start_col,
2570
						thetext, paintlen);
2571

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

2585
2586
		/* There is no end on this line.  But maybe on later lines? */
		end_line = fileptr->next;
2587

2588
2589
2590
		while (end_line != NULL && regexec(varnish->end, end_line->data,
					0, NULL, 0) == REG_NOMATCH)
		    end_line = end_line->next;
2591

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

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

2609
#ifndef NANO_TINY
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
    /* 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. */
2621
	int start_col;
2622
2623
2624
2625
2626
	    /* 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". */
2627

2628
	mark_order(&top, &top_x, &bot, &bot_x, NULL);
2629

2630
2631
2632
2633
	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
2634

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

2640
2641
2642
	    if (start_col < 0)
		start_col = 0;

2643
	    thetext = converted + actual_x(converted, start_col);
2644

2645
2646
2647
2648
2649
2650
	    /* 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);
	    }
2651

2652
	    wattron(edit, interface_color_pair[SELECTED_TEXT]);
2653
	    mvwaddnstr(edit, row, margin + start_col, thetext, paintlen);
2654
	    wattroff(edit, interface_color_pair[SELECTED_TEXT]);
Chris Allegretta's avatar
Chris Allegretta committed
2655
	}
2656
    }
2657
#endif /* !NANO_TINY */
Chris Allegretta's avatar
Chris Allegretta committed
2658
2659
}

2660
2661
2662
/* 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
2663
2664
2665
 * 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). */
2666
int update_line(filestruct *fileptr, size_t index)
Chris Allegretta's avatar
Chris Allegretta committed
2667
{
2668
    int row = 0;
2669
	/* The row in the edit window we will be updating. */
2670
    char *converted;
2671
	/* The data of the line with tabs and control characters expanded. */
2672
2673
    size_t from_col = 0;
	/* From which column a horizontally scrolled line is displayed. */
Chris Allegretta's avatar
Chris Allegretta committed
2674

2675
#ifndef NANO_TINY
2676
2677
    if (ISSET(SOFTWRAP))
	return update_softwrapped_line(fileptr);
2678
#endif
2679
2680

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

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

2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
    /* 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, '$');

2710
    return 1;
Chris Allegretta's avatar
Chris Allegretta committed
2711
2712
}

2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
#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. */
2727
2728
    size_t to_col = 0;
	/* To which column a line is displayed. */
2729
2730
2731
2732
2733
2734
    char *converted;
	/* The data of the chunk with tabs and control characters expanded. */

    if (fileptr == openfile->edittop)
	from_col = openfile->firstcolumn;
    else
2735
	row -= chunk_for(openfile->firstcolumn, openfile->edittop);
2736
2737
2738

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

2743
2744
2745
2746
    /* 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);
2747
	return 0;
2748
    }
2749
2750
2751

    starting_row = row;

2752
    while (row < editwinrows) {
2753
	bool end_of_line = FALSE;
2754
2755
2756

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

2757
2758
2759
	blank_row(edit, row, 0, COLS);

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

2764
2765
2766
2767
	if (end_of_line)
	    break;

	/* If the line is softwrapped before its last column, add a ">" just
2768
2769
2770
	 * 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)
2771
2772
2773
	    mvwaddch(edit, row - 1, to_col - from_col, '>');

	from_col = to_col;
2774
2775
2776
2777
2778
2779
    }

    return (row - starting_row);
}
#endif

2780
2781
2782
2783
/* 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)
2784
{
2785
#ifndef NANO_TINY
2786
2787
2788
    if (openfile->mark_set)
	return TRUE;
    else
2789
#endif
2790
	return (get_page_start(old_column) != get_page_start(new_column));
2791
2792
}

2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
/* 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)) {
2803
	/* Recede through the requested number of chunks. */
2804
	for (i = nrows; i > 0; i--) {
2805
2806
2807
2808
2809
2810
	    size_t chunk = chunk_for(*leftedge, *line);

	    *leftedge = 0;

	    if (chunk >= i)
		return go_forward_chunks(chunk - i, line, leftedge);
2811
2812
2813
2814

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

2815
	    i -= chunk;
2816
	    *line = (*line)->prev;
2817
	    *leftedge = HIGHEST_POSITIVE;
2818
2819
	}

2820
2821
	if (*leftedge == HIGHEST_POSITIVE)
	    *leftedge = leftedge_for(*leftedge, *line);
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
    } 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)) {
2840
	size_t current_leftedge = *leftedge;
2841

2842
	/* Advance through the requested number of chunks. */
2843
	for (i = nrows; i > 0; i--) {
2844
	    bool end_of_line = FALSE;
2845

2846
2847
2848
2849
	    current_leftedge = get_softwrap_breakpoint((*line)->data,
					current_leftedge, &end_of_line);

	    if (!end_of_line)
2850
2851
2852
2853
2854
2855
		continue;

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

	    *line = (*line)->next;
2856
	    current_leftedge = 0;
2857
2858
2859
2860
	}

	/* Only change leftedge when we actually could move. */
	if (i < nrows)
2861
	    *leftedge = current_leftedge;
2862
2863
2864
2865
2866
2867
2868
2869
    } else
#endif
	for (i = nrows; i > 0 && (*line)->next != NULL; i--)
	    *line = (*line)->next;

    return i;
}

2870
2871
2872
2873
2874
2875
2876
2877
/* 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;
2878
	size_t leftedge = leftedge_for(xplustabs(), openfile->current);
2879
2880
2881
2882
2883
2884
2885
2886
2887
	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);
}

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

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

2898
2899
    /* 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. */
2900
    if (direction == UPWARD)
2901
	nrows -= go_back_chunks(nrows, &openfile->edittop, &openfile->firstcolumn);
2902
    else
2903
	nrows -= go_forward_chunks(nrows, &openfile->edittop, &openfile->firstcolumn);
2904

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

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

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

2929
2930
2931
2932
    /* 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++;
2933

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

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

2942
#ifndef NANO_TINY
2943
2944
2945
    if (ISSET(SOFTWRAP)) {
	/* Compensate for the earlier chunks of a softwrapped line. */
	nrows += chunk_for(leftedge, line);
2946

2947
2948
2949
2950
	/* Don't compensate for the chunks that are offscreen. */
	if (line == openfile->edittop)
	    nrows -= chunk_for(openfile->firstcolumn, line);
    }
2951
#endif
2952
2953
2954

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

2962
#ifndef NANO_TINY
2963
2964
2965
2966
2967
2968
2969
/* 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)
{
2970
2971
    size_t index = 0;
	/* Current index in text. */
2972
2973
2974
2975
2976
2977
    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. */
2978
2979
2980
2981
2982
2983
    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. */
2984
2985
2986
2987
2988
2989
    int char_len = 0;
	/* Length of current character, in bytes. */

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

2990
2991
    /* Use a full screen row for text. */
    goal_column = column + editwincols;
2992
2993

    while (*text != '\0' && column <= goal_column) {
2994
2995
	/* When breaking at blanks, do it *before* the target column. */
	if (ISSET(AT_BLANKS) && is_blank_mbchar(text) && column < goal_column) {
2996
2997
2998
2999
3000
	    found_blank = TRUE;
	    lastblank_index = index;
	    lastblank_column = column;
	}

3001
3002
3003
	prev_column = column;
	char_len = parse_mbchar(text, NULL, &column);
	text += char_len;
3004
	index += char_len;
3005
3006
    }

3007
    /* If we didn't overshoot the target, we've found a breaking point. */
3008
    if (column <= goal_column) {
3009
3010
	/* We've reached EOL if we didn't even reach the target. */
	*end_of_line = (column < goal_column);
3011
3012
3013
	return column;
    }

3014
3015
3016
3017
3018
3019
3020
3021
    /* 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);
	return lastblank_column;
    }

3022
3023
3024
    /* 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;
3025
3026
3027
3028
3029
}

/* 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. */
3030
size_t get_chunk_and_edge(size_t column, filestruct *line, size_t *leftedge)
3031
3032
{
    size_t current_chunk = 0, start_col = 0, end_col;
3033
    bool end_of_line = FALSE;
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049

    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;
    }
}

3050
3051
/* Return the row of the softwrapped chunk of the given line that column is on,
 * relative to the first row (zero-based). */
3052
size_t chunk_for(size_t column, filestruct *line)
3053
{
3054
    return get_chunk_and_edge(column, line, NULL);
3055
3056
3057
3058
}

/* Return the leftmost column of the softwrapped chunk of the given line that
 * column is on. */
3059
size_t leftedge_for(size_t column, filestruct *line)
3060
{
3061
3062
    size_t leftedge;

3063
    get_chunk_and_edge(column, line, &leftedge);
3064
3065

    return leftedge;
3066
3067
3068
3069
}

/* Return the row of the last softwrapped chunk of the given line, relative to
 * the first row (zero-based). */
3070
size_t number_of_chunks_in(filestruct *line)
3071
{
3072
    return chunk_for((size_t)-1, line);
3073
3074
}

3075
/* Ensure that firstcolumn is at the starting column of the softwrapped chunk
3076
3077
3078
3079
 * 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)
{
3080
3081
    openfile->firstcolumn = leftedge_for(openfile->firstcolumn,
						openfile->edittop);
3082
3083
3084

    /* If smooth scrolling is on, make sure the viewport doesn't center. */
    focusing = FALSE;
3085
}
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
#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
3102
	 * a multi-column character, but actual_x() will fix that later. */
3103
3104
3105
3106
3107
3108
	if (!last_chunk)
	    end_col--;

	if (column > end_col)
	    column = end_col;
    }
3109
#endif
3110

3111
3112
3113
    return leftedge + column;
}

3114
3115
3116
3117
/* Return TRUE if current[current_x] is above the top of the screen, and FALSE
 * otherwise. */
bool current_is_above_screen(void)
{
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
#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);
3128
3129
3130
3131
3132
3133
3134
3135
3136
}

/* 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;
3137
	size_t leftedge = openfile->firstcolumn;
3138
3139

	/* If current[current_x] is more than a screen's worth of lines after
3140
	 * edittop at column firstcolumn, it's below the screen. */
3141
3142
3143
	return (go_forward_chunks(editwinrows - 1, &line, &leftedge) == 0 &&
			(line->lineno < openfile->current->lineno ||
			(line->lineno == openfile->current->lineno &&
3144
3145
			leftedge < leftedge_for(xplustabs(),
						openfile->current))));
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
    } 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());
}

3159
/* Update any lines between old_current and current that need to be
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3160
 * updated.  Use this if we've moved without changing any text. */
3161
void edit_redraw(filestruct *old_current)
3162
{
3163
3164
3165
3166
    size_t was_pww = openfile->placewewant;

    openfile->placewewant = xplustabs();

3167
    /* If the current line is offscreen, scroll until it's onscreen. */
3168
    if (current_is_offscreen()) {
3169
	adjust_viewport((focusing || !ISSET(SMOOTH_SCROLL)) ? CENTERING : FLOWING);
3170
	refresh_needed = TRUE;
3171
	return;
3172
    }
3173

3174
#ifndef NANO_TINY
3175
3176
    /* If the mark is on, update all lines between old_current and current. */
    if (openfile->mark_set) {
3177
	filestruct *line = old_current;
3178

3179
3180
	while (line != openfile->current) {
	    update_line(line, 0);
3181

3182
3183
	    line = (line->lineno > openfile->current->lineno) ?
			line->prev : line->next;
3184
	}
3185
3186
3187
3188
3189
3190
    } 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);
3191

3192
3193
    /* 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. */
3194
    if (line_needs_update(was_pww, openfile->placewewant) ||
3195
			(old_current != openfile->current &&
3196
			get_page_start(openfile->placewewant) > 0))
3197
	update_line(openfile->current, openfile->current_x);
3198
3199
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3200
3201
/* 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
3202
3203
void edit_refresh(void)
{
3204
3205
    filestruct *line;
    int row = 0;
3206

3207
3208
3209
3210
3211
3212
#ifndef DISABLE_COLOR
    /* When needed, initialize the colors for the current syntax. */
    if (!have_palette)
	color_init();
#endif

3213
    /* If the current line is out of view, get it back on screen. */
3214
    if (current_is_offscreen()) {
3215
#ifdef DEBUG
3216
3217
	fprintf(stderr, "edit-refresh: line = %ld, edittop = %ld and editwinrows = %d\n",
		(long)openfile->current->lineno, (long)openfile->edittop->lineno, editwinrows);
3218
#endif
3219
	adjust_viewport((focusing || !ISSET(SMOOTH_SCROLL)) ? CENTERING : STATIONARY);
3220
    }
Chris Allegretta's avatar
Chris Allegretta committed
3221

3222
#ifdef DEBUG
3223
    fprintf(stderr, "edit-refresh: now edittop = %ld\n", (long)openfile->edittop->lineno);
3224
#endif
3225

3226
3227
3228
3229
    line = openfile->edittop;

    while (row < editwinrows && line != NULL) {
	if (line == openfile->current)
3230
	    row += update_line(line, openfile->current_x);
3231
	else
3232
	    row += update_line(line, 0);
3233
	line = line->next;
3234
3235
    }

3236
    while (row < editwinrows)
3237
	blank_row(edit, row++, 0, COLS);
3238

3239
    place_the_cursor();
3240
    wnoutrefresh(edit);
3241
3242

    refresh_needed = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
3243
3244
}

3245
3246
3247
3248
3249
/* 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. */
3250
void adjust_viewport(update_type manner)
Chris Allegretta's avatar
Chris Allegretta committed
3251
{
3252
    int goal = 0;
3253

3254
    if (manner == STATIONARY)
3255
	goal = openfile->current_y;
3256
3257
3258
3259
    else if (manner == CENTERING)
	goal = editwinrows / 2;
    else if (!current_is_above_screen())
	goal = editwinrows - 1;
3260

3261
    openfile->edittop = openfile->current;
3262
#ifndef NANO_TINY
3263
    if (ISSET(SOFTWRAP))
3264
	openfile->firstcolumn = leftedge_for(xplustabs(), openfile->current);
3265
#endif
3266
3267

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

3270
#ifdef DEBUG
3271
    fprintf(stderr, "adjust_viewport(): setting edittop to lineno %ld\n", (long)openfile->edittop->lineno);
3272
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3273
3274
}

3275
/* Unconditionally redraw the entire screen. */
3276
void total_redraw(void)
3277
{
3278
3279
3280
3281
3282
3283
#ifdef USE_SLANG
    /* Slang curses emulation brain damage, part 4: Slang doesn't define
     * curscr. */
    SLsmg_touch_screen();
    SLsmg_refresh();
#else
3284
    wrefresh(curscr);
3285
#endif
3286
3287
}

3288
3289
/* 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. */
3290
3291
void total_refresh(void)
{
3292
    total_redraw();
3293
3294
    if (currmenu != MBROWSER && currmenu != MWHEREISFILE && currmenu != MGOTODIR)
	titlebar(title);
3295
#ifdef ENABLE_HELP
3296
    if (inhelp)
3297
	wrap_the_help_text(TRUE);
3298
3299
    else
#endif
3300
    if (currmenu != MBROWSER && currmenu != MWHEREISFILE && currmenu != MGOTODIR)
3301
	edit_refresh();
3302
    bottombars(currmenu);
3303
3304
}

3305
3306
/* Display the main shortcut list on the last two rows of the bottom
 * portion of the window. */
3307
3308
void display_main_list(void)
{
3309
#ifndef DISABLE_COLOR
3310
3311
    if (openfile->syntax &&
		(openfile->syntax->formatter || openfile->syntax->linter))
3312
	set_lint_or_format_shortcuts();
3313
3314
3315
3316
    else
	set_spell_shortcuts();
#endif

3317
    bottombars(MMAIN);
3318
3319
}

3320
3321
3322
3323
/* 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
3324
{
3325
3326
    char saved_byte;
    size_t sum, cur_xpt = xplustabs() + 1;
3327
    size_t cur_lenpt = strlenpt(openfile->current->data) + 1;
3328
    int linepct, colpct, charpct;
Chris Allegretta's avatar
Chris Allegretta committed
3329

3330
3331
3332
3333
3334
    /* If the showing needs to be suppressed, don't suppress it next time. */
    if (suppress_cursorpos && !force) {
	suppress_cursorpos = FALSE;
	return;
    }
3335

3336
3337
3338
    /* Hide the cursor while we are calculating. */
    curs_set(0);

3339
    /* Determine the size of the file up to the cursor. */
3340
    saved_byte = openfile->current->data[openfile->current_x];
3341
    openfile->current->data[openfile->current_x] = '\0';
3342

3343
    sum = get_totsize(openfile->fileage, openfile->current);
3344

3345
    openfile->current->data[openfile->current_x] = saved_byte;
3346
3347
3348

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

3351
    /* Display the current cursor position on the statusbar. */
3352
    linepct = 100 * openfile->current->lineno / openfile->filebot->lineno;
3353
    colpct = 100 * cur_xpt / cur_lenpt;
3354
    charpct = (openfile->totsize == 0) ? 0 : 100 * sum / openfile->totsize;
3355

3356
    statusline(HUSH,
3357
	_("line %ld/%ld (%d%%), col %lu/%lu (%d%%), char %lu/%lu (%d%%)"),
3358
	(long)openfile->current->lineno,
3359
	(long)openfile->filebot->lineno, linepct,
3360
	(unsigned long)cur_xpt, (unsigned long)cur_lenpt, colpct,
3361
	(unsigned long)sum, (unsigned long)openfile->totsize, charpct);
3362
3363
3364

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

3367
/* Unconditionally display the current cursor position. */
3368
void do_cursorpos_void(void)
3369
{
3370
    do_cursorpos(TRUE);
3371
3372
}

3373
void disable_waiting(void)
3374
{
3375
    waiting_mode = FALSE;
3376
    nodelay(edit, TRUE);
3377
3378
}

3379
void enable_waiting(void)
3380
{
3381
    waiting_mode = TRUE;
3382
    nodelay(edit, FALSE);
3383
3384
}

3385
3386
3387
/* 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
3388
{
3389
3390
    char *word;
    size_t word_span, room;
Chris Allegretta's avatar
Chris Allegretta committed
3391

3392
    place_the_cursor();
3393

3394
3395
3396
3397
#ifndef NANO_TINY
    if (ISSET(SOFTWRAP)) {
	spotlight_softwrapped(active, from_col, to_col);
	return;
3398
    }
3399
#endif
3400

3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
    /* 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--;

3418
    if (active)
3419
	wattron(edit, interface_color_pair[SELECTED_TEXT]);
Chris Allegretta's avatar
Chris Allegretta committed
3420

3421
    waddnstr(edit, word, actual_x(word, room));
3422

3423
    if (word_span > room)
3424
	waddch(edit, '$');
Chris Allegretta's avatar
Chris Allegretta committed
3425

3426
    if (active)
3427
	wattroff(edit, interface_color_pair[SELECTED_TEXT]);
3428

3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
    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)
{
3440
    ssize_t row = openfile->current_y;
3441
    size_t leftedge = leftedge_for(from_col, openfile->current);
3442
    size_t break_col;
3443
    bool end_of_line = FALSE;
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
    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)
3466
	    wattron(edit, interface_color_pair[SELECTED_TEXT]);
3467
3468
3469
3470

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

	if (active)
3471
	    wattroff(edit, interface_color_pair[SELECTED_TEXT]);
3472
3473
3474
3475
3476
3477

	free(word);

	if (end_of_line)
	    break;

3478
	wmove(edit, ++row, 0);
3479
3480
3481
3482
3483

	leftedge = break_col;
	from_col = break_col;
    }

3484
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
3485
}
3486
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3487

3488
#ifndef DISABLE_EXTRA
3489
3490
#define CREDIT_LEN 54
#define XLCREDIT_LEN 9
3491

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

3556
    const char *xlcredits[XLCREDIT_LEN] = {
3557
3558
3559
3560
3561
	N_("The nano text editor"),
	N_("version"),
	N_("Brought to you by:"),
	N_("Special thanks to:"),
	N_("The Free Software Foundation"),
3562
	N_("the many translators and the TP"),
3563
3564
3565
	N_("For ncurses:"),
	N_("and anyone else we forgot..."),
	N_("Thank you for using nano!")
3566
    };
3567

3568
3569
3570
3571
3572
3573
    if (!old_more_space || !old_no_help) {
	SET(MORE_SPACE);
	SET(NO_HELP);
	window_init();
    }

3574
3575
    curs_set(0);
    nodelay(edit, TRUE);
3576

3577
    blank_titlebar();
Chris Allegretta's avatar
Chris Allegretta committed
3578
    blank_edit();
3579
    blank_statusbar();
3580

3581
    wrefresh(topwin);
Chris Allegretta's avatar
Chris Allegretta committed
3582
    wrefresh(edit);
3583
    wrefresh(bottomwin);
3584
    napms(700);
3585

3586
    for (crpos = 0; crpos < CREDIT_LEN + editwinrows / 2; crpos++) {
3587
	if ((kbinput = wgetch(edit)) != ERR)
3588
	    break;
3589

3590
	if (crpos < CREDIT_LEN) {
3591
	    const char *what;
3592
	    size_t start_col;
3593

3594
3595
3596
	    if (credits[crpos] == NULL)
		what = _(xlcredits[xlpos++]);
	    else
3597
		what = credits[crpos];
3598

3599
	    start_col = COLS / 2 - strlenpt(what) / 2 - 1;
3600
	    mvwaddstr(edit, editwinrows - 1 - (editwinrows % 2),
3601
						start_col, what);
3602
	}
3603

3604
3605
3606
3607
	wrefresh(edit);

	if ((kbinput = wgetch(edit)) != ERR)
	    break;
3608
	napms(700);
3609

3610
	scrollok(edit, TRUE);
3611
	wscrl(edit, 1);
3612
	scrollok(edit, FALSE);
3613
	wrefresh(edit);
3614

3615
	if ((kbinput = wgetch(edit)) != ERR)
3616
	    break;
3617
	napms(700);
3618

3619
	scrollok(edit, TRUE);
3620
	wscrl(edit, 1);
3621
	scrollok(edit, FALSE);
3622
	wrefresh(edit);
3623
3624
    }

3625
3626
3627
    if (kbinput != ERR)
	ungetch(kbinput);

3628
    if (!old_more_space)
3629
	UNSET(MORE_SPACE);
3630
    if (!old_no_help)
3631
	UNSET(NO_HELP);
3632
    window_init();
3633

3634
    nodelay(edit, FALSE);
3635

3636
    total_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
3637
}
3638
#endif /* !DISABLE_EXTRA */