winio.c 111 KB
Newer Older
Chris Allegretta's avatar
Chris Allegretta committed
1
/**************************************************************************
2
 *   winio.c  --  This file is part of GNU nano.                          *
Chris Allegretta's avatar
Chris Allegretta committed
3
 *                                                                        *
4
 *   Copyright (C) 1999-2011, 2013-2017 Free Software Foundation, Inc.    *
5
 *   Copyright (C) 2014, 2015, 2016, 2017 Benno Schulenberg               *
6
 *                                                                        *
7
8
9
10
 *   GNU nano is free software: you can redistribute it and/or modify     *
 *   it under the terms of the GNU General Public License as published    *
 *   by the Free Software Foundation, either version 3 of the License,    *
 *   or (at your option) any later version.                               *
Chris Allegretta's avatar
Chris Allegretta committed
11
 *                                                                        *
12
13
14
15
 *   GNU nano is distributed in the hope that it will be useful,          *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty          *
 *   of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.              *
 *   See the GNU General Public License for more details.                 *
Chris Allegretta's avatar
Chris Allegretta committed
16
17
 *                                                                        *
 *   You should have received a copy of the GNU General Public License    *
18
 *   along with this program.  If not, see http://www.gnu.org/licenses/.  *
Chris Allegretta's avatar
Chris Allegretta committed
19
20
21
 *                                                                        *
 **************************************************************************/

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

25
#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
	case KEY_F0:
	    return ERR;
709
    }
710

711
712
713
    return retval;
}

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

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

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

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

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

1204
    return ERR;
1205
1206
}

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

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

    free(seq);

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

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

    return retval;
}

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

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

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

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

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

    return retval;
}

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

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

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

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

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

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

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

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

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

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

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

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

1459
1460
    return retval;
}
1461

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

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

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

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

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

1497
    return retval;
1498
1499
}

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

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

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

1521
1522
    *count = 1;

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

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

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

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

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

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

1560
1561
    free(kbinput);

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

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

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

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

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

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

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

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

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

		return 0;
	    }
1634

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return NULL;
}

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

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

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

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

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

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

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

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

    statusblank--;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1963
    return converted;
1964
1965
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    lastmessage = importance;
2152

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

2156
2157
    blank_statusbar();

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2227
    blank_bottombars();
2228

2229
#ifdef DEBUG
2230
    fprintf(stderr, "In bottombars, number of items == \"%d\"\n", (int) number);
2231
#endif
2232

2233
    for (f = allfuncs, i = 0; i < number && f != NULL; f = f->next) {
2234
#ifdef DEBUG
2235
	fprintf(stderr, "Checking menu items....");
2236
#endif
2237
	if ((f->menus & menu) == 0)
2238
	    continue;
2239

2240
#ifdef DEBUG
2241
	fprintf(stderr, "found one! f->menus = %x, desc = \"%s\"\n", f->menus, f->desc);
2242
#endif
2243
2244
	s = first_sc_for(menu, f->scfunc);
	if (s == NULL) {
2245
2246
2247
#ifdef DEBUG
	    fprintf(stderr, "Whoops, guess not, no shortcut key found for func!\n");
#endif
2248
2249
	    continue;
	}
2250
2251

	wmove(bottomwin, 1 + i % 2, (i / 2) * itemwidth);
2252
#ifdef DEBUG
2253
	fprintf(stderr, "Calling onekey with keystr \"%s\" and desc \"%s\"\n", s->keystr, f->desc);
2254
#endif
2255
	onekey(s->keystr, _(f->desc), itemwidth + (COLS % itemwidth));
2256
	i++;
Chris Allegretta's avatar
Chris Allegretta committed
2257
    }
2258

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

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

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

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

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

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

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

2299
	row -= get_chunk_row(openfile->edittop, openfile->firstcolumn);
2300

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

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

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

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

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

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

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

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

2367
#ifdef USING_OLD_NCURSES
2368
2369
2370
    /* Tell ncurses to really redraw the line without trying to optimize
     * for what it thinks is already there, because it gets it wrong in
     * the case of a wide character in column zero.  See bug #31743. */
2371
    if (seen_wide)
2372
	wredrawln(edit, row, 1);
2373
#endif
2374

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

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

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

2403
2404
2405
	    /* Two notes about regexec().  A return value of zero means
	     * that there is a match.  Also, rm_eo is the first
	     * non-matching character after the match. */
2406

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

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

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

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

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

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

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

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

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

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

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

2461
2462
2463
2464
	    /* First check the multidata of the preceding line -- it tells
	     * us about the situation so far, and thus what to do here. */
	    if (start_line != NULL && start_line->multidata != NULL) {
		if (start_line->multidata[varnish->id] == CWHOLELINE ||
2465
2466
			start_line->multidata[varnish->id] == CENDAFTER ||
			start_line->multidata[varnish->id] == CWOULDBE)
2467
2468
2469
		    goto seek_an_end;
		if (start_line->multidata[varnish->id] == CNONE ||
			start_line->multidata[varnish->id] == CBEGINBEFORE ||
2470
			start_line->multidata[varnish->id] == CSTARTENDHERE)
2471
2472
2473
		    goto step_two;
	    }

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

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

2488
2489
2490
2491
2492
2493
2494
	    /* If no start was found, skip to the next step. */
	    if (start_line == NULL)
		goto step_two;

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

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

2523
  seek_an_end:
2524
2525
	    /* We've already checked that there is no end between the start
	     * and the current line.  But is there an end after the start
2526
2527
2528
2529
2530
	     * at all?  We don't paint unterminated starts. */
	    while (end_line != NULL && regexec(varnish->end, end_line->data,
				 1, &endmatch, 0) == REG_NOMATCH)
		end_line = end_line->next;

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

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

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

2552
  step_two:
2553
2554
2555
	    /* Second step: look for starts on this line, but begin
	     * looking only after an end match, if there is one. */
	    index = (paintlen == 0) ? 0 : endmatch.rm_eo;
2556

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

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

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

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

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

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

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

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

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

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

2625
#ifndef NANO_TINY
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
    /* If the mark is on, and fileptr is at least partially selected, we
     * need to paint it. */
    if (openfile->mark_set &&
		(fileptr->lineno <= openfile->mark_begin->lineno ||
		fileptr->lineno <= openfile->current->lineno) &&
		(fileptr->lineno >= openfile->mark_begin->lineno ||
		fileptr->lineno >= openfile->current->lineno)) {
	const filestruct *top, *bot;
	    /* The lines where the marked region begins and ends. */
	size_t top_x, bot_x;
	    /* The x positions where the marked region begins and ends. */
2637
	int start_col;
2638
2639
2640
2641
2642
	    /* The column where painting starts.  Zero-based. */
	const char *thetext;
	    /* The place in converted from where painting starts. */
	int paintlen = -1;
	    /* The number of characters to paint.  Negative means "all". */
2643

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

2646
2647
2648
2649
	if (top->lineno < fileptr->lineno || top_x < from_x)
	    top_x = from_x;
	if (bot->lineno > fileptr->lineno || bot_x > till_x)
	    bot_x = till_x;
Chris Allegretta's avatar
Chris Allegretta committed
2650

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

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

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

2661
2662
2663
2664
2665
2666
	    /* If the end of the mark is onscreen, compute how many
	     * characters to paint.  Otherwise, just paint all. */
	    if (bot_x < till_x) {
		size_t end_col = strnlenpt(fileptr->data, bot_x) - from_col;
		paintlen = actual_x(thetext, end_col - start_col);
	    }
2667

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

2676
2677
2678
/* Redraw the line at fileptr.  The line will be displayed so that the
 * character with the given index is visible -- if necessary, the line
 * will be horizontally scrolled.  In softwrap mode, however, the entire
2679
2680
2681
 * line will be passed to update_softwrapped_line().  Likely values of
 * index are current_x or zero.  Return the number of additional rows
 * consumed (when softwrapping). */
2682
int update_line(filestruct *fileptr, size_t index)
Chris Allegretta's avatar
Chris Allegretta committed
2683
{
2684
    int row = 0;
2685
	/* The row in the edit window we will be updating. */
2686
    char *converted;
2687
	/* The data of the line with tabs and control characters expanded. */
2688
2689
    size_t from_col = 0;
	/* From which column a horizontally scrolled line is displayed. */
Chris Allegretta's avatar
Chris Allegretta committed
2690

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

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

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

2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
    /* First, blank out the row. */
    blank_row(edit, row, 0, COLS);

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

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

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

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

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

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

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

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

2759
2760
2761
2762
    /* If the first chunk is offscreen, don't even try to display it. */
    if (row < 0 || row >= editwinrows) {
	statusline(ALERT, "Badness: tried to display a chunk on row %i"
				" -- please report a bug", row);
2763
	return 0;
2764
    }
2765
2766
2767

    starting_row = row;

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

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

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

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

2780
2781
2782
2783
	if (end_of_line)
	    break;

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

	from_col = to_col;
2790
2791
2792
2793
2794
2795
    }

    return (row - starting_row);
}
#endif

2796
2797
2798
2799
/* Check whether the mark is on, or whether old_column and new_column are on
 * different "pages" (in softwrap mode, only the former applies), which means
 * that the relevant line needs to be redrawn. */
bool line_needs_update(const size_t old_column, const size_t new_column)
2800
{
2801
#ifndef NANO_TINY
2802
2803
2804
    if (openfile->mark_set)
	return TRUE;
    else
2805
#endif
2806
	return (get_page_start(old_column) != get_page_start(new_column));
2807
2808
}

2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
/* Try to move up nrows softwrapped chunks from the given line and the
 * given column (leftedge).  After moving, leftedge will be set to the
 * starting column of the current chunk.  Return the number of chunks we
 * couldn't move up, which will be zero if we completely succeeded. */
int go_back_chunks(int nrows, filestruct **line, size_t *leftedge)
{
    int i;

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

#ifndef NANO_TINY
    if (ISSET(SOFTWRAP)) {
2823
	size_t current_leftedge = *leftedge;
2824

2825
	/* Recede through the requested number of chunks. */
2826
	for (i = nrows; i > 0; i--) {
2827
2828
2829
	    if (current_leftedge > 0) {
		current_leftedge = get_chunk_leftedge(*line,
							current_leftedge - 1);
2830
2831
2832
2833
2834
2835
2836
		continue;
	    }

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

	    *line = (*line)->prev;
2837
	    current_leftedge = get_last_chunk_leftedge(*line);
2838
2839
2840
2841
	}

	/* Only change leftedge when we actually could move. */
	if (i < nrows)
2842
	    *leftedge = current_leftedge;
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
    } else
#endif
	for (i = nrows; i > 0 && (*line)->prev != NULL; i--)
	    *line = (*line)->prev;

    return i;
}

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

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

#ifndef NANO_TINY
    if (ISSET(SOFTWRAP)) {
2865
2866
	size_t current_leftedge = *leftedge;
	size_t last_leftedge = get_last_chunk_leftedge(*line);
2867

2868
	/* Advance through the requested number of chunks. */
2869
	for (i = nrows; i > 0; i--) {
2870
	    if (current_leftedge < last_leftedge) {
2871
		bool dummy;
2872
2873

		current_leftedge = get_softwrap_breakpoint((*line)->data,
2874
						current_leftedge, &dummy);
2875
2876
2877
2878
2879
2880
2881
		continue;
	    }

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

	    *line = (*line)->next;
2882
2883
	    current_leftedge = 0;
	    last_leftedge = get_last_chunk_leftedge(*line);
2884
2885
2886
2887
	}

	/* Only change leftedge when we actually could move. */
	if (i < nrows)
2888
	    *leftedge = current_leftedge;
2889
2890
2891
2892
2893
2894
2895
2896
    } else
#endif
	for (i = nrows; i > 0 && (*line)->next != NULL; i--)
	    *line = (*line)->next;

    return i;
}

2897
2898
2899
2900
2901
2902
2903
2904
/* 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;
2905
	size_t leftedge = get_chunk_leftedge(openfile->current, xplustabs());
2906
2907
2908
2909
2910
2911
2912
2913
2914
	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);
}

2915
/* Scroll the edit window in the given direction and the given number of rows,
2916
 * and draw new lines on the blank lines left after the scrolling. */
2917
void edit_scroll(scroll_dir direction, int nrows)
2918
{
2919
    filestruct *line;
2920
2921
    size_t leftedge;

2922
2923
    /* Part 1: nrows is the number of rows we're going to scroll the text of
     * the edit window. */
2924

2925
2926
    /* 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. */
2927
    if (direction == UPWARD)
2928
	nrows -= go_back_chunks(nrows, &openfile->edittop, &openfile->firstcolumn);
2929
    else
2930
	nrows -= go_forward_chunks(nrows, &openfile->edittop, &openfile->firstcolumn);
2931

2932
    /* Don't bother scrolling zero rows, nor more than the window can hold. */
2933
    if (nrows == 0) {
2934
#ifndef NANO_TINY
2935
	statusline(ALERT, "Underscrolling -- please report a bug");
2936
#endif
2937
	return;
2938
    }
2939
    if (nrows >= editwinrows) {
2940
#ifndef NANO_TINY
2941
2942
	if (editwinrows > 1)
	    statusline(ALERT, "Overscrolling -- please report a bug");
2943
#endif
2944
	refresh_needed = TRUE;
2945
	return;
2946
    }
2947

2948
    /* Scroll the text of the edit window a number of rows up or down. */
2949
    scrollok(edit, TRUE);
2950
    wscrl(edit, (direction == UPWARD) ? -nrows : nrows);
2951
2952
    scrollok(edit, FALSE);

2953
2954
    /* Part 2: nrows is now the number of rows in the scrolled region of the
     * edit window that we need to draw. */
2955

2956
2957
2958
2959
    /* 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++;
2960

2961
    /* If we scrolled backward, start on the first line of the blank region. */
2962
    line = openfile->edittop;
2963
    leftedge = openfile->firstcolumn;
2964

2965
    /* If we scrolled forward, move down to the start of the blank region. */
2966
2967
    if (direction == DOWNWARD)
	go_forward_chunks(editwinrows - nrows, &line, &leftedge);
2968

2969
#ifndef NANO_TINY
2970
    /* Compensate for the earlier chunks of a softwrapped line. */
2971
    nrows += get_chunk_row(line, leftedge);
2972
2973
2974

    /* Don't compensate for the chunks that are offscreen. */
    if (line == openfile->edittop)
2975
	nrows -= get_chunk_row(line, openfile->firstcolumn);
2976
#endif
2977
2978
2979

    /* Draw new content on the blank rows inside the scrolled region
     * (and on the bordering row too when it was deemed necessary). */
2980
2981
2982
    while (nrows > 0 && line != NULL) {
	nrows -= update_line(line, (line == openfile->current) ?
					openfile->current_x : 0);
2983
	line = line->next;
2984
2985
2986
    }
}

2987
#ifndef NANO_TINY
2988
2989
2990
2991
2992
2993
2994
/* 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)
{
2995
2996
    size_t index = 0;
	/* Current index in text. */
2997
2998
2999
3000
3001
3002
    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. */
3003
3004
3005
3006
3007
3008
    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. */
3009
3010
3011
3012
3013
3014
    int char_len = 0;
	/* Length of current character, in bytes. */

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

3015
3016
3017
3018
3019
3020
3021
3022
    /* Use a full screen row for text, or, if we're softwrapping at blanks, use
     * a full screen row less one column for text and reserve the last column
     * for blanks.  The latter case is to ensure that we have enough room for
     * blanks exactly on the last column of the screen. */
    if (ISSET(AT_BLANKS) && editwincols > 2)
	goal_column = column + (editwincols - 1);
    else
	goal_column = column + editwincols;
3023
3024

    while (*text != '\0' && column <= goal_column) {
3025
3026
3027
3028
3029
3030
	if (ISSET(AT_BLANKS) && editwincols > 2 && is_blank_mbchar(text)) {
	    found_blank = TRUE;
	    lastblank_index = index;
	    lastblank_column = column;
	}

3031
3032
3033
	prev_column = column;
	char_len = parse_mbchar(text, NULL, &column);
	text += char_len;
3034
	index += char_len;
3035
3036
3037
3038
3039
3040
3041
3042
3043
    }

    /* If the text displays within goal_column, we've reached the end of the
     * line, and we're done. */
    if (column <= goal_column) {
	*end_of_line = TRUE;
	return column;
    }

3044
3045
3046
3047
3048
3049
3050
3051
    /* 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;
    }

3052
3053
3054
3055
3056
    /* Otherwise, return the column of the last character before goal_column,
     * since we can't break the text anywhere else. */
    return (editwincols > 2) ? prev_column : column - 1;
}

3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
/* 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)) {
	size_t end_col;
	bool last_chunk;

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

    return leftedge + column;
}

3085
3086
3087
3088
3089
3090
/* 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. */
size_t get_chunk(filestruct *line, size_t column, size_t *leftedge)
{
    size_t current_chunk = 0, start_col = 0, end_col;
3091
    bool end_of_line = FALSE;
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107

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

3108
3109
3110
3111
/* Return the row of the softwrapped chunk of the given line that column is on,
 * relative to the first row (zero-based). */
size_t get_chunk_row(filestruct *line, size_t column)
{
3112
    return get_chunk(line, column, NULL);
3113
3114
3115
3116
3117
3118
}

/* Return the leftmost column of the softwrapped chunk of the given line that
 * column is on. */
size_t get_chunk_leftedge(filestruct *line, size_t column)
{
3119
3120
3121
3122
3123
    size_t leftedge;

    get_chunk(line, column, &leftedge);

    return leftedge;
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
}

/* Return the row of the last softwrapped chunk of the given line, relative to
 * the first row (zero-based). */
size_t get_last_chunk_row(filestruct *line)
{
    return get_chunk_row(line, (size_t)-1);
}

/* Return the leftmost column of the last softwrapped chunk of the given
 * line. */
size_t get_last_chunk_leftedge(filestruct *line)
{
    return get_chunk_leftedge(line, (size_t)-1);
}

3140
/* Ensure that firstcolumn is at the starting column of the softwrapped chunk
3141
3142
3143
3144
 * 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)
{
3145
3146
    openfile->firstcolumn = get_chunk_leftedge(openfile->edittop,
						openfile->firstcolumn);
3147
3148
3149

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

3153
3154
3155
3156
/* Return TRUE if current[current_x] is above the top of the screen, and FALSE
 * otherwise. */
bool current_is_above_screen(void)
{
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
#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);
3167
3168
3169
3170
3171
3172
3173
3174
3175
}

/* 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;
3176
	size_t leftedge = openfile->firstcolumn;
3177
3178

	/* If current[current_x] is more than a screen's worth of lines after
3179
	 * edittop at column firstcolumn, it's below the screen. */
3180
3181
3182
	return (go_forward_chunks(editwinrows - 1, &line, &leftedge) == 0 &&
			(line->lineno < openfile->current->lineno ||
			(line->lineno == openfile->current->lineno &&
3183
3184
			leftedge < get_chunk_leftedge(openfile->current,
					xplustabs()))));
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
    } 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());
}

3198
/* Update any lines between old_current and current that need to be
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3199
 * updated.  Use this if we've moved without changing any text. */
3200
void edit_redraw(filestruct *old_current)
3201
{
3202
3203
3204
3205
    size_t was_pww = openfile->placewewant;

    openfile->placewewant = xplustabs();

3206
    /* If the current line is offscreen, scroll until it's onscreen. */
3207
    if (current_is_offscreen()) {
3208
	adjust_viewport((focusing || !ISSET(SMOOTH_SCROLL)) ? CENTERING : FLOWING);
3209
	refresh_needed = TRUE;
3210
	return;
3211
    }
3212

3213
#ifndef NANO_TINY
3214
3215
    /* If the mark is on, update all lines between old_current and current. */
    if (openfile->mark_set) {
3216
	filestruct *line = old_current;
3217

3218
3219
	while (line != openfile->current) {
	    update_line(line, 0);
3220

3221
3222
	    line = (line->lineno > openfile->current->lineno) ?
			line->prev : line->next;
3223
	}
3224
3225
3226
3227
3228
3229
    } 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);
3230

3231
3232
    /* 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. */
3233
    if (line_needs_update(was_pww, openfile->placewewant) ||
3234
			(old_current != openfile->current &&
3235
			get_page_start(openfile->placewewant) > 0))
3236
	update_line(openfile->current, openfile->current_x);
3237
3238
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3239
3240
/* 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
3241
3242
void edit_refresh(void)
{
3243
3244
    filestruct *line;
    int row = 0;
3245

3246
3247
3248
3249
3250
3251
#ifndef DISABLE_COLOR
    /* When needed, initialize the colors for the current syntax. */
    if (!have_palette)
	color_init();
#endif

3252
    /* If the current line is out of view, get it back on screen. */
3253
    if (current_is_offscreen()) {
3254
#ifdef DEBUG
3255
3256
	fprintf(stderr, "edit-refresh: line = %ld, edittop = %ld and editwinrows = %d\n",
		(long)openfile->current->lineno, (long)openfile->edittop->lineno, editwinrows);
3257
#endif
3258
	adjust_viewport((focusing || !ISSET(SMOOTH_SCROLL)) ? CENTERING : STATIONARY);
3259
    }
Chris Allegretta's avatar
Chris Allegretta committed
3260

3261
#ifdef DEBUG
3262
    fprintf(stderr, "edit-refresh: now edittop = %ld\n", (long)openfile->edittop->lineno);
3263
#endif
3264

3265
3266
3267
3268
    line = openfile->edittop;

    while (row < editwinrows && line != NULL) {
	if (line == openfile->current)
3269
	    row += update_line(line, openfile->current_x);
3270
	else
3271
	    row += update_line(line, 0);
3272
	line = line->next;
3273
3274
    }

3275
    while (row < editwinrows)
3276
	blank_row(edit, row++, 0, COLS);
3277

3278
    place_the_cursor(TRUE);
3279
    wnoutrefresh(edit);
3280
3281

    refresh_needed = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
3282
3283
}

3284
3285
3286
3287
3288
/* 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. */
3289
void adjust_viewport(update_type manner)
Chris Allegretta's avatar
Chris Allegretta committed
3290
{
3291
    int goal = 0;
3292

3293
    if (manner == STATIONARY)
3294
	goal = openfile->current_y;
3295
3296
3297
3298
    else if (manner == CENTERING)
	goal = editwinrows / 2;
    else if (!current_is_above_screen())
	goal = editwinrows - 1;
3299

3300
    openfile->edittop = openfile->current;
3301
#ifndef NANO_TINY
3302
    if (ISSET(SOFTWRAP))
3303
3304
	openfile->firstcolumn = get_chunk_leftedge(openfile->current,
							xplustabs());
3305
#endif
3306
3307

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

3310
#ifdef DEBUG
3311
    fprintf(stderr, "adjust_viewport(): setting edittop to lineno %ld\n", (long)openfile->edittop->lineno);
3312
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3313
3314
}

3315
/* Unconditionally redraw the entire screen. */
3316
void total_redraw(void)
3317
{
3318
3319
3320
3321
3322
3323
#ifdef USE_SLANG
    /* Slang curses emulation brain damage, part 4: Slang doesn't define
     * curscr. */
    SLsmg_touch_screen();
    SLsmg_refresh();
#else
3324
    wrefresh(curscr);
3325
#endif
3326
3327
}

3328
3329
/* 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. */
3330
3331
void total_refresh(void)
{
3332
    total_redraw();
3333
3334
    if (currmenu != MBROWSER && currmenu != MWHEREISFILE && currmenu != MGOTODIR)
	titlebar(title);
3335
#ifdef ENABLE_HELP
3336
    if (inhelp)
3337
	wrap_the_help_text(TRUE);
3338
3339
    else
#endif
3340
    if (currmenu != MBROWSER && currmenu != MWHEREISFILE && currmenu != MGOTODIR)
3341
	edit_refresh();
3342
    bottombars(currmenu);
3343
3344
}

3345
3346
/* Display the main shortcut list on the last two rows of the bottom
 * portion of the window. */
3347
3348
void display_main_list(void)
{
3349
#ifndef DISABLE_COLOR
3350
3351
    if (openfile->syntax &&
		(openfile->syntax->formatter || openfile->syntax->linter))
3352
	set_lint_or_format_shortcuts();
3353
3354
3355
3356
    else
	set_spell_shortcuts();
#endif

3357
    bottombars(MMAIN);
3358
3359
}

3360
3361
3362
3363
/* 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
3364
{
3365
3366
    char saved_byte;
    size_t sum, cur_xpt = xplustabs() + 1;
3367
    size_t cur_lenpt = strlenpt(openfile->current->data) + 1;
3368
    int linepct, colpct, charpct;
Chris Allegretta's avatar
Chris Allegretta committed
3369

3370
3371
3372
3373
3374
    /* If the showing needs to be suppressed, don't suppress it next time. */
    if (suppress_cursorpos && !force) {
	suppress_cursorpos = FALSE;
	return;
    }
3375

3376
3377
3378
    /* Hide the cursor while we are calculating. */
    curs_set(0);

3379
    /* Determine the size of the file up to the cursor. */
3380
    saved_byte = openfile->current->data[openfile->current_x];
3381
    openfile->current->data[openfile->current_x] = '\0';
3382

3383
    sum = get_totsize(openfile->fileage, openfile->current);
3384

3385
    openfile->current->data[openfile->current_x] = saved_byte;
3386
3387
3388

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

3391
    /* Display the current cursor position on the statusbar. */
3392
    linepct = 100 * openfile->current->lineno / openfile->filebot->lineno;
3393
    colpct = 100 * cur_xpt / cur_lenpt;
3394
    charpct = (openfile->totsize == 0) ? 0 : 100 * sum / openfile->totsize;
3395

3396
    statusline(HUSH,
3397
	_("line %ld/%ld (%d%%), col %lu/%lu (%d%%), char %lu/%lu (%d%%)"),
3398
	(long)openfile->current->lineno,
3399
	(long)openfile->filebot->lineno, linepct,
3400
	(unsigned long)cur_xpt, (unsigned long)cur_lenpt, colpct,
3401
	(unsigned long)sum, (unsigned long)openfile->totsize, charpct);
3402
3403
3404

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

3407
/* Unconditionally display the current cursor position. */
3408
void do_cursorpos_void(void)
3409
{
3410
    do_cursorpos(TRUE);
3411
3412
}

3413
void disable_waiting(void)
3414
{
3415
    waiting_mode = FALSE;
3416
    nodelay(edit, TRUE);
3417
3418
}

3419
void enable_waiting(void)
3420
{
3421
    waiting_mode = TRUE;
3422
    nodelay(edit, FALSE);
3423
3424
}

3425
3426
3427
/* 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
3428
{
3429
3430
    char *word;
    size_t word_span, room;
Chris Allegretta's avatar
Chris Allegretta committed
3431

3432
3433
    place_the_cursor(FALSE);

3434
3435
3436
3437
#ifndef NANO_TINY
    if (ISSET(SOFTWRAP)) {
	spotlight_softwrapped(active, from_col, to_col);
	return;
3438
    }
3439
#endif
3440

3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
    /* 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--;

3458
    if (active)
3459
	wattron(edit, hilite_attribute);
Chris Allegretta's avatar
Chris Allegretta committed
3460

3461
    waddnstr(edit, word, actual_x(word, room));
3462

3463
    if (word_span > room)
3464
	waddch(edit, '$');
Chris Allegretta's avatar
Chris Allegretta committed
3465

3466
    if (active)
3467
	wattroff(edit, hilite_attribute);
3468

3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
    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)
{
3480
    ssize_t row = openfile->current_y;
3481
3482
    size_t leftedge = get_chunk_leftedge(openfile->current, from_col);
    size_t break_col;
3483
    bool end_of_line = FALSE;
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
3497
3498
3499
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
    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;

3518
	wmove(edit, ++row, 0);
3519
3520
3521
3522
3523

	leftedge = break_col;
	from_col = break_col;
    }

3524
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
3525
}
3526
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3527

3528
#ifndef DISABLE_EXTRA
3529
3530
#define CREDIT_LEN 54
#define XLCREDIT_LEN 9
3531

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3532
3533
/* Easter egg: Display credits.  Assume nodelay(edit) and scrollok(edit)
 * are FALSE. */
3534
3535
void do_credits(void)
{
3536
3537
    bool old_more_space = ISSET(MORE_SPACE);
    bool old_no_help = ISSET(NO_HELP);
3538
    int kbinput = ERR, crpos = 0, xlpos = 0;
3539
3540
3541
    const char *credits[CREDIT_LEN] = {
	NULL,				/* "The nano text editor" */
	NULL,				/* "version" */
Chris Allegretta's avatar
Chris Allegretta committed
3542
3543
	VERSION,
	"",
3544
	NULL,				/* "Brought to you by:" */
Chris Allegretta's avatar
Chris Allegretta committed
3545
3546
3547
3548
3549
	"Chris Allegretta",
	"Jordi Mallach",
	"Adam Rogoyski",
	"Rob Siemborski",
	"Rocco Corsi",
3550
	"David Lawrence Ramsey",
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3551
	"David Benbennick",
Benno Schulenberg's avatar
Benno Schulenberg committed
3552
	"Mark Majeres",
3553
	"Mike Frysinger",
3554
	"Benno Schulenberg",
Chris Allegretta's avatar
Chris Allegretta committed
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
	"Ken Tyler",
	"Sven Guckes",
	"Bill Soudan",
	"Christian Weisgerber",
	"Erik Andersen",
	"Big Gaute",
	"Joshua Jensen",
	"Ryan Krebs",
	"Albert Chin",
	"",
3565
	NULL,				/* "Special thanks to:" */
3566
	"Monique, Brielle & Joseph",
Chris Allegretta's avatar
Chris Allegretta committed
3567
3568
3569
3570
3571
3572
	"Plattsburgh State University",
	"Benet Laboratories",
	"Amy Allegretta",
	"Linda Young",
	"Jeremy Robichaud",
	"Richard Kolb II",
3573
	NULL,				/* "The Free Software Foundation" */
Chris Allegretta's avatar
Chris Allegretta committed
3574
	"Linus Torvalds",
3575
	NULL,				/* "the many translators and the TP" */
3576
	NULL,				/* "For ncurses:" */
3577
3578
3579
3580
	"Thomas Dickey",
	"Pavel Curtis",
	"Zeyd Ben-Halim",
	"Eric S. Raymond",
3581
3582
3583
3584
3585
3586
	NULL,				/* "and anyone else we forgot..." */
	NULL,				/* "Thank you for using nano!" */
	"",
	"",
	"",
	"",
3587
	"(C) 2017",
3588
	"Free Software Foundation, Inc.",
3589
3590
3591
3592
	"",
	"",
	"",
	"",
3593
	"https://nano-editor.org/"
3594
3595
    };

3596
    const char *xlcredits[XLCREDIT_LEN] = {
3597
3598
3599
3600
3601
	N_("The nano text editor"),
	N_("version"),
	N_("Brought to you by:"),
	N_("Special thanks to:"),
	N_("The Free Software Foundation"),
3602
	N_("the many translators and the TP"),
3603
3604
3605
	N_("For ncurses:"),
	N_("and anyone else we forgot..."),
	N_("Thank you for using nano!")
3606
    };
3607

3608
3609
3610
3611
3612
3613
    if (!old_more_space || !old_no_help) {
	SET(MORE_SPACE);
	SET(NO_HELP);
	window_init();
    }

3614
3615
    curs_set(0);
    nodelay(edit, TRUE);
3616

3617
    blank_titlebar();
Chris Allegretta's avatar
Chris Allegretta committed
3618
    blank_edit();
3619
    blank_statusbar();
3620

3621
    wrefresh(topwin);
Chris Allegretta's avatar
Chris Allegretta committed
3622
    wrefresh(edit);
3623
    wrefresh(bottomwin);
3624
    napms(700);
3625

3626
    for (crpos = 0; crpos < CREDIT_LEN + editwinrows / 2; crpos++) {
3627
	if ((kbinput = wgetch(edit)) != ERR)
3628
	    break;
3629

3630
	if (crpos < CREDIT_LEN) {
3631
	    const char *what;
3632
	    size_t start_col;
3633

3634
3635
3636
	    if (credits[crpos] == NULL)
		what = _(xlcredits[xlpos++]);
	    else
3637
		what = credits[crpos];
3638

3639
	    start_col = COLS / 2 - strlenpt(what) / 2 - 1;
3640
	    mvwaddstr(edit, editwinrows - 1 - (editwinrows % 2),
3641
						start_col, what);
3642
	}
3643

3644
3645
3646
3647
	wrefresh(edit);

	if ((kbinput = wgetch(edit)) != ERR)
	    break;
3648
	napms(700);
3649

3650
	scrollok(edit, TRUE);
3651
	wscrl(edit, 1);
3652
	scrollok(edit, FALSE);
3653
	wrefresh(edit);
3654

3655
	if ((kbinput = wgetch(edit)) != ERR)
3656
	    break;
3657
	napms(700);
3658

3659
	scrollok(edit, TRUE);
3660
	wscrl(edit, 1);
3661
	scrollok(edit, FALSE);
3662
	wrefresh(edit);
3663
3664
    }

3665
3666
3667
    if (kbinput != ERR)
	ungetch(kbinput);

3668
    if (!old_more_space)
3669
	UNSET(MORE_SPACE);
3670
    if (!old_no_help)
3671
	UNSET(NO_HELP);
3672
    window_init();
3673

3674
    nodelay(edit, FALSE);
3675

3676
    total_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
3677
}
3678
#endif /* !DISABLE_EXTRA */