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
#ifdef __linux__
26
#include <sys/ioctl.h>
27
#endif
28

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return input;
289
290
}

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

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

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

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

309
310
311
    return kbinput;
}

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

324
    meta_key = FALSE;
325
    shift_held = FALSE;
326

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

330
    if (kbinput == NULL && !waiting_mode)
331
332
333
	return 0;

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

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

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

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

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

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

		    byte_digits++;
		    byte = get_byte_kbinput(keycode);

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

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

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

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

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

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

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

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

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

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

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

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

714
715
716
    return retval;
}

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

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

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

949
	switch (seq[3]) {
950
	    case '2':
951
952
953
954
955
		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. */
956
			shift_held = TRUE;
957
958
			return arrow_from_abcd(seq[4]);
		}
959
		break;
960
961
962
963
964
965
966
967
968
969
#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. */
970
			return SHIFT_END;
971
		    case 'D': /* Esc [ 1 ; 4 D == Shift-Alt-Left on xterm. */
972
			return SHIFT_HOME;
973
974
975
		}
		break;
#endif
976
	    case '5':
977
978
979
980
981
982
983
984
985
		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;
986
987
988
989
		    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;
990
		}
991
		break;
992
993
994
995
996
997
998
999
1000
1001
1002
#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;
1003
1004
1005
1006
		    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;
1007
1008
1009
		}
		break;
#endif
1010
	}
1011
1012
1013
1014

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

1207
    return ERR;
1208
1209
}

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

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

    free(seq);

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

1261
#ifdef DEBUG
1262
1263
    fprintf(stderr, "parse_escape_sequence(): kbinput = %d, seq_len = %lu, retval = %d\n",
		kbinput, (unsigned long)seq_len, retval);
1264
1265
1266
1267
1268
#endif

    return retval;
}

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

1276
1277
    /* Increment the byte digit counter. */
    byte_digits++;
1278

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

    /* If we have a result, reset the byte digit counter and the byte
     * sequence holder. */
    if (retval != ERR) {
	byte_digits = 0;
1330
	byte = 0;
1331
1332
1333
    }

#ifdef DEBUG
1334
    fprintf(stderr, "get_byte_kbinput(): kbinput = %d, byte_digits = %d, byte = %d, retval = %d\n", kbinput, byte_digits, byte, retval);
1335
1336
1337
1338
1339
#endif

    return retval;
}

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

1353
    return ERR;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1354
1355
}

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

1365
    /* Increment the Unicode digit counter. */
1366
    uni_digits++;
1367

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

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

1418
#ifdef DEBUG
1419
1420
    fprintf(stderr, "get_unicode_kbinput(): kbinput = %d, uni_digits = %d, uni = %ld, retval = %ld\n",
						kbinput, uni_digits, uni, retval);
1421
1422
#endif

1423
1424
1425
1426
    /* If we have an end result, reset the Unicode digit counter. */
    if (retval != ERR)
	uni_digits = 0;

1427
1428
    return retval;
}
1429
#endif /* ENABLE_UTF8 */
1430

1431
1432
1433
1434
1435
1436
/* Translate a control character sequence: turn an ASCII non-control
 * character into its corresponding control character. */
int get_control_kbinput(int kbinput)
{
    int retval;

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

1458
#ifdef DEBUG
1459
    fprintf(stderr, "get_control_kbinput(): kbinput = %d, retval = %d\n", kbinput, retval);
1460
1461
#endif

1462
1463
    return retval;
}
1464

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

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

1479
    /* Read in one keycode, or one or two escapes. */
1480
    retval = parse_verbatim_kbinput(win, kbinput_len);
1481

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

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

1500
    return retval;
1501
1502
}

1503
1504
1505
1506
/* 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). */
1507
int *parse_verbatim_kbinput(WINDOW *win, size_t *count)
1508
{
1509
    int *kbinput;
1510

1511
    /* Read in the first code. */
1512
1513
    while ((kbinput = get_input(win, 1)) == NULL)
	;
1514

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

1524
1525
    *count = 1;

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

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

1540
	    while (unicode == ERR) {
1541
		free(kbinput);
1542
1543
		while ((kbinput = get_input(win, 1)) == NULL)
		    ;
1544
		unicode = get_unicode_kbinput(win, *kbinput);
1545
	    }
1546

1547
	    /* Convert the Unicode value to a multibyte sequence. */
1548
	    multibyte = make_mbchar(unicode, (int *)count);
1549

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

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

1563
1564
    free(kbinput);

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

    return get_input(NULL, *count);
1571
1572
}

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

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

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

1601
    /* Save the screen coordinates where the mouse event took place. */
1602
    *mouse_x = mevent.x - margin;
1603
    *mouse_y = mevent.y;
1604

1605
1606
    in_bottomwin = wenclose(bottomwin, *mouse_y, *mouse_x);

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

1623
1624
1625
1626
1627
1628
1629
1630
1631
	    /* 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. */
1632
		*mouse_x = mevent.x - margin;
1633
1634
1635
1636
		*mouse_y = mevent.y;

		return 0;
	    }
1637

1638
	    /* Determine how many shortcuts are being shown. */
1639
	    number = length_of_list(currmenu);
1640

1641
1642
	    if (number > MAIN_VISIBLE)
		number = MAIN_VISIBLE;
1643

1644
1645
1646
	    /* 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. */
1647
	    if (number < 2)
1648
1649
		i = COLS / (MAIN_VISIBLE / 2);
	    else
1650
		i = COLS / ((number / 2) + (number % 2));
1651

1652
1653
	    /* Calculate the one-based index in the shortcut list. */
	    j = (*mouse_x / i) * 2 + *mouse_y;
1654

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

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

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

1700
1701
1702
1703
	if (in_bottomwin)
	    /* Translate the mouse event coordinates so that they're
	     * relative to bottomwin. */
	    wmouse_trafo(bottomwin, mouse_y, mouse_x, FALSE);
1704

1705
	if (in_edit || (in_bottomwin && *mouse_y == 0)) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1706
1707
	    int i;

1708
1709
1710
1711
1712
	    /* 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) ?
1713
				KEY_PPAGE : KEY_NPAGE, FALSE);
1714
1715
1716
1717
1718
1719
1720

	    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;
1721
1722
    }
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1723
1724
1725

    /* Ignore all other mouse events. */
    return 2;
1726
}
1727
#endif /* ENABLE_MOUSE */
1728

1729
1730
1731
1732
/* 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. */
1733
const sc *get_shortcut(int *kbinput)
1734
{
1735
    sc *s;
1736

1737
#ifdef DEBUG
1738
1739
    fprintf(stderr, "get_shortcut(): kbinput = %d, meta_key = %s -- ",
				*kbinput, meta_key ? "TRUE" : "FALSE");
1740
1741
#endif

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

    return NULL;
}

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

1765
1766
1767
1768
    for (; n > 0; n--)
	waddch(win, ' ');
}

1769
/* Blank the first line of the top portion of the window. */
1770
void blank_titlebar(void)
Chris Allegretta's avatar
Chris Allegretta committed
1771
{
1772
    blank_row(topwin, 0, 0, COLS);
1773
1774
}

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

1781
1782
    for (row = 0; row < editwinrows; row++)
	blank_row(edit, row, 0, COLS);
Chris Allegretta's avatar
Chris Allegretta committed
1783
1784
}

1785
/* Blank the first line of the bottom portion of the window. */
Chris Allegretta's avatar
Chris Allegretta committed
1786
1787
void blank_statusbar(void)
{
1788
    blank_row(bottomwin, 0, 0, COLS);
Chris Allegretta's avatar
Chris Allegretta committed
1789
1790
}

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

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

    statusblank--;

    /* When editing and 'constantshow' is active, skip the blanking. */
1812
    if (currmenu == MMAIN && ISSET(CONSTANT_SHOW))
1813
1814
1815
1816
1817
	return;

    if (statusblank == 0) {
	blank_statusbar();
	wnoutrefresh(bottomwin);
Chris Allegretta's avatar
Chris Allegretta committed
1818
    }
1819
1820
1821
1822

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

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

1844
#ifdef USING_OLD_NCURSES
1845
    seen_wide = FALSE;
1846
#endif
1847
    buf += start_index;
1848

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

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

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

1878
    while (*buf != '\0' && column < beyond) {
1879
	int charlength, charwidth = 1;
1880

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

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

1916
	charlength = length_of_char(buf, &charwidth);
1917

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

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

1932
	    column += charwidth;
1933
#ifdef USING_OLD_NCURSES
1934
	    if (charwidth > 1)
1935
		seen_wide = TRUE;
1936
#endif
1937
	    continue;
1938
1939
	}

1940
1941
1942
1943
	/* Represent an invalid sequence with the Replacement Character. */
	converted[index++] = '\xEF';
	converted[index++] = '\xBF';
	converted[index++] = '\xBD';
1944
	column++;
1945
1946
1947
1948
1949
	buf++;

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

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

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

1963
1964
    /* Null-terminate the converted string. */
    converted[index] = '\0';
1965

1966
    return converted;
1967
1968
}

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

1991
1992
1993
1994
    /* If the screen is too small, there is no titlebar. */
    if (topwin == NULL)
	return;

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

1997
    wattron(topwin, interface_color_pair[TITLE_BAR]);
1998

1999
    blank_titlebar();
2000
    as_an_at = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
2001

2002
2003
2004
    /* 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
2005

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

2021
2022
2023
2024
	if (openfile->modified)
	    state = _("Modified");
	else if (ISSET(VIEW_MODE))
	    state = _("View");
2025

2026
2027
	pluglen = strlenpt(_("Modified")) + 1;
    }
2028

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

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

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

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

2082
2083
2084
2085
2086
2087
    /* 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));

2088
    wattroff(topwin, interface_color_pair[TITLE_BAR]);
2089

2090
    wrefresh(topwin);
Chris Allegretta's avatar
Chris Allegretta committed
2091
2092
}

2093
2094
2095
2096
2097
2098
/* Display a normal message on the statusbar, quietly. */
void statusbar(const char *msg)
{
    statusline(HUSH, msg);
}

2099
2100
2101
2102
2103
2104
2105
2106
2107
/* 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);
}

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

    UNSET(WHITESPACE_DISPLAY);
2122
#endif
2123
2124
2125
2126
2127

    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(). */
2128
    if (isendwin()) {
2129
2130
2131
2132
2133
	vfprintf(stderr, msg, ap);
	va_end(ap);
	return;
    }

2134
2135
2136
2137
2138
    /* If there already was an alert message, ignore lesser ones. */
    if ((lastmessage == ALERT && importance != ALERT) ||
		(lastmessage == MILD && importance == HUSH))
	return;

2139
2140
2141
2142
2143
2144
    /* 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. */
2145
    if (lastmessage == ALERT && alerts < 4 && !ISSET(NO_PAUSES))
2146
2147
	napms(1200);

2148
    if (importance == ALERT) {
2149
	if (++alerts > 3 && !ISSET(NO_PAUSES))
2150
	    msg = _("Further warnings were suppressed");
2151
	beep();
2152
    }
2153
2154

    lastmessage = importance;
2155

2156
2157
2158
    /* Turn the cursor off while fiddling in the statusbar. */
    curs_set(0);

2159
2160
    blank_statusbar();

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

2168
2169
    start_col = (COLS - strlenpt(message)) / 2;
    bracketed = (start_col > 1);
2170

2171
    wmove(bottomwin, 0, (bracketed ? start_col - 2 : start_col));
2172
    wattron(bottomwin, interface_color_pair[STATUS_BAR]);
2173
2174
    if (bracketed)
	waddstr(bottomwin, "[ ");
2175
2176
    waddstr(bottomwin, message);
    free(message);
2177
2178
    if (bracketed)
	waddstr(bottomwin, " ]");
2179
    wattroff(bottomwin, interface_color_pair[STATUS_BAR]);
2180

2181
2182
2183
2184
    /* Defeat a VTE/Konsole bug, where the cursor can go off-limits. */
    if (ISSET(CONSTANT_SHOW) && ISSET(NO_HELP))
	wmove(bottomwin, 0, 0);

2185
    /* Push the message to the screen straightaway. */
2186
    wrefresh(bottomwin);
2187

2188
    suppress_cursorpos = TRUE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2189

2190
#ifndef NANO_TINY
2191
2192
    if (old_whitespace)
	SET(WHITESPACE_DISPLAY);
2193
#endif
2194
2195
2196

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

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

2211
2212
2213
    /* Set the global variable to the given menu. */
    currmenu = menu;

2214
    if (ISSET(NO_HELP) || LINES < 5)
Chris Allegretta's avatar
Chris Allegretta committed
2215
2216
	return;

2217
    /* Determine how many shortcuts there are to show. */
2218
    number = length_of_list(menu);
2219

2220
2221
    if (number > MAIN_VISIBLE)
	number = MAIN_VISIBLE;
2222

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

2226
    /* If there is no room, don't print anything. */
2227
    if (itemwidth == 0)
2228
2229
	return;

2230
    blank_bottombars();
2231

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

2238
	s = first_sc_for(menu, f->scfunc);
2239
	if (s == NULL)
2240
	    continue;
2241
2242

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

2244
	onekey(s->keystr, _(f->desc), itemwidth + (COLS % itemwidth));
2245
	i++;
Chris Allegretta's avatar
Chris Allegretta committed
2246
    }
2247

2248
    /* Defeat a VTE bug by homing the cursor and forcing a screen update. */
2249
    wmove(bottomwin, 0, 0);
2250
    wrefresh(bottomwin);
Chris Allegretta's avatar
Chris Allegretta committed
2251
2252
}

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

2262
    wattron(bottomwin, interface_color_pair[KEY_COMBO]);
2263
    waddnstr(bottomwin, keystroke, actual_x(keystroke, length));
2264
    wattroff(bottomwin, interface_color_pair[KEY_COMBO]);
2265

2266
    length -= strlenpt(keystroke) + 1;
2267

2268
    if (length > 0) {
2269
	waddch(bottomwin, ' ');
2270
	wattron(bottomwin, interface_color_pair[FUNCTION_TAG]);
2271
	waddnstr(bottomwin, desc, actual_x(desc, length));
2272
	wattroff(bottomwin, interface_color_pair[FUNCTION_TAG]);
Chris Allegretta's avatar
Chris Allegretta committed
2273
2274
2275
    }
}

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

2283
#ifndef NANO_TINY
2284
    if (ISSET(SOFTWRAP)) {
2285
	filestruct *line = openfile->edittop;
2286
	size_t leftedge;
2287

2288
	row -= chunk_for(openfile->firstcolumn, openfile->edittop);
2289

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

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

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

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

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

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

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

2352
2353
2354
    /* 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);
2355

2356
#ifdef USING_OLD_NCURSES
2357
2358
2359
    /* 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. */
2360
    if (seen_wide)
2361
	wredrawln(edit, row, 1);
2362
#endif
2363

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

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

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

2392
2393
2394
	    /* 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. */
2395

2396
2397
	    wattron(edit, varnish->attributes);

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

2414
		    /* If the match is of length zero, skip it. */
2415
		    if (match.rm_so == match.rm_eo) {
2416
			index = move_mbright(fileptr->data,
2417
						index + match.rm_eo);
2418
2419
2420
			continue;
		    }

2421
		    /* Translate the match to the beginning of the line. */
2422
2423
2424
		    match.rm_so += index;
		    match.rm_eo += index;
		    index = match.rm_eo;
2425

2426
2427
		    /* If the matching part is not visible, skip it. */
		    if (match.rm_eo <= from_x || match.rm_so >= till_x)
2428
2429
			continue;

2430
2431
2432
		    start_col = (match.rm_so <= from_x) ?
					0 : strnlenpt(fileptr->data,
					match.rm_so) - from_col;
2433

2434
		    thetext = converted + actual_x(converted, start_col);
2435

2436
		    paintlen = actual_x(thetext, strnlenpt(fileptr->data,
2437
					match.rm_eo) - from_col - start_col);
2438

2439
		    mvwaddnstr(edit, row, margin + start_col,
2440
						thetext, paintlen);
Chris Allegretta's avatar
Chris Allegretta committed
2441
		}
2442
2443
2444
2445
		goto tail_of_loop;
	    }

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

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

2450
2451
2452
2453
	    /* 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 ||
2454
2455
			start_line->multidata[varnish->id] == CENDAFTER ||
			start_line->multidata[varnish->id] == CWOULDBE)
2456
2457
2458
		    goto seek_an_end;
		if (start_line->multidata[varnish->id] == CNONE ||
			start_line->multidata[varnish->id] == CBEGINBEFORE ||
2459
			start_line->multidata[varnish->id] == CSTARTENDHERE)
2460
2461
2462
		    goto step_two;
	    }

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

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

2477
2478
2479
2480
2481
2482
2483
	    /* 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 &&
2484
2485
			(start_line->multidata[varnish->id] == CBEGINBEFORE ||
			start_line->multidata[varnish->id] == CSTARTENDHERE))
2486
		goto step_two;
2487

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

2512
  seek_an_end:
2513
2514
	    /* We've already checked that there is no end between the start
	     * and the current line.  But is there an end after the start
2515
2516
2517
2518
2519
	     * 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;

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

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

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

2541
  step_two:
2542
2543
2544
	    /* 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;
2545

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

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

2558
		thetext = converted + actual_x(converted, start_col);
2559

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

2574
			mvwaddnstr(edit, row, margin + start_col,
2575
						thetext, paintlen);
2576

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

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

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

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

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

2614
#ifndef NANO_TINY
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
    /* 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. */
2626
	int start_col;
2627
2628
2629
2630
2631
	    /* 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". */
2632

2633
	mark_order(&top, &top_x, &bot, &bot_x, NULL);
2634

2635
2636
2637
2638
	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
2639

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

2645
2646
2647
	    if (start_col < 0)
		start_col = 0;

2648
	    thetext = converted + actual_x(converted, start_col);
2649

2650
2651
2652
2653
2654
2655
	    /* 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);
	    }
2656

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

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

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

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

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

2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
    /* 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, '$');

2715
    return 1;
Chris Allegretta's avatar
Chris Allegretta committed
2716
2717
}

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

    if (fileptr == openfile->edittop)
	from_col = openfile->firstcolumn;
    else
2740
	row -= chunk_for(openfile->firstcolumn, openfile->edittop);
2741
2742
2743

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

2748
2749
2750
2751
    /* 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);
2752
	return 0;
2753
    }
2754
2755
2756

    starting_row = row;

2757
    while (row < editwinrows) {
2758
	bool end_of_line = FALSE;
2759
2760
2761

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

2762
2763
2764
	blank_row(edit, row, 0, COLS);

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

2769
2770
2771
2772
	if (end_of_line)
	    break;

	/* If the line is softwrapped before its last column, add a ">" just
2773
2774
2775
	 * 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)
2776
2777
2778
	    mvwaddch(edit, row - 1, to_col - from_col, '>');

	from_col = to_col;
2779
2780
2781
2782
2783
2784
    }

    return (row - starting_row);
}
#endif

2785
2786
2787
2788
/* 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)
2789
{
2790
#ifndef NANO_TINY
2791
2792
2793
    if (openfile->mark_set)
	return TRUE;
    else
2794
#endif
2795
	return (get_page_start(old_column) != get_page_start(new_column));
2796
2797
}

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

	    *leftedge = 0;

	    if (chunk >= i)
		return go_forward_chunks(chunk - i, line, leftedge);
2816
2817
2818
2819

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

2820
	    i -= chunk;
2821
	    *line = (*line)->prev;
2822
	    *leftedge = HIGHEST_POSITIVE;
2823
2824
	}

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

2847
	/* Advance through the requested number of chunks. */
2848
	for (i = nrows; i > 0; i--) {
2849
	    bool end_of_line = FALSE;
2850

2851
2852
2853
2854
	    current_leftedge = get_softwrap_breakpoint((*line)->data,
					current_leftedge, &end_of_line);

	    if (!end_of_line)
2855
2856
2857
2858
2859
2860
		continue;

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

	    *line = (*line)->next;
2861
	    current_leftedge = 0;
2862
2863
2864
2865
	}

	/* Only change leftedge when we actually could move. */
	if (i < nrows)
2866
	    *leftedge = current_leftedge;
2867
2868
2869
2870
2871
2872
2873
2874
    } else
#endif
	for (i = nrows; i > 0 && (*line)->next != NULL; i--)
	    *line = (*line)->next;

    return i;
}

2875
2876
2877
2878
2879
2880
2881
2882
/* 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;
2883
	size_t leftedge = leftedge_for(xplustabs(), openfile->current);
2884
2885
2886
2887
2888
2889
2890
2891
2892
	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);
}

2893
/* Scroll the edit window in the given direction and the given number of rows,
2894
 * and draw new lines on the blank lines left after the scrolling. */
2895
void edit_scroll(scroll_dir direction, int nrows)
2896
{
2897
    filestruct *line;
2898
2899
    size_t leftedge;

2900
2901
    /* Part 1: nrows is the number of rows we're going to scroll the text of
     * the edit window. */
2902

2903
2904
    /* 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. */
2905
    if (direction == UPWARD)
2906
	nrows -= go_back_chunks(nrows, &openfile->edittop, &openfile->firstcolumn);
2907
    else
2908
	nrows -= go_forward_chunks(nrows, &openfile->edittop, &openfile->firstcolumn);
2909

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

2926
    /* Scroll the text of the edit window a number of rows up or down. */
2927
    scrollok(edit, TRUE);
2928
    wscrl(edit, (direction == UPWARD) ? -nrows : nrows);
2929
2930
    scrollok(edit, FALSE);

2931
2932
    /* Part 2: nrows is now the number of rows in the scrolled region of the
     * edit window that we need to draw. */
2933

2934
2935
2936
2937
    /* 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++;
2938

2939
    /* If we scrolled backward, start on the first line of the blank region. */
2940
    line = openfile->edittop;
2941
    leftedge = openfile->firstcolumn;
2942

2943
    /* If we scrolled forward, move down to the start of the blank region. */
2944
2945
    if (direction == DOWNWARD)
	go_forward_chunks(editwinrows - nrows, &line, &leftedge);
2946

2947
#ifndef NANO_TINY
2948
2949
2950
    if (ISSET(SOFTWRAP)) {
	/* Compensate for the earlier chunks of a softwrapped line. */
	nrows += chunk_for(leftedge, line);
2951

2952
2953
2954
2955
	/* Don't compensate for the chunks that are offscreen. */
	if (line == openfile->edittop)
	    nrows -= chunk_for(openfile->firstcolumn, line);
    }
2956
#endif
2957
2958
2959

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

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

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

2995
2996
    /* Use a full screen row for text. */
    goal_column = column + editwincols;
2997
2998

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

3006
3007
3008
	prev_column = column;
	char_len = parse_mbchar(text, NULL, &column);
	text += char_len;
3009
	index += char_len;
3010
3011
    }

3012
    /* If we didn't overshoot the target, we've found a breaking point. */
3013
    if (column <= goal_column) {
3014
3015
	/* We've reached EOL if we didn't even reach the target. */
	*end_of_line = (column < goal_column);
3016
3017
3018
	return column;
    }

3019
3020
3021
3022
3023
3024
3025
3026
    /* 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;
    }

3027
3028
3029
    /* 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;
3030
3031
3032
3033
3034
}

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

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

3055
3056
/* Return the row of the softwrapped chunk of the given line that column is on,
 * relative to the first row (zero-based). */
3057
size_t chunk_for(size_t column, filestruct *line)
3058
{
3059
    return get_chunk_and_edge(column, line, NULL);
3060
3061
3062
3063
}

/* Return the leftmost column of the softwrapped chunk of the given line that
 * column is on. */
3064
size_t leftedge_for(size_t column, filestruct *line)
3065
{
3066
3067
    size_t leftedge;

3068
    get_chunk_and_edge(column, line, &leftedge);
3069
3070

    return leftedge;
3071
3072
3073
3074
}

/* Return the row of the last softwrapped chunk of the given line, relative to
 * the first row (zero-based). */
3075
size_t number_of_chunks_in(filestruct *line)
3076
{
3077
    return chunk_for((size_t)-1, line);
3078
3079
}

3080
/* Ensure that firstcolumn is at the starting column of the softwrapped chunk
3081
3082
3083
3084
 * 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)
{
3085
3086
    openfile->firstcolumn = leftedge_for(openfile->firstcolumn,
						openfile->edittop);
3087
3088
3089

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

	if (column > end_col)
	    column = end_col;
    }
3114
#endif
3115

3116
3117
3118
    return leftedge + column;
}

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

/* 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;
3142
	size_t leftedge = openfile->firstcolumn;
3143
3144

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

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

    openfile->placewewant = xplustabs();

3172
    /* If the current line is offscreen, scroll until it's onscreen. */
3173
    if (current_is_offscreen()) {
3174
	adjust_viewport((focusing || !ISSET(SMOOTH_SCROLL)) ? CENTERING : FLOWING);
3175
	refresh_needed = TRUE;
3176
	return;
3177
    }
3178

3179
#ifndef NANO_TINY
3180
3181
    /* If the mark is on, update all lines between old_current and current. */
    if (openfile->mark_set) {
3182
	filestruct *line = old_current;
3183

3184
3185
	while (line != openfile->current) {
	    update_line(line, 0);
3186

3187
3188
	    line = (line->lineno > openfile->current->lineno) ?
			line->prev : line->next;
3189
	}
3190
3191
3192
3193
3194
3195
    } 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);
3196

3197
3198
    /* 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. */
3199
    if (line_needs_update(was_pww, openfile->placewewant) ||
3200
			(old_current != openfile->current &&
3201
			get_page_start(openfile->placewewant) > 0))
3202
	update_line(openfile->current, openfile->current_x);
3203
3204
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3205
3206
/* 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
3207
3208
void edit_refresh(void)
{
3209
3210
    filestruct *line;
    int row = 0;
3211

3212
3213
3214
3215
3216
3217
#ifndef DISABLE_COLOR
    /* When needed, initialize the colors for the current syntax. */
    if (!have_palette)
	color_init();
#endif

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

3227
#ifdef DEBUG
3228
    fprintf(stderr, "edit-refresh: now edittop = %ld\n", (long)openfile->edittop->lineno);
3229
#endif
3230

3231
3232
3233
3234
    line = openfile->edittop;

    while (row < editwinrows && line != NULL) {
	if (line == openfile->current)
3235
	    row += update_line(line, openfile->current_x);
3236
	else
3237
	    row += update_line(line, 0);
3238
	line = line->next;
3239
3240
    }

3241
    while (row < editwinrows)
3242
	blank_row(edit, row++, 0, COLS);
3243

3244
    place_the_cursor(TRUE);
3245
    wnoutrefresh(edit);
3246
3247

    refresh_needed = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
3248
3249
}

3250
3251
3252
3253
3254
/* 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. */
3255
void adjust_viewport(update_type manner)
Chris Allegretta's avatar
Chris Allegretta committed
3256
{
3257
    int goal = 0;
3258

3259
    if (manner == STATIONARY)
3260
	goal = openfile->current_y;
3261
3262
3263
3264
    else if (manner == CENTERING)
	goal = editwinrows / 2;
    else if (!current_is_above_screen())
	goal = editwinrows - 1;
3265

3266
    openfile->edittop = openfile->current;
3267
#ifndef NANO_TINY
3268
    if (ISSET(SOFTWRAP))
3269
	openfile->firstcolumn = leftedge_for(xplustabs(), openfile->current);
3270
#endif
3271
3272

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

3275
#ifdef DEBUG
3276
    fprintf(stderr, "adjust_viewport(): setting edittop to lineno %ld\n", (long)openfile->edittop->lineno);
3277
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3278
3279
}

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

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

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

3322
    bottombars(MMAIN);
3323
3324
}

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

3335
3336
3337
3338
3339
    /* If the showing needs to be suppressed, don't suppress it next time. */
    if (suppress_cursorpos && !force) {
	suppress_cursorpos = FALSE;
	return;
    }
3340

3341
3342
3343
    /* Hide the cursor while we are calculating. */
    curs_set(0);

3344
    /* Determine the size of the file up to the cursor. */
3345
    saved_byte = openfile->current->data[openfile->current_x];
3346
    openfile->current->data[openfile->current_x] = '\0';
3347

3348
    sum = get_totsize(openfile->fileage, openfile->current);
3349

3350
    openfile->current->data[openfile->current_x] = saved_byte;
3351
3352
3353

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

3356
    /* Display the current cursor position on the statusbar. */
3357
    linepct = 100 * openfile->current->lineno / openfile->filebot->lineno;
3358
    colpct = 100 * cur_xpt / cur_lenpt;
3359
    charpct = (openfile->totsize == 0) ? 0 : 100 * sum / openfile->totsize;
3360

3361
    statusline(HUSH,
3362
	_("line %ld/%ld (%d%%), col %lu/%lu (%d%%), char %lu/%lu (%d%%)"),
3363
	(long)openfile->current->lineno,
3364
	(long)openfile->filebot->lineno, linepct,
3365
	(unsigned long)cur_xpt, (unsigned long)cur_lenpt, colpct,
3366
	(unsigned long)sum, (unsigned long)openfile->totsize, charpct);
3367
3368
3369

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

3372
/* Unconditionally display the current cursor position. */
3373
void do_cursorpos_void(void)
3374
{
3375
    do_cursorpos(TRUE);
3376
3377
}

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

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

3390
3391
3392
/* 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
3393
{
3394
3395
    char *word;
    size_t word_span, room;
Chris Allegretta's avatar
Chris Allegretta committed
3396

3397
3398
    place_the_cursor(FALSE);

3399
3400
3401
3402
#ifndef NANO_TINY
    if (ISSET(SOFTWRAP)) {
	spotlight_softwrapped(active, from_col, to_col);
	return;
3403
    }
3404
#endif
3405

3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
    /* 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--;

3423
    if (active)
3424
	wattron(edit, hilite_attribute);
Chris Allegretta's avatar
Chris Allegretta committed
3425

3426
    waddnstr(edit, word, actual_x(word, room));
3427

3428
    if (word_span > room)
3429
	waddch(edit, '$');
Chris Allegretta's avatar
Chris Allegretta committed
3430

3431
    if (active)
3432
	wattroff(edit, hilite_attribute);
3433

3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
    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)
{
3445
    ssize_t row = openfile->current_y;
3446
    size_t leftedge = leftedge_for(from_col, openfile->current);
3447
    size_t break_col;
3448
    bool end_of_line = FALSE;
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
    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)
	    wattron(edit, hilite_attribute);

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

	if (active)
	    wattroff(edit, hilite_attribute);

	free(word);

	if (end_of_line)
	    break;

3483
	wmove(edit, ++row, 0);
3484
3485
3486
3487
3488

	leftedge = break_col;
	from_col = break_col;
    }

3489
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
3490
}
3491
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3492

3493
#ifndef DISABLE_EXTRA
3494
3495
#define CREDIT_LEN 54
#define XLCREDIT_LEN 9
3496

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

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

3573
3574
3575
3576
3577
3578
    if (!old_more_space || !old_no_help) {
	SET(MORE_SPACE);
	SET(NO_HELP);
	window_init();
    }

3579
3580
    curs_set(0);
    nodelay(edit, TRUE);
3581

3582
    blank_titlebar();
Chris Allegretta's avatar
Chris Allegretta committed
3583
    blank_edit();
3584
    blank_statusbar();
3585

3586
    wrefresh(topwin);
Chris Allegretta's avatar
Chris Allegretta committed
3587
    wrefresh(edit);
3588
    wrefresh(bottomwin);
3589
    napms(700);
3590

3591
    for (crpos = 0; crpos < CREDIT_LEN + editwinrows / 2; crpos++) {
3592
	if ((kbinput = wgetch(edit)) != ERR)
3593
	    break;
3594

3595
	if (crpos < CREDIT_LEN) {
3596
	    const char *what;
3597
	    size_t start_col;
3598

3599
3600
3601
	    if (credits[crpos] == NULL)
		what = _(xlcredits[xlpos++]);
	    else
3602
		what = credits[crpos];
3603

3604
	    start_col = COLS / 2 - strlenpt(what) / 2 - 1;
3605
	    mvwaddstr(edit, editwinrows - 1 - (editwinrows % 2),
3606
						start_col, what);
3607
	}
3608

3609
3610
3611
3612
	wrefresh(edit);

	if ((kbinput = wgetch(edit)) != ERR)
	    break;
3613
	napms(700);
3614

3615
	scrollok(edit, TRUE);
3616
	wscrl(edit, 1);
3617
	scrollok(edit, FALSE);
3618
	wrefresh(edit);
3619

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

3624
	scrollok(edit, TRUE);
3625
	wscrl(edit, 1);
3626
	scrollok(edit, FALSE);
3627
	wrefresh(edit);
3628
3629
    }

3630
3631
3632
    if (kbinput != ERR)
	ungetch(kbinput);

3633
    if (!old_more_space)
3634
	UNSET(MORE_SPACE);
3635
    if (!old_no_help)
3636
	UNSET(NO_HELP);
3637
    window_init();
3638

3639
    nodelay(edit, FALSE);
3640

3641
    total_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
3642
}
3643
#endif /* !DISABLE_EXTRA */