winio.c 102 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, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007,  *
5
 *   2008, 2009, 2010, 2011, 2013, 2014 Free Software Foundation, Inc.    *
6
7
 *   Copyright (C) 2014, 2015, 2016 Benno Schulenberg                     *
 *                                                                        *
8
9
10
11
 *   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
12
 *                                                                        *
13
14
15
16
 *   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
17
18
 *                                                                        *
 *   You should have received a copy of the GNU General Public License    *
19
 *   along with this program.  If not, see http://www.gnu.org/licenses/.  *
Chris Allegretta's avatar
Chris Allegretta committed
20
21
22
 *                                                                        *
 **************************************************************************/

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

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

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

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

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

58
#ifndef NANO_TINY
59
60
61
62
63
64
65
66
67
68
69
70
static sig_atomic_t last_sigwinch_counter = 0;

/* Did we receive a SIGWINCH since we were last called? */
bool the_window_resized(void)
{
    if (sigwinch_counter == last_sigwinch_counter)
	return FALSE;

    last_sigwinch_counter = sigwinch_counter;
    regenerate_screen();
    return TRUE;
}
71
#endif
72

73
74
/* Control character compatibility:
 *
75
76
77
78
79
 * - 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.
80
81
 * - Ctrl-8 (Ctrl-?) is Delete under ASCII, ANSI, VT100, and VT220,
 *          but is Backspace under VT320.
82
 *
83
 * Note: VT220 and VT320 also generate Esc [ 3 ~ for Delete.  By
84
85
 * default, xterm assumes it's running on a VT320 and generates Ctrl-8
 * (Ctrl-?) for Backspace and Esc [ 3 ~ for Delete.  This causes
86
 * problems for VT100-derived terminals such as the FreeBSD console,
87
 * which expect Ctrl-H for Backspace and Ctrl-8 (Ctrl-?) for Delete, and
88
89
90
91
92
93
94
95
96
 * 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
97
 * console, the FreeBSD console, the Mach console, xterm, rxvt, Eterm,
98
99
 * and Terminal, and some for iTerm2.  Among these, there are several
 * conflicts and omissions, outlined as follows:
100
101
102
103
104
105
 *
 * - 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
106
 *   keypad key, because the latter has no value when NumLock is off.)
107
108
109
110
 * - 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.)
111
 * - F9 on FreeBSD console == PageDown on Mach console; the former is
112
113
114
 *   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.)
115
 * - F10 on FreeBSD console == PageUp on Mach console; the former is
116
 *   omitted.  (Same as above.)
117
 * - F13 on FreeBSD console == End on Mach console; the former is
118
 *   omitted.  (Same as above.)
119
120
121
122
123
124
 * - 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
125
 *   omitted.  (Same as above.) */
126

127
/* Read in a sequence of keystrokes from win and save them in the
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
128
129
 * keystroke buffer.  This should only be called when the keystroke
 * buffer is empty. */
130
void get_key_buffer(WINDOW *win)
131
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
132
    int input;
133
    size_t errcount = 0;
134
135
136
137
138

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

139
140
141
142
    /* Just before reading in the first character, display any pending
     * screen updates. */
    doupdate();

143
    /* Read in the first character using whatever mode we're in. */
144
145
    input = wgetch(win);

146
#ifndef NANO_TINY
147
148
    if (the_window_resized()) {
	ungetch(input);
149
	input = KEY_WINCH;
150
    }
151
152
#endif

153
154
155
156
157
158
159
160
161
162
163
    if (input == ERR && nodelay_mode)
	return;

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

164
#ifndef NANO_TINY
165
	if (the_window_resized()) {
166
167
	    input = KEY_WINCH;
	    break;
168
	}
169
170
#endif
	input = wgetch(win);
171
    }
172

173
174
    /* Increment the length of the keystroke buffer, and save the value
     * of the keystroke at the end of it. */
175
    key_buffer_len++;
176
177
    key_buffer = (int *)nmalloc(sizeof(int));
    key_buffer[0] = input;
178

179
180
181
182
183
184
185
#ifndef NANO_TINY
    /* If we got SIGWINCH, get out immediately since the win argument is
     * no longer valid. */
    if (input == KEY_WINCH)
	return;
#endif

186
187
188
189
    /* Read in the remaining characters using non-blocking input. */
    nodelay(win, TRUE);

    while (TRUE) {
190
	input = wgetch(win);
191

192
	/* If there aren't any more characters, stop reading. */
193
	if (input == ERR)
194
195
	    break;

196
197
	/* Otherwise, increment the length of the keystroke buffer, and
	 * save the value of the keystroke at the end of it. */
198
	key_buffer_len++;
199
200
201
	key_buffer = (int *)nrealloc(key_buffer, key_buffer_len *
		sizeof(int));
	key_buffer[key_buffer_len - 1] = input;
202
203
    }

204
205
206
    /* Restore waiting mode if it was on. */
    if (!nodelay_mode)
	nodelay(win, FALSE);
207
208

#ifdef DEBUG
209
210
    {
	size_t i;
211
	fprintf(stderr, "\nget_key_buffer(): the sequence of hex codes:");
212
213
214
215
	for (i = 0; i < key_buffer_len; i++)
	    fprintf(stderr, " %3x", key_buffer[i]);
	fprintf(stderr, "\n");
    }
216
#endif
217
}
218

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
219
/* Return the length of the keystroke buffer. */
220
size_t get_key_buffer_len(void)
221
222
223
224
{
    return key_buffer_len;
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
225
/* Add the keystrokes in input to the keystroke buffer. */
226
void unget_input(int *input, size_t input_len)
227
228
{
    /* If input is empty, get out. */
229
    if (input_len == 0)
230
231
	return;

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
232
233
    /* If adding input would put the keystroke buffer beyond maximum
     * capacity, only add enough of input to put it at maximum
234
     * capacity. */
235
236
    if (key_buffer_len + input_len < key_buffer_len)
	input_len = (size_t)-1 - key_buffer_len;
237

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
238
239
240
    /* 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. */
241
242
243
    key_buffer_len += input_len;
    key_buffer = (int *)nrealloc(key_buffer, key_buffer_len *
	sizeof(int));
244

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
245
246
    /* If the keystroke buffer wasn't empty before, move its beginning
     * forward far enough so that we can add input to its beginning. */
247
248
249
    if (key_buffer_len > input_len)
	memmove(key_buffer + input_len, key_buffer,
		(key_buffer_len - input_len) * sizeof(int));
250

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
251
    /* Copy input to the beginning of the keystroke buffer. */
252
    memcpy(key_buffer, input, input_len * sizeof(int));
253
254
}

255
256
257
/* 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)
258
{
259
    unget_input(&kbinput, 1);
260

261
    if (metakey) {
262
	kbinput = ESC_CODE;
263
	unget_input(&kbinput, 1);
264
265
266
    }
}

267
/* Try to read input_len codes from the keystroke buffer.  If the
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
268
 * keystroke buffer is empty and win isn't NULL, try to read in more
269
 * codes from win and add them to the keystroke buffer before doing
270
 * anything else.  If the keystroke buffer is (still) empty, return NULL. */
271
int *get_input(WINDOW *win, size_t input_len)
272
{
273
    int *input;
274

275
276
    if (key_buffer_len == 0 && win != NULL)
	get_key_buffer(win);
277

278
279
    if (key_buffer_len == 0)
	return NULL;
280

281
    /* Limit the request to the number of available codes in the buffer. */
282
283
284
    if (input_len > key_buffer_len)
	input_len = key_buffer_len;

285
    /* Copy input_len codes from the head of the keystroke buffer. */
286
287
    input = (int *)nmalloc(input_len * sizeof(int));
    memcpy(input, key_buffer, input_len * sizeof(int));
288
    key_buffer_len -= input_len;
289

290
    /* If the keystroke buffer is now empty, mark it as such. */
291
292
293
294
    if (key_buffer_len == 0) {
	free(key_buffer);
	key_buffer = NULL;
    } else {
295
	/* Trim from the buffer the codes that were copied. */
296
	memmove(key_buffer, key_buffer + input_len, key_buffer_len *
297
298
299
		sizeof(int));
	key_buffer = (int *)nrealloc(key_buffer, key_buffer_len *
		sizeof(int));
300
301
302
    }

    return input;
303
304
}

305
/* Read in a single keystroke, ignoring any that are invalid. */
306
int get_kbinput(WINDOW *win)
307
{
308
    int kbinput = ERR;
309

310
    /* Extract one keystroke from the input stream. */
311
312
    while (kbinput == ERR)
	kbinput = parse_kbinput(win);
313

314
315
316
317
318
#ifdef DEBUG
    fprintf(stderr, "after parsing:  kbinput = %d, meta_key = %s\n",
	kbinput, meta_key ? "TRUE" : "FALSE");
#endif

319
    /* If we read from the edit window, blank the statusbar if needed. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
320
    if (win == edit)
321
322
	check_statusblank();

323
324
325
    return kbinput;
}

326
327
/* Extract a single keystroke from the input stream.  Translate escape
 * sequences and extended keypad codes into their corresponding values.
328
 * Set meta_key to TRUE when appropriate.  Supported extended keypad values
329
330
331
 * 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. */
332
int parse_kbinput(WINDOW *win)
333
{
334
    static int escapes = 0, byte_digits = 0;
335
    static bool double_esc = FALSE;
336
    int *kbinput, keycode, retval = ERR;
337

338
    meta_key = FALSE;
339
    shift_held = FALSE;
340

341
    /* Read in a character. */
342
343
344
345
346
347
    kbinput = get_input(win, 1);

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

    while (kbinput == NULL)
348
	kbinput = get_input(win, 1);
349

350
351
352
    keycode = *kbinput;
    free(kbinput);

353
354
355
356
357
#ifdef DEBUG
    fprintf(stderr, "before parsing:  keycode = %d, escapes = %d, byte_digits = %d\n",
	keycode, escapes, byte_digits);
#endif

358
359
360
    if (keycode == ERR)
	return ERR;

361
    if (keycode == ESC_CODE) {
362
363
364
365
366
367
368
	/* 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;
369
370
    }

371
372
373
374
375
376
    switch (escapes) {
	case 0:
	    /* One non-escape: normal input mode. */
	    retval = keycode;
	    break;
	case 1:
377
378
379
	    if (keycode >= 0x80)
		retval = keycode;
	    else if ((keycode != 'O' && keycode != 'o' && keycode != '[') ||
380
			key_buffer_len == 0 || *key_buffer == ESC_CODE) {
381
382
383
384
385
386
387
388
389
		/* 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);
390
	    escapes = 0;
391
392
393
	    break;
	case 2:
	    if (double_esc) {
394
395
		/* An "ESC ESC [ X" sequence from Option+arrow, or
		 * an "ESC ESC [ x" sequence from Shift+Alt+arrow. */
396
397
398
399
400
401
402
403
		switch (keycode) {
		    case 'A':
			retval = KEY_HOME;
			break;
		    case 'B':
			retval = KEY_END;
			break;
		    case 'C':
404
			retval = CONTROL_RIGHT;
405
406
			break;
		    case 'D':
407
			retval = CONTROL_LEFT;
408
			break;
409
#ifndef NANO_TINY
410
411
412
413
414
415
416
417
418
419
420
421
		    case 'a':
			retval = shiftaltup;
			break;
		    case 'b':
			retval = shiftaltdown;
			break;
		    case 'c':
			retval = shiftaltright;
			break;
		    case 'd':
			retval = shiftaltleft;
			break;
422
#endif
423
424
425
		}
		double_esc = FALSE;
		escapes = 0;
426
	    } else if (key_buffer_len == 0) {
427
428
429
430
431
432
433
434
435
436
437
438
439
440
		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);

441
442
		    /* If the decimal byte value is complete, convert it and
		     * put the obtained byte(s) back into the input buffer. */
443
444
445
446
		    if (byte != ERR) {
			char *byte_mb;
			int byte_mb_len, *seq, i;

447
448
			/* Convert the decimal code to one or two bytes. */
			byte_mb = make_mbchar((long)byte, &byte_mb_len);
449

450
			seq = (int *)nmalloc(byte_mb_len * sizeof(int));
451
452
453
454

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

455
			/* Insert the byte(s) into the input buffer. */
456
457
458
459
			unget_input(seq, byte_mb_len);

			free(byte_mb);
			free(seq);
460
461
462

			byte_digits = 0;
			escapes = 0;
463
		    }
464
465
466
467
468
469
470
471
472
473
474
475
476
		} 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;
477
			retval = keycode;
478
		    }
479
		    escapes = 0;
480
481
		}
	    } else if (keycode == '[' && key_buffer_len > 0 &&
482
483
484
			(('A' <= *key_buffer && *key_buffer <= 'D') ||
			('a' <= *key_buffer && *key_buffer <= 'd'))) {
		/* An iTerm2/Eterm/rxvt sequence: ^[ ^[ [ X. */
485
486
487
488
489
		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);
490
491
		meta_key = TRUE;
		escapes = 0;
492
	    }
493
494
	    break;
	case 3:
495
	    if (key_buffer_len == 0)
496
497
498
499
500
501
502
503
504
505
		/* 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));
506
	    escapes = 0;
507
508
	    break;
    }
509

510
511
512
    if (retval == ERR)
	return ERR;

513
    if (retval == controlleft)
514
	return CONTROL_LEFT;
515
    else if (retval == controlright)
516
	return CONTROL_RIGHT;
517
    else if (retval == controlup)
518
	return CONTROL_UP;
519
    else if (retval == controldown)
520
	return CONTROL_DOWN;
521
#ifndef NANO_TINY
522
523
    else if (retval == shiftcontrolleft) {
	shift_held = TRUE;
524
	return sc_seq_or(do_prev_word_void, shiftcontrolleft);
525
526
    } else if (retval == shiftcontrolright) {
	shift_held = TRUE;
527
	return sc_seq_or(do_next_word_void, shiftcontrolright);
528
529
    } else if (retval == shiftcontrolup) {
	shift_held = TRUE;
530
	return sc_seq_or(do_prev_block, shiftcontrolup);
531
532
    } else if (retval == shiftcontroldown) {
	shift_held = TRUE;
533
	return sc_seq_or(do_next_block, shiftcontroldown);
534
535
    } else if (retval == shiftaltleft) {
	shift_held = TRUE;
536
	return sc_seq_or(do_home, shiftaltleft);
537
538
    } else if (retval == shiftaltright) {
	shift_held = TRUE;
539
	return sc_seq_or(do_end, shiftaltright);
540
541
    } else if (retval == shiftaltup) {
	shift_held = TRUE;
542
	return sc_seq_or(do_page_up, shiftaltup);
543
544
    } else if (retval == shiftaltdown) {
	shift_held = TRUE;
545
	return sc_seq_or(do_page_down, shiftaltdown);
546
    }
547
548
#endif

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

    if (console && ioctl(0, TIOCLINUX, &modifiers) >= 0) {
	/* Is Ctrl being held? */
	if (modifiers & 0x04) {
557
	    if (retval == KEY_UP)
558
		return sc_seq_or(do_prev_block, controlup);
559
	    else if (retval == KEY_DOWN)
560
		return sc_seq_or(do_next_block, controldown);
561
	    else if (retval == KEY_LEFT)
562
		return sc_seq_or(do_prev_word_void, controlleft);
563
	    else if (retval == KEY_RIGHT)
564
		return sc_seq_or(do_next_word_void, controlright);
565
	}
566

567
568
569
570
571
#ifndef NANO_TINY
	/* Is Shift being held? */
	if (modifiers & 0x01)
	    shift_held =TRUE;

572
573
574
	/* Are both Shift and Alt being held? */
	if ((modifiers & 0x09) == 0x09) {
	    if (retval == KEY_UP)
575
		return sc_seq_or(do_page_up, shiftaltup);
576
	    else if (retval == KEY_DOWN)
577
		return sc_seq_or(do_page_down, shiftaltdown);
578
	    else if (retval == KEY_LEFT)
579
		return sc_seq_or(do_home, shiftaltleft);
580
	    else if (retval == KEY_RIGHT)
581
		return sc_seq_or(do_end, shiftaltright);
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 sc_seq_or(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 sc_seq_or(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
683
	/* Slang doesn't support KEY_CANCEL. */
	case KEY_CANCEL:
	    return first_sc_for(currmenu, do_cancel)->keycode;
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 sc_seq_or(do_suspend_void, KEY_SUSPEND);
693
694
#endif
#ifdef PDCURSES
695
696
697
698
699
700
701
	case KEY_SHIFT_L:
	case KEY_SHIFT_R:
	case KEY_CONTROL_L:
	case KEY_CONTROL_R:
	case KEY_ALT_L:
	case KEY_ALT_R:
	    return ERR;
702
#endif
703
704
#ifdef KEY_RESIZE
	/* Slang and SunOS 5.7-5.9 don't support KEY_RESIZE. */
705
706
	case KEY_RESIZE:
	    return ERR;
707
#endif
708
    }
709

710
711
712
    return retval;
}

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

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

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

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

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

1166
    return ERR;
1167
1168
}

1169
1170
/* Return the equivalent arrow-key value for the first four letters
 * in the alphabet, common to many escape sequences. */
1171
int arrow_from_abcd(int kbinput)
1172
1173
1174
{
    switch (tolower(kbinput)) {
	case 'a':
1175
	    return KEY_UP;
1176
	case 'b':
1177
	    return KEY_DOWN;
1178
	case 'c':
1179
	    return KEY_RIGHT;
1180
	case 'd':
1181
	    return KEY_LEFT;
1182
1183
1184
1185
1186
	default:
	    return ERR;
    }
}

1187
/* Interpret the escape sequence in the keystroke buffer, the first
1188
1189
 * character of which is kbinput.  Assume that the keystroke buffer
 * isn't empty, and that the initial escape has already been read in. */
1190
int parse_escape_sequence(WINDOW *win, int kbinput)
1191
1192
1193
1194
1195
1196
1197
1198
{
    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);
1199
    seq_len = key_buffer_len;
1200
    seq = get_input(NULL, seq_len);
1201
    retval = convert_sequence(seq, seq_len);
1202
1203
1204

    free(seq);

1205
    /* If we got an unrecognized escape sequence, notify the user. */
1206
    if (retval == ERR) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1207
	if (win == edit) {
1208
1209
	    /* TRANSLATORS: This refers to a sequence of escape codes
	     * (from the keyboard) that nano does not know about. */
1210
	    statusline(ALERT, _("Unknown sequence"));
1211
	    suppress_cursorpos = FALSE;
1212
	    lastmessage = HUSH;
1213
1214
1215
1216
	    if (currmenu == MMAIN) {
		reset_cursor();
		curs_set(1);
	    }
1217
1218
1219
	}
    }

1220
#ifdef DEBUG
1221
1222
    fprintf(stderr, "parse_escape_sequence(): kbinput = %d, seq_len = %lu, retval = %d\n",
		kbinput, (unsigned long)seq_len, retval);
1223
1224
1225
1226
1227
#endif

    return retval;
}

1228
1229
/* Translate a byte sequence: turn a three-digit decimal number (from
 * 000 to 255) into its corresponding byte value. */
1230
int get_byte_kbinput(int kbinput)
1231
{
1232
    static int byte_digits = 0, byte = 0;
1233
    int retval = ERR;
1234

1235
1236
    /* Increment the byte digit counter. */
    byte_digits++;
1237

1238
    switch (byte_digits) {
1239
	case 1:
1240
1241
	    /* First digit: This must be from zero to two.  Put it in
	     * the 100's position of the byte sequence holder. */
1242
	    if ('0' <= kbinput && kbinput <= '2')
1243
		byte = (kbinput - '0') * 100;
1244
	    else
1245
1246
		/* This isn't the start of a byte sequence.  Return this
		 * character as the result. */
1247
1248
1249
		retval = kbinput;
	    break;
	case 2:
1250
1251
1252
1253
	    /* 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. */
1254
1255
1256
	    if (('0' <= kbinput && kbinput <= '5') || (byte < 200 &&
		'6' <= kbinput && kbinput <= '9'))
		byte += (kbinput - '0') * 10;
1257
	    else
1258
1259
		/* This isn't the second digit of a byte sequence.
		 * Return this character as the result. */
1260
1261
1262
		retval = kbinput;
	    break;
	case 3:
1263
	    /* Third digit: This must be from zero to five if the first
1264
1265
1266
	     * 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. */
1267
1268
	    if (('0' <= kbinput && kbinput <= '5') || (byte < 250 &&
		'6' <= kbinput && kbinput <= '9')) {
1269
		byte += kbinput - '0';
1270
		/* The byte sequence is complete. */
1271
		retval = byte;
1272
	    } else
1273
1274
		/* This isn't the third digit of a byte sequence.
		 * Return this character as the result. */
1275
1276
		retval = kbinput;
	    break;
1277
	default:
1278
1279
1280
	    /* If there are more than three digits, return this
	     * character as the result.  (Maybe we should produce an
	     * error instead?) */
1281
1282
1283
1284
1285
1286
1287
1288
	    retval = kbinput;
	    break;
    }

    /* If we have a result, reset the byte digit counter and the byte
     * sequence holder. */
    if (retval != ERR) {
	byte_digits = 0;
1289
	byte = 0;
1290
1291
1292
    }

#ifdef DEBUG
1293
    fprintf(stderr, "get_byte_kbinput(): kbinput = %d, byte_digits = %d, byte = %d, retval = %d\n", kbinput, byte_digits, byte, retval);
1294
1295
1296
1297
1298
#endif

    return retval;
}

1299
#ifdef ENABLE_UTF8
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1300
/* If the character in kbinput is a valid hexadecimal digit, multiply it
1301
 * by factor and add the result to uni, and return ERR to signify okay. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1302
1303
1304
1305
1306
1307
1308
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
1309
1310
	/* The character isn't hexadecimal; give it as the result. */
	return (long)kbinput;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1311

1312
    return ERR;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1313
1314
}

1315
/* Translate a Unicode sequence: turn a six-digit hexadecimal number
1316
 * (from 000000 to 10FFFF, case-insensitive) into its corresponding
1317
 * multibyte value. */
1318
long get_unicode_kbinput(WINDOW *win, int kbinput)
1319
{
1320
1321
1322
    static int uni_digits = 0;
    static long uni = 0;
    long retval = ERR;
1323

1324
    /* Increment the Unicode digit counter. */
1325
    uni_digits++;
1326

1327
    switch (uni_digits) {
1328
	case 1:
1329
1330
1331
1332
	    /* 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')
1333
		uni = (kbinput - '0') * 0x100000;
1334
1335
1336
1337
	    else
		retval = kbinput;
	    break;
	case 2:
1338
1339
1340
	    /* 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)
1341
		retval = add_unicode_digit(kbinput, 0x10000, &uni);
1342
1343
1344
1345
	    else
		retval = kbinput;
	    break;
	case 3:
1346
	    /* Later digits may be any hexadecimal value. */
1347
	    retval = add_unicode_digit(kbinput, 0x1000, &uni);
1348
	    break;
1349
	case 4:
1350
	    retval = add_unicode_digit(kbinput, 0x100, &uni);
1351
	    break;
1352
	case 5:
1353
	    retval = add_unicode_digit(kbinput, 0x10, &uni);
1354
	    break;
1355
	case 6:
1356
	    retval = add_unicode_digit(kbinput, 0x1, &uni);
1357
1358
	    /* If also the sixth digit was a valid hexadecimal value, then
	     * the Unicode sequence is complete, so return it. */
1359
	    if (retval == ERR)
1360
		retval = uni;
1361
1362
	    break;
    }
1363

1364
    /* Show feedback only when editing, not when at a prompt. */
1365
    if (retval == ERR && win == edit) {
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
	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);
    }
1376

1377
#ifdef DEBUG
1378
1379
    fprintf(stderr, "get_unicode_kbinput(): kbinput = %d, uni_digits = %d, uni = %ld, retval = %ld\n",
						kbinput, uni_digits, uni, retval);
1380
1381
#endif

1382
1383
1384
1385
    /* If we have an end result, reset the Unicode digit counter. */
    if (retval != ERR)
	uni_digits = 0;

1386
1387
    return retval;
}
1388
#endif /* ENABLE_UTF8 */
1389

1390
1391
1392
1393
1394
1395
/* Translate a control character sequence: turn an ASCII non-control
 * character into its corresponding control character. */
int get_control_kbinput(int kbinput)
{
    int retval;

1396
    /* Ctrl-Space (Ctrl-2, Ctrl-@, Ctrl-`) */
1397
    if (kbinput == ' ' || kbinput == '2')
1398
	retval = 0;
1399
1400
    /* Ctrl-/ (Ctrl-7, Ctrl-_) */
    else if (kbinput == '/')
1401
	retval = 31;
1402
    /* Ctrl-3 (Ctrl-[, Esc) to Ctrl-7 (Ctrl-/, Ctrl-_) */
1403
1404
1405
    else if ('3' <= kbinput && kbinput <= '7')
	retval = kbinput - 24;
    /* Ctrl-8 (Ctrl-?) */
1406
    else if (kbinput == '8' || kbinput == '?')
1407
	retval = DEL_CODE;
1408
1409
    /* Ctrl-@ (Ctrl-Space, Ctrl-2, Ctrl-`) to Ctrl-_ (Ctrl-/, Ctrl-7) */
    else if ('@' <= kbinput && kbinput <= '_')
1410
	retval = kbinput - '@';
1411
1412
    /* Ctrl-` (Ctrl-2, Ctrl-Space, Ctrl-@) to Ctrl-~ (Ctrl-6, Ctrl-^) */
    else if ('`' <= kbinput && kbinput <= '~')
1413
	retval = kbinput - '`';
1414
1415
1416
    else
	retval = kbinput;

1417
#ifdef DEBUG
1418
    fprintf(stderr, "get_control_kbinput(): kbinput = %d, retval = %d\n", kbinput, retval);
1419
1420
#endif

1421
1422
    return retval;
}
1423

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1424
1425
/* Put the output-formatted characters in output back into the keystroke
 * buffer, so that they can be parsed and displayed as output again. */
1426
void unparse_kbinput(char *output, size_t output_len)
1427
{
1428
1429
    int *input;
    size_t i;
1430

1431
1432
1433
1434
    if (output_len == 0)
	return;

    input = (int *)nmalloc(output_len * sizeof(int));
1435

1436
1437
    for (i = 0; i < output_len; i++)
	input[i] = (int)output[i];
1438

1439
    unget_input(input, output_len);
1440

1441
    free(input);
1442
1443
}

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

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

    /* Read in a stream of characters and interpret it if possible. */
    retval = parse_verbatim_kbinput(win, kbinput_len);
1460
1461

    /* Turn flow control characters back on if necessary and turn the
1462
     * keypad back on if necessary now that we're done. */
1463
1464
    if (ISSET(PRESERVE))
	enable_flow_control();
1465
1466
1467
1468
1469
1470
    /* 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);
    }
1471

1472
    return retval;
1473
1474
}

1475
1476
1477
1478
/* 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). */
1479
int *parse_verbatim_kbinput(WINDOW *win, size_t *count)
1480
{
1481
    int *kbinput;
1482

1483
    /* Read in the first code. */
1484
1485
    while ((kbinput = get_input(win, 1)) == NULL)
	;
1486

1487
#ifndef NANO_TINY
1488
1489
1490
    /* When the window was resized, abort and return nothing. */
    if (*kbinput == KEY_WINCH) {
	free(kbinput);
1491
	*count = 0;
1492
1493
	return NULL;
    }
1494
#endif
1495

1496
#ifdef ENABLE_UTF8
1497
    if (using_utf8()) {
1498
	/* Check whether the first code is a valid starter digit: 0 or 1. */
1499
	long uni = get_unicode_kbinput(win, *kbinput);
1500

1501
	/* If the first code isn't the digit 0 nor 1, put it back. */
1502
1503
	if (uni != ERR)
	    unget_input(kbinput, 1);
1504
1505
	/* Otherwise, continue reading in digits until we have a complete
	 * Unicode value, and put back the corresponding byte(s). */
1506
1507
1508
1509
1510
	else {
	    char *uni_mb;
	    int uni_mb_len, *seq, i;

	    while (uni == ERR) {
1511
		free(kbinput);
1512
1513
		while ((kbinput = get_input(win, 1)) == NULL)
		    ;
1514
		uni = get_unicode_kbinput(win, *kbinput);
1515
	    }
1516

1517
	    /* Convert the Unicode value to a multibyte sequence. */
1518
	    uni_mb = make_mbchar(uni, &uni_mb_len);
1519

1520
	    seq = (int *)nmalloc(uni_mb_len * sizeof(int));
1521

1522
1523
	    for (i = 0; i < uni_mb_len; i++)
		seq[i] = (unsigned char)uni_mb[i];
1524

1525
	    /* Insert the multibyte sequence into the input buffer. */
1526
	    unget_input(seq, uni_mb_len);
1527

1528
1529
	    free(seq);
	    free(uni_mb);
1530
	}
1531
    } else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1532
#endif /* ENABLE_UTF8 */
1533
	/* Put back the first code. */
1534
	unget_input(kbinput, 1);
1535

1536
1537
    free(kbinput);

1538
    *count = 1;
1539

1540
    /* If this is an iTerm/Eterm/rxvt double escape, take both Escapes. */
1541
1542
1543
1544
1545
    if (key_buffer_len > 3 && *key_buffer == ESC_CODE &&
		key_buffer[1] == ESC_CODE && key_buffer[2] == '[')
	*count = 2;

    return get_input(NULL, *count);
1546
1547
}

1548
#ifndef DISABLE_MOUSE
1549
/* Handle any mouse event that may have occurred.  We currently handle
1550
1551
1552
1553
1554
1555
1556
 * 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
1557
1558
1559
1560
1561
1562
 * 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. */
1563
int get_mouseinput(int *mouse_x, int *mouse_y, bool allow_shortcuts)
1564
1565
{
    MEVENT mevent;
1566
    bool in_bottomwin;
1567
    subnfunc *f;
1568
1569
1570
1571
1572
1573

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

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

1576
    /* Save the screen coordinates where the mouse event took place. */
1577
    *mouse_x = mevent.x - margin;
1578
    *mouse_y = mevent.y;
1579

1580
1581
    in_bottomwin = wenclose(bottomwin, *mouse_y, *mouse_x);

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1582
    /* Handle releases/clicks of the first mouse button. */
1583
    if (mevent.bstate & (BUTTON1_RELEASED | BUTTON1_CLICKED)) {
1584
1585
	/* If we're allowing shortcuts, the current shortcut list is
	 * being displayed on the last two lines of the screen, and the
1586
1587
1588
	 * 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. */
1589
	if (allow_shortcuts && !ISSET(NO_HELP) && in_bottomwin) {
1590
1591
1592
1593
	    int i;
		/* The width of all the shortcuts, except for the last
		 * two, in the shortcut list in bottomwin. */
	    int j;
1594
		/* The calculated index number of the clicked item. */
1595
1596
	    size_t number;
		/* The number of available shortcuts in the current menu. */
1597

1598
1599
1600
1601
1602
1603
1604
1605
1606
	    /* 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. */
1607
		*mouse_x = mevent.x - margin;
1608
1609
1610
1611
		*mouse_y = mevent.y;

		return 0;
	    }
1612

1613
	    /* Determine how many shortcuts are being shown. */
1614
	    number = length_of_list(currmenu);
1615

1616
1617
	    if (number > MAIN_VISIBLE)
		number = MAIN_VISIBLE;
1618

1619
1620
1621
	    /* 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. */
1622
	    if (number < 2)
1623
1624
		i = COLS / (MAIN_VISIBLE / 2);
	    else
1625
		i = COLS / ((number / 2) + (number % 2));
1626

1627
1628
	    /* Calculate the one-based index in the shortcut list. */
	    j = (*mouse_x / i) * 2 + *mouse_y;
1629

1630
	    /* Adjust the index if we hit the last two wider ones. */
1631
	    if ((j > number) && (*mouse_x % i < COLS % i))
1632
		j -= 2;
1633
1634
1635
#ifdef DEBUG
	    fprintf(stderr, "Calculated %i as index in shortcut list, currmenu = %x.\n", j, currmenu);
#endif
1636
1637
	    /* Ignore releases/clicks of the first mouse button beyond
	     * the last shortcut. */
1638
	    if (j > number)
1639
		return 2;
1640

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

1657
	    /* And put the corresponding key into the keyboard buffer. */
1658
	    if (f != NULL) {
1659
		const sc *s = first_sc_for(currmenu, f->scfunc);
1660
		unget_kbinput(s->keycode, s->meta);
1661
	    }
1662
	    return 1;
1663
	} else
1664
1665
	    /* Handle releases/clicks of the first mouse button that
	     * aren't on the current shortcut list elsewhere. */
1666
	    return 0;
1667
    }
1668
1669
#if NCURSES_MOUSE_VERSION >= 2
    /* Handle presses of the fourth mouse button (upward rolls of the
1670
1671
1672
     * 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
1673
	bool in_edit = wenclose(edit, *mouse_y, *mouse_x);
1674

1675
1676
1677
1678
	if (in_bottomwin)
	    /* Translate the mouse event coordinates so that they're
	     * relative to bottomwin. */
	    wmouse_trafo(bottomwin, mouse_y, mouse_x, FALSE);
1679

1680
	if (in_edit || (in_bottomwin && *mouse_y == 0)) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1681
1682
	    int i;

1683
1684
1685
1686
1687
	    /* 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) ?
1688
				KEY_PPAGE : KEY_NPAGE, FALSE);
1689
1690
1691
1692
1693
1694
1695

	    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;
1696
1697
    }
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1698
1699
1700

    /* Ignore all other mouse events. */
    return 2;
1701
}
1702
1703
#endif /* !DISABLE_MOUSE */

1704
1705
1706
1707
/* 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. */
1708
const sc *get_shortcut(int *kbinput)
1709
{
1710
    sc *s;
1711

1712
#ifdef DEBUG
1713
1714
    fprintf(stderr, "get_shortcut(): kbinput = %d, meta_key = %s -- ",
				*kbinput, meta_key ? "TRUE" : "FALSE");
1715
1716
#endif

1717
    for (s = sclist; s != NULL; s = s->next) {
1718
	if ((s->menus & currmenu) && *kbinput == s->keycode &&
1719
					meta_key == s->meta) {
1720
#ifdef DEBUG
1721
1722
	    fprintf (stderr, "matched seq '%s'  (menu is %x from %x)\n",
				s->keystr, currmenu, s->menus);
1723
#endif
1724
	    return s;
1725
1726
	}
    }
1727
#ifdef DEBUG
1728
    fprintf (stderr, "matched nothing\n");
1729
#endif
1730
1731
1732
1733

    return NULL;
}

1734
1735
1736
1737
1738
/* Move to (x, y) in win, and display a line of n spaces with the
 * current attributes. */
void blank_line(WINDOW *win, int y, int x, int n)
{
    wmove(win, y, x);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1739

1740
1741
1742
1743
    for (; n > 0; n--)
	waddch(win, ' ');
}

1744
/* Blank the first line of the top portion of the window. */
1745
void blank_titlebar(void)
Chris Allegretta's avatar
Chris Allegretta committed
1746
{
1747
    blank_line(topwin, 0, 0, COLS);
1748
1749
}

1750
/* Blank all the lines of the middle portion of the window, i.e. the
1751
 * edit window. */
Chris Allegretta's avatar
Chris Allegretta committed
1752
1753
void blank_edit(void)
{
1754
    int i;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1755

1756
    for (i = 0; i < editwinrows; i++)
1757
	blank_line(edit, i, 0, COLS);
Chris Allegretta's avatar
Chris Allegretta committed
1758
1759
}

1760
/* Blank the first line of the bottom portion of the window. */
Chris Allegretta's avatar
Chris Allegretta committed
1761
1762
void blank_statusbar(void)
{
1763
    blank_line(bottomwin, 0, 0, COLS);
Chris Allegretta's avatar
Chris Allegretta committed
1764
1765
}

1766
1767
/* If the NO_HELP flag isn't set, blank the last two lines of the bottom
 * portion of the window. */
1768
1769
void blank_bottombars(void)
{
1770
    if (!ISSET(NO_HELP) && LINES > 4) {
1771
1772
	blank_line(bottomwin, 1, 0, COLS);
	blank_line(bottomwin, 2, 0, COLS);
1773
1774
1775
    }
}

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

    statusblank--;

    /* When editing and 'constantshow' is active, skip the blanking. */
    if (currmenu == MMAIN && ISSET(CONST_UPDATE))
	return;

    if (statusblank == 0) {
	blank_statusbar();
	wnoutrefresh(bottomwin);
	reset_cursor();
	wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
1795
    }
1796
1797
1798
1799

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

1802
1803
/* Convert buf into a string that can be displayed on screen.  The
 * caller wants to display buf starting with column start_col, and
1804
1805
 * extending for at most span columns.  start_col is zero-based.  span
 * is one-based, so span == 0 means you get "" returned.  The returned
1806
1807
1808
 * string is dynamically allocated, and should be freed.  If dollars is
 * TRUE, the caller might put "$" at the beginning or end of the line if
 * it's too long. */
1809
1810
char *display_string(const char *buf, size_t start_col, size_t span,
	bool dollars)
1811
1812
{
    size_t start_index;
1813
	/* Index in buf of the first character shown. */
1814
    size_t column;
1815
	/* Screen column that start_index corresponds to. */
1816
1817
1818
1819
    char *converted;
	/* The string we return. */
    size_t index;
	/* Current position in converted. */
1820

1821
1822
    /* If dollars is TRUE, make room for the "$" at the end of the
     * line. */
1823
1824
    if (dollars && span > 0 && strlenpt(buf) > start_col + span)
	span--;
1825

1826
    if (span == 0)
1827
1828
1829
1830
	return mallocstrcpy(NULL, "");

    start_index = actual_x(buf, start_col);
    column = strnlenpt(buf, start_index);
1831

1832
    assert(column <= start_col);
1833

1834
1835
    /* Allocate enough space to hold the entire converted buffer. */
    converted = charalloc(strlen(buf) * (mb_cur_max() + tabsize) + 1);
1836

1837
    index = 0;
1838
#ifdef USING_OLD_NCURSES
1839
    seen_wide = FALSE;
1840
#endif
1841
    buf += start_index;
1842

1843
    if (*buf != '\0' && *buf != '\t' &&
1844
	(column < start_col || (dollars && column > 0))) {
1845
	/* We don't display the complete first character as it starts to
1846
	 * the left of the screen. */
1847
	if (is_cntrl_mbchar(buf)) {
1848
	    if (column < start_col) {
1849
		converted[index++] = control_mbrep(buf);
1850
		start_col++;
1851
		buf += parse_mbchar(buf, NULL, NULL);
1852
	    }
1853
	}
1854
#ifdef ENABLE_UTF8
1855
	else if (using_utf8() && mbwidth(buf) == 2) {
1856
1857
1858
1859
1860
	    if (column >= start_col) {
		converted[index++] = ' ';
		start_col++;
	    }

1861
	    converted[index++] = ' ';
1862
	    start_col++;
1863

1864
	    buf += parse_mbchar(buf, NULL, NULL);
1865
	}
1866
#endif
1867
1868
    }

1869
    while (*buf != '\0') {
1870
	int charlength, charwidth = 1;
1871

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

1892
1893
		while (i < whitespace_len[0])
		    converted[index++] = whitespace[i++];
1894
	    } else
1895
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1896
		converted[index++] = ' ';
1897
	    start_col++;
1898
	    /* Fill the tab up with the required number of spaces. */
1899
	    while (start_col % tabsize != 0) {
1900
		converted[index++] = ' ';
1901
1902
		start_col++;
	    }
1903
1904
1905
1906
	    buf++;
	    continue;
	}

1907
	charlength = length_of_char(buf, &charwidth);
1908

1909
	/* If buf contains a control character, represent it. */
1910
	if (is_cntrl_mbchar(buf)) {
1911
	    converted[index++] = '^';
1912
	    converted[index++] = control_mbrep(buf);
1913
	    start_col += 2;
1914
1915
1916
	    buf += charlength;
	    continue;
	}
1917

1918
1919
1920
1921
	/* If buf contains a valid non-control character, simply copy it. */
	if (charlength > 0) {
	    for (; charlength > 0; charlength--)
		converted[index++] = *(buf++);
1922

1923
	    start_col += charwidth;
1924
#ifdef USING_OLD_NCURSES
1925
	    if (charwidth > 1)
1926
		seen_wide = TRUE;
1927
#endif
1928
	    continue;
1929
1930
	}

1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
	/* Represent an invalid sequence with the Replacement Character. */
	converted[index++] = '\xEF';
	converted[index++] = '\xBF';
	converted[index++] = '\xBD';

	start_col += 1;
	buf++;

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1944
    /* Null-terminate converted. */
1945
    converted[index] = '\0';
1946

1947
1948
    /* Make sure converted takes up no more than span columns. */
    index = actual_x(converted, span);
1949
    null_at(&converted, index);
1950

1951
    return converted;
1952
1953
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1954
1955
1956
1957
1958
1959
/* 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
 * has been modified on the titlebar.  If path isn't NULL, we're in the
 * file browser, and path contains the directory to start the file
 * browser in, so display the current version of nano and the contents
 * of path on the titlebar. */
1960
void titlebar(const char *path)
Chris Allegretta's avatar
Chris Allegretta committed
1961
{
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
    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. */
    const char *prefix = "";
	/* What is shown before the path -- "File:", "DIR:", or "". */
    const char *state = "";
	/* The state of the current buffer -- "Modified", "View", or "". */
    char *fragment;
	/* The tail part of the pathname when dottified. */
1974

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

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

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

1983
    blank_titlebar();
Chris Allegretta's avatar
Chris Allegretta committed
1984

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

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

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

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

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

2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
    /* Only print the version message when there is room for it. */
    if (verlen + prefixlen + pathlen + pluglen + statelen <= COLS)
	mvwaddstr(topwin, 0, 2, BRANDING);
    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;
2035
2036
2037
	}
    }

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

2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
    /* 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. */
    if (pathlen + pluglen + statelen <= COLS)
	waddstr(topwin, path);
    else if (5 + statelen <= COLS) {
	waddstr(topwin, "...");
	fragment = display_string(path, 3 + pathlen - COLS + statelen,
					COLS - statelen, FALSE);
	waddstr(topwin, fragment);
	free(fragment);
2060
    }
2061

2062
2063
2064
2065
2066
2067
    /* 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));

2068
    wattroff(topwin, interface_color_pair[TITLE_BAR]);
2069

2070
    wnoutrefresh(topwin);
Chris Allegretta's avatar
Chris Allegretta committed
2071
    reset_cursor();
2072
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
2073
2074
}

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

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

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

    UNSET(WHITESPACE_DISPLAY);
2103
#endif
2104
2105
2106
2107
2108

    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(). */
2109
    if (isendwin()) {
2110
2111
2112
2113
2114
	vfprintf(stderr, msg, ap);
	va_end(ap);
	return;
    }

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

    /* Delay another alert message, to allow an earlier one to be noticed. */
    if (lastmessage == ALERT)
2122
2123
	napms(1200);

2124
    if (importance == ALERT)
2125
	beep();
2126
2127

    lastmessage = importance;
2128

2129
2130
2131
    /* Turn the cursor off while fiddling in the statusbar. */
    curs_set(0);

2132
2133
    blank_statusbar();

2134
2135
2136
    /* Construct the message out of all the arguments. */
    compound = charalloc(mb_cur_max() * (COLS + 1));
    vsnprintf(compound, mb_cur_max() * (COLS + 1), msg, ap);
2137
    va_end(ap);
2138
2139
    message = display_string(compound, 0, COLS, FALSE);
    free(compound);
2140

2141
    start_x = (COLS - strlenpt(message)) / 2;
2142
    bracketed = (start_x > 1);
2143

2144
    wmove(bottomwin, 0, (bracketed ? start_x - 2 : start_x));
2145
    wattron(bottomwin, interface_color_pair[STATUS_BAR]);
2146
2147
    if (bracketed)
	waddstr(bottomwin, "[ ");
2148
2149
    waddstr(bottomwin, message);
    free(message);
2150
2151
    if (bracketed)
	waddstr(bottomwin, " ]");
2152
    wattroff(bottomwin, interface_color_pair[STATUS_BAR]);
2153

2154
    /* Push the message to the screen straightaway. */
2155
    wnoutrefresh(bottomwin);
2156
    doupdate();
2157

2158
    suppress_cursorpos = TRUE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2159

2160
#ifndef NANO_TINY
2161
2162
2163
2164
2165
    if (old_whitespace)
	SET(WHITESPACE_DISPLAY);

    /* If doing quick blanking, blank the statusbar after just one keystroke.
     * Otherwise, blank it after twenty-six keystrokes, as Pico does. */
2166
2167
2168
    if (ISSET(QUICK_BLANK))
	statusblank = 1;
    else
2169
#endif
2170
	statusblank = 26;
2171
2172
}

2173
2174
/* Display the shortcut list corresponding to menu on the last two rows
 * of the bottom portion of the window. */
2175
void bottombars(int menu)
Chris Allegretta's avatar
Chris Allegretta committed
2176
{
2177
    size_t number, itemwidth, i;
2178
2179
    subnfunc *f;
    const sc *s;
2180

2181
2182
2183
    /* Set the global variable to the given menu. */
    currmenu = menu;

2184
    if (ISSET(NO_HELP) || LINES < 5)
Chris Allegretta's avatar
Chris Allegretta committed
2185
2186
	return;

2187
    /* Determine how many shortcuts there are to show. */
2188
    number = length_of_list(menu);
2189

2190
2191
    if (number > MAIN_VISIBLE)
	number = MAIN_VISIBLE;
2192

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

2196
    /* If there is no room, don't print anything. */
2197
    if (itemwidth == 0)
2198
2199
	return;

2200
    blank_bottombars();
2201

2202
#ifdef DEBUG
2203
    fprintf(stderr, "In bottombars, number of items == \"%d\"\n", (int) number);
2204
#endif
2205

2206
    for (f = allfuncs, i = 0; i < number && f != NULL; f = f->next) {
2207
#ifdef DEBUG
2208
	fprintf(stderr, "Checking menu items....");
2209
#endif
2210
	if ((f->menus & menu) == 0)
2211
	    continue;
2212

2213
#ifdef DEBUG
2214
	fprintf(stderr, "found one! f->menus = %x, desc = \"%s\"\n", f->menus, f->desc);
2215
#endif
2216
2217
	s = first_sc_for(menu, f->scfunc);
	if (s == NULL) {
2218
2219
2220
#ifdef DEBUG
	    fprintf(stderr, "Whoops, guess not, no shortcut key found for func!\n");
#endif
2221
2222
	    continue;
	}
2223
2224

	wmove(bottomwin, 1 + i % 2, (i / 2) * itemwidth);
2225
#ifdef DEBUG
2226
	fprintf(stderr, "Calling onekey with keystr \"%s\" and desc \"%s\"\n", s->keystr, f->desc);
2227
#endif
2228
	onekey(s->keystr, _(f->desc), itemwidth + (COLS % itemwidth));
2229
	i++;
Chris Allegretta's avatar
Chris Allegretta committed
2230
    }
2231

2232
2233
    /* Defeat a VTE bug by moving the cursor and forcing a screen update. */
    wmove(bottomwin, 0, 0);
2234
    wnoutrefresh(bottomwin);
2235
2236
    doupdate();

2237
    reset_cursor();
2238
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
2239
2240
}

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

2250
    wattron(bottomwin, interface_color_pair[KEY_COMBO]);
2251
    waddnstr(bottomwin, keystroke, actual_x(keystroke, length));
2252
    wattroff(bottomwin, interface_color_pair[KEY_COMBO]);
2253

2254
    length -= strlenpt(keystroke) + 1;
2255

2256
    if (length > 0) {
2257
	waddch(bottomwin, ' ');
2258
	wattron(bottomwin, interface_color_pair[FUNCTION_TAG]);
2259
	waddnstr(bottomwin, desc, actual_x(desc, length));
2260
	wattroff(bottomwin, interface_color_pair[FUNCTION_TAG]);
Chris Allegretta's avatar
Chris Allegretta committed
2261
2262
2263
    }
}

2264
2265
/* Redetermine current_y from the position of current relative to edittop,
 * and put the cursor in the edit window at (current_y, current_x). */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2266
2267
void reset_cursor(void)
{
2268
    size_t xpt = xplustabs();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2269

2270
#ifndef NANO_TINY
2271
    if (ISSET(SOFTWRAP)) {
2272
	filestruct *line = openfile->edittop;
2273
2274
	openfile->current_y = 0;

2275
	while (line != NULL && line != openfile->current) {
2276
	    openfile->current_y += strlenpt(line->data) / editwincols + 1;
2277
2278
	    line = line->next;
	}
2279
	openfile->current_y += xpt / editwincols;
2280

2281
	if (openfile->current_y < editwinrows)
2282
	    wmove(edit, openfile->current_y, xpt % editwincols + margin);
2283
2284
2285
    } else
#endif
    {
2286
	openfile->current_y = openfile->current->lineno -
2287
				openfile->edittop->lineno;
2288
2289

	if (openfile->current_y < editwinrows)
2290
	    wmove(edit, openfile->current_y, xpt - get_page_start(xpt) + margin);
2291
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2292
}
Chris Allegretta's avatar
Chris Allegretta committed
2293

2294
2295
2296
2297
2298
2299
2300
2301
/* edit_draw() takes care of the job of actually painting a line into
 * the edit window.  fileptr is the line to be painted, at row line of
 * the window.  converted is the actual string to be written to the
 * window, with tabs and control characters replaced by strings of
 * regular characters.  start is the column number of the first
 * character of this page.  That is, the first character of converted
 * corresponds to character number actual_x(fileptr->data, start) of the
 * line. */
2302
void edit_draw(filestruct *fileptr, const char *converted, int
2303
	line, size_t start)
Chris Allegretta's avatar
Chris Allegretta committed
2304
{
2305
#if !defined(NANO_TINY) || !defined(DISABLE_COLOR)
2306
2307
2308
    size_t startpos = actual_x(fileptr->data, start);
	/* The position in fileptr->data of the leftmost character
	 * that displays at least partially on the window. */
2309
    size_t endpos = actual_x(fileptr->data, start + editwincols - 1) + 1;
2310
2311
2312
2313
2314
	/* The position in fileptr->data of the first character that is
	 * completely off the window to the right.
	 *
	 * Note that endpos might be beyond the null terminator of the
	 * string. */
2315
2316
#endif

2317
    assert(openfile != NULL && fileptr != NULL && converted != NULL);
2318
2319
2320
    assert(strlenpt(converted) <= editwincols);

#ifdef ENABLE_LINENUMBERS
2321
2322
2323
    /* If line numbering is switched on, show a line number in front of
     * the text -- but only for the parts that are not softwrapped. */
    if (margin > 0) {
2324
	wattron(edit, interface_color_pair[LINE_NUMBER]);
2325
2326
2327
2328
	if (last_drawn_line != fileptr->lineno || last_line_y >= line)
	    mvwprintw(edit, line, 0, "%*i", margin - 1, fileptr->lineno);
	else
	    mvwprintw(edit, line, 0, "%*s", margin - 1, " ");
2329
	wattroff(edit, interface_color_pair[LINE_NUMBER]);
2330
2331
    }
#endif
2332

2333
2334
    /* First simply paint the line -- then we'll add colors or the
     * marking highlight on just the pieces that need it. */
2335
    mvwaddstr(edit, line, margin, converted);
2336

2337
#ifdef USING_OLD_NCURSES
2338
2339
2340
    /* 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. */
2341
2342
    if (seen_wide)
	wredrawln(edit, line, 1);
2343
#endif
2344

2345
#ifndef DISABLE_COLOR
2346
2347
2348
    /* If color syntaxes are available and turned on, we need to display
     * them. */
    if (openfile->colorstrings != NULL && !ISSET(NO_COLOR_SYNTAX)) {
2349
	const colortype *varnish = openfile->colorstrings;
2350

2351
2352
2353
2354
	/* If there are multiline regexes, make sure there is a cache. */
	if (openfile->syntax->nmultis > 0)
	    alloc_multidata_if_needed(fileptr);

2355
	for (; varnish != NULL; varnish = varnish->next) {
2356
2357
	    int x_start;
		/* Starting column for mvwaddnstr.  Zero-based. */
2358
	    int paintlen = 0;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2359
2360
		/* Number of chars to paint on this line.  There are
		 * COLS characters on a whole line. */
2361
	    size_t index;
2362
		/* Index in converted where we paint. */
2363
2364
2365
2366
	    regmatch_t startmatch;
		/* Match position for start_regex. */
	    regmatch_t endmatch;
		/* Match position for end_regex. */
2367

2368
	    wattron(edit, varnish->attributes);
2369
2370
2371
	    /* 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. */
2372

2373
2374
	    /* First case: varnish is a single-line expression. */
	    if (varnish->end == NULL) {
2375
2376
2377
		size_t k = 0;

		/* We increment k by rm_eo, to move past the end of the
2378
		 * last match.  Even though two matches may overlap, we
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2379
2380
		 * want to ignore them, so that we can highlight e.g. C
		 * strings correctly. */
2381
2382
2383
		while (k < endpos) {
		    /* Note the fifth parameter to regexec().  It says
		     * not to match the beginning-of-line character
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2384
2385
2386
		     * unless k is zero.  If regexec() returns
		     * REG_NOMATCH, there are no more matches in the
		     * line. */
2387
		    if (regexec(varnish->start, &fileptr->data[k], 1,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2388
2389
			&startmatch, (k == 0) ? 0 : REG_NOTBOL) ==
			REG_NOMATCH)
2390
			break;
2391
2392
		    /* Translate the match to the beginning of the
		     * line. */
2393
2394
		    startmatch.rm_so += k;
		    startmatch.rm_eo += k;
2395
2396
2397

		    /* Skip over a zero-length regex match. */
		    if (startmatch.rm_so == startmatch.rm_eo)
2398
			startmatch.rm_eo++;
2399
		    else if (startmatch.rm_so < endpos &&
2400
			startmatch.rm_eo > startpos) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2401
2402
			x_start = (startmatch.rm_so <= startpos) ? 0 :
				strnlenpt(fileptr->data,
2403
				startmatch.rm_so) - start;
2404

2405
2406
2407
			index = actual_x(converted, x_start);

			paintlen = actual_x(converted + index,
2408
2409
				strnlenpt(fileptr->data,
				startmatch.rm_eo) - start - x_start);
2410
2411
2412

			assert(0 <= x_start && 0 <= paintlen);

2413
			mvwaddnstr(edit, line, x_start + margin, converted +
2414
				index, paintlen);
2415
		    }
2416
		    k = startmatch.rm_eo;
Chris Allegretta's avatar
Chris Allegretta committed
2417
		}
2418
	    } else {	/* Second case: varnish is a multiline expression. */
2419
		const filestruct *start_line = fileptr->prev;
2420
		    /* The first line before fileptr that matches 'start'. */
2421
		size_t start_col;
2422
		    /* Where the match starts in that line. */
2423
		const filestruct *end_line;
2424
		    /* The line that matches 'end'. */
2425

2426
		/* First see if the multidata was maybe already calculated. */
2427
		if (fileptr->multidata[varnish->id] == CNONE)
2428
		    goto tail_of_loop;
2429
		else if (fileptr->multidata[varnish->id] == CWHOLELINE) {
2430
		    mvwaddnstr(edit, line, margin, converted, -1);
2431
		    goto tail_of_loop;
2432
2433
		} else if (fileptr->multidata[varnish->id] == CBEGINBEFORE) {
		    regexec(varnish->end, fileptr->data, 1, &endmatch, 0);
2434
2435
		    /* If the coloured part is scrolled off, skip it. */
		    if (endmatch.rm_eo <= startpos)
2436
			goto tail_of_loop;
2437
2438
		    paintlen = actual_x(converted, strnlenpt(fileptr->data,
			endmatch.rm_eo) - start);
2439
		    mvwaddnstr(edit, line, margin, converted, paintlen);
2440
		    goto tail_of_loop;
2441
		} if (fileptr->multidata[varnish->id] == -1)
2442
		    /* Assume this until proven otherwise below. */
2443
		    fileptr->multidata[varnish->id] = CNONE;
2444

2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
		/* There is no precalculated multidata, so find it out now.
		 * First check if the beginning of the line is colored by a
		 * start on an earlier line, and an end on this line or later.
		 *
		 * So: find the first line before fileptr matching the start.
		 * If every match on that line is followed by an end, then go
		 * to step two.  Otherwise, find a line after start_line that
		 * matches the end.  If that line is not before fileptr, then
		 * paint the beginning of this line. */

2455
		while (start_line != NULL && regexec(varnish->start,
2456
2457
2458
			start_line->data, 1, &startmatch, 0) == REG_NOMATCH) {
		    /* There is no start; but if there is an end on this line,
		     * there is no need to look for starts on earlier lines. */
2459
		    if (regexec(varnish->end, start_line->data, 0, NULL, 0) == 0)
2460
2461
2462
			goto step_two;
		    start_line = start_line->prev;
		}
2463

2464
2465
2466
2467
		/* If no start was found, skip to the next step. */
		if (start_line == NULL)
		    goto step_two;

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

2475
		/* Skip over a zero-length regex match. */
2476
		if (startmatch.rm_so == startmatch.rm_eo)
2477
2478
		    goto tail_of_loop;

2479
2480
2481
2482
2483
2484
2485
		/* Now start_line is the first line before fileptr containing
		 * a start match.  Is there a start on that line not followed
		 * by an end on that line? */
		start_col = 0;
		while (TRUE) {
		    start_col += startmatch.rm_so;
		    startmatch.rm_eo -= startmatch.rm_so;
2486
		    if (regexec(varnish->end, start_line->data +
2487
2488
2489
				start_col + startmatch.rm_eo, 0, NULL,
				(start_col + startmatch.rm_eo == 0) ?
				0 : REG_NOTBOL) == REG_NOMATCH)
2490
2491
2492
			/* No end found after this start. */
			break;
		    start_col++;
2493
2494
		    if (regexec(varnish->start, start_line->data + start_col,
				1, &startmatch, REG_NOTBOL) == REG_NOMATCH)
2495
2496
2497
2498
2499
2500
2501
2502
2503
			/* No later start on this line. */
			goto step_two;
		}
		/* Indeed, there is a start without an end on that line. */

		/* We've already checked that there is no end before fileptr
		 * and after the start.  But is there an end after the start
		 * at all?  We don't paint unterminated starts. */
		end_line = fileptr;
2504
		while (end_line != NULL && regexec(varnish->end,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2505
			end_line->data, 1, &endmatch, 0) == REG_NOMATCH)
2506
		    end_line = end_line->next;
2507

2508
2509
2510
2511
		/* If no end was found, or it is too early, next step. */
		if (end_line == NULL)
		    goto step_two;
		if (end_line == fileptr && endmatch.rm_eo <= startpos) {
2512
		    fileptr->multidata[varnish->id] = CBEGINBEFORE;
2513
2514
		    goto step_two;
		}
2515

2516
2517
2518
2519
2520
2521
2522
		/* Now paint the start of fileptr.  If the start of fileptr
		 * is on a different line from the end, paintlen is -1, which
		 * means that everything on the line gets painted.  Otherwise,
		 * paintlen is the expanded location of the end of the match
		 * minus the expanded location of the beginning of the page. */
		if (end_line != fileptr) {
		    paintlen = -1;
2523
		    fileptr->multidata[varnish->id] = CWHOLELINE;
2524
#ifdef DEBUG
2525
    fprintf(stderr, "  Marking for id %i  line %i as CWHOLELINE\n", varnish->id, line);
2526
#endif
2527
2528
2529
		} else {
		    paintlen = actual_x(converted, strnlenpt(fileptr->data,
						endmatch.rm_eo) - start);
2530
		    fileptr->multidata[varnish->id] = CBEGINBEFORE;
2531
#ifdef DEBUG
2532
    fprintf(stderr, "  Marking for id %i  line %i as CBEGINBEFORE\n", varnish->id, line);
2533
#endif
2534
		}
2535
		mvwaddnstr(edit, line, margin, converted, paintlen);
2536
2537
2538
2539
		/* If the whole line has been painted, don't bother looking
		 * for any more starts. */
		if (paintlen < 0)
		    goto tail_of_loop;
2540
  step_two:
2541
2542
2543
2544
2545
		/* Second step: look for starts on this line, but start
		 * looking only after an end match, if there is one. */
		start_col = (paintlen == 0) ? 0 : endmatch.rm_eo;

		while (start_col < endpos) {
2546
		    if (regexec(varnish->start, fileptr->data + start_col,
2547
2548
				1, &startmatch, (start_col == 0) ?
				0 : REG_NOTBOL) == REG_NOMATCH ||
2549
				start_col + startmatch.rm_so >= endpos)
2550
2551
			/* No more starts on this line. */
			break;
2552

2553
2554
2555
2556
2557
2558
2559
		    /* Translate the match to be relative to the
		     * beginning of the line. */
		    startmatch.rm_so += start_col;
		    startmatch.rm_eo += start_col;

		    x_start = (startmatch.rm_so <= startpos) ?
				0 : strnlenpt(fileptr->data,
2560
				startmatch.rm_so) - start;
2561

2562
		    index = actual_x(converted, x_start);
2563

2564
		    if (regexec(varnish->end, fileptr->data +
2565
				startmatch.rm_eo, 1, &endmatch,
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
				(startmatch.rm_eo == 0) ?
				0 : REG_NOTBOL) == 0) {
			/* 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;
			/* There is an end on this line.  But does
			 * it appear on this page, and is the match
			 * more than zero characters long? */
			if (endmatch.rm_eo > startpos &&
2576
				endmatch.rm_eo > startmatch.rm_so) {
2577
			    paintlen = actual_x(converted + index,
2578
					strnlenpt(fileptr->data,
2579
					endmatch.rm_eo) - start - x_start);
2580

2581
			    assert(0 <= x_start && x_start < editwincols);
2582

2583
			    mvwaddnstr(edit, line, x_start + margin,
2584
					converted + index, paintlen);
2585
			    if (paintlen > 0) {
2586
				fileptr->multidata[varnish->id] = CSTARTENDHERE;
2587
#ifdef DEBUG
2588
    fprintf(stderr, "  Marking for id %i  line %i as CSTARTENDHERE\n", varnish->id, line);
2589
#endif
2590
			    }
2591
2592
			}
			start_col = endmatch.rm_eo;
2593
2594
2595
			/* Skip over a zero-length match. */
			if (endmatch.rm_so == endmatch.rm_eo)
			    start_col += 1;
2596
2597
2598
2599
		    } else {
			/* There is no end on this line.  But we haven't yet
			 * looked for one on later lines. */
			end_line = fileptr->next;
2600

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

2606
2607
2608
			/* If there is no end, we're done on this line. */
			if (end_line == NULL)
			    break;
2609

2610
			assert(0 <= x_start && x_start < editwincols);
2611
2612

			/* Paint the rest of the line. */
2613
			mvwaddnstr(edit, line, x_start + margin, converted + index, -1);
2614
			fileptr->multidata[varnish->id] = CENDAFTER;
2615
#ifdef DEBUG
2616
    fprintf(stderr, "  Marking for id %i  line %i as CENDAFTER\n", varnish->id, line);
2617
#endif
2618
2619
2620
			/* We've painted to the end of the line, so don't
			 * bother checking for any more starts. */
			break;
2621
		    }
2622
2623
		}
	    }
2624
  tail_of_loop:
2625
	    wattroff(edit, varnish->attributes);
2626
	}
2627
    }
2628
#endif /* !DISABLE_COLOR */
2629

2630
#ifndef NANO_TINY
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
    /* 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. */
2642
	int x_start;
2643
	    /* The starting column for mvwaddnstr().  Zero-based. */
2644
	int paintlen;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2645
2646
	    /* Number of characters to paint on this line.  There are
	     * COLS characters on a whole line. */
2647
	size_t index;
2648
	    /* Index in converted where we paint. */
2649

2650
	mark_order(&top, &top_x, &bot, &bot_x, NULL);
2651
2652
2653
2654
2655

	if (top->lineno < fileptr->lineno || top_x < startpos)
	    top_x = startpos;
	if (bot->lineno > fileptr->lineno || bot_x > endpos)
	    bot_x = endpos;
Chris Allegretta's avatar
Chris Allegretta committed
2656

2657
	/* Only paint if the marked bit of fileptr is on this page. */
2658
2659
	if (top_x < endpos && bot_x > startpos) {
	    assert(startpos <= top_x);
2660
2661
2662
2663

	    /* x_start is the expanded location of the beginning of the
	     * mark minus the beginning of the page. */
	    x_start = strnlenpt(fileptr->data, top_x) - start;
2664

2665
2666
2667
2668
2669
	    /* If the end of the mark is off the page, paintlen is -1,
	     * meaning that everything on the line gets painted.
	     * Otherwise, paintlen is the expanded location of the end
	     * of the mark minus the expanded location of the beginning
	     * of the mark. */
2670
2671
2672
	    if (bot_x >= endpos)
		paintlen = -1;
	    else
2673
		paintlen = strnlenpt(fileptr->data, bot_x) - (x_start + start);
2674
2675
2676
2677
2678
2679
2680
2681

	    /* If x_start is before the beginning of the page, shift
	     * paintlen x_start characters to compensate, and put
	     * x_start at the beginning of the page. */
	    if (x_start < 0) {
		paintlen += x_start;
		x_start = 0;
	    }
2682
2683
2684

	    assert(x_start >= 0 && x_start <= strlen(converted));

2685
	    index = actual_x(converted, x_start);
2686

2687
2688
2689
	    if (paintlen > 0)
		paintlen = actual_x(converted + index, paintlen);

2690
	    wattron(edit, hilite_attribute);
2691
	    mvwaddnstr(edit, line, x_start + margin, converted + index, paintlen);
2692
	    wattroff(edit, hilite_attribute);
Chris Allegretta's avatar
Chris Allegretta committed
2693
	}
2694
    }
2695
#endif /* !NANO_TINY */
2696
2697
2698
2699
#ifdef ENABLE_LINENUMBERS
    last_drawn_line = fileptr->lineno;
    last_line_y = line;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2700
2701
}

2702
/* Just update one line in the edit buffer.  This is basically a wrapper
2703
 * for edit_draw().  The line will be displayed starting with
2704
 * fileptr->data[index].  Likely arguments are current_x or zero.
2705
 * Returns: Number of additional lines consumed (needed for SOFTWRAP). */
2706
int update_line(filestruct *fileptr, size_t index)
Chris Allegretta's avatar
Chris Allegretta committed
2707
{
2708
    int line = 0;
2709
	/* The line in the edit window that we want to update. */
2710
    int extralinesused = 0;
2711
2712
2713
2714
    char *converted;
	/* fileptr->data converted to have tabs and control characters
	 * expanded. */
    size_t page_start;
Chris Allegretta's avatar
Chris Allegretta committed
2715

2716
    assert(fileptr != NULL);
2717

2718
#ifndef NANO_TINY
2719
    if (ISSET(SOFTWRAP)) {
2720
2721
	filestruct *tmp;

2722
	for (tmp = openfile->edittop; tmp && tmp != fileptr; tmp = tmp->next)
2723
	    line += (strlenpt(tmp->data) / editwincols) + 1;
2724
    } else
2725
#endif
2726
2727
	line = fileptr->lineno - openfile->edittop->lineno;

2728
    if (line < 0 || line >= editwinrows)
Chris Allegretta's avatar
Chris Allegretta committed
2729
	return 1;
2730

2731
    /* First, blank out the line. */
2732
    blank_line(edit, line, 0, COLS);
2733

2734
2735
    /* Next, convert variables that index the line to their equivalent
     * positions in the expanded line. */
2736
#ifndef NANO_TINY
2737
2738
2739
    if (ISSET(SOFTWRAP))
	index = 0;
    else
2740
#endif
2741
	index = strnlenpt(fileptr->data, index);
2742
    page_start = get_page_start(index);
2743

2744
2745
    /* Expand the line, replacing tabs with spaces, and control
     * characters with their displayed forms. */
2746
#ifdef NANO_TINY
2747
    converted = display_string(fileptr->data, page_start, editwincols, TRUE);
2748
#else
2749
    converted = display_string(fileptr->data, page_start, editwincols, !ISSET(SOFTWRAP));
2750
#ifdef DEBUG
2751
    if (ISSET(SOFTWRAP) && strlen(converted) >= editwincols - 2)
2752
	fprintf(stderr, "update_line(): converted(1) line = %s\n", converted);
2753
#endif
2754
#endif /* !NANO_TINY */
2755

2756
    /* Paint the line. */
2757
    edit_draw(fileptr, converted, line, page_start);
2758
    free(converted);
Chris Allegretta's avatar
Chris Allegretta committed
2759

2760
#ifndef NANO_TINY
2761
    if (!ISSET(SOFTWRAP)) {
2762
#endif
2763
	if (page_start > 0)
2764
2765
	    mvwaddch(edit, line, margin, '$');
	if (strlenpt(fileptr->data) > page_start + editwincols)
2766
	    mvwaddch(edit, line, COLS - 1, '$');
2767
#ifndef NANO_TINY
2768
    } else {
2769
	size_t full_length = strlenpt(fileptr->data);
2770
	for (index += editwincols; index <= full_length && line < editwinrows - 1; index += editwincols) {
2771
2772
	    line++;
#ifdef DEBUG
2773
	    fprintf(stderr, "update_line(): softwrap code, moving to %d index %lu\n", line, (unsigned long)index);
2774
#endif
2775
	    blank_line(edit, line, 0, COLS);
2776
2777

	    /* Expand the line, replacing tabs with spaces, and control
2778
	     * characters with their displayed forms. */
2779
	    converted = display_string(fileptr->data, index, editwincols, !ISSET(SOFTWRAP));
2780
#ifdef DEBUG
2781
	    if (ISSET(SOFTWRAP) && strlen(converted) >= editwincols - 2)
2782
2783
		fprintf(stderr, "update_line(): converted(2) line = %s\n", converted);
#endif
2784
2785
2786

	    /* Paint the line. */
	    edit_draw(fileptr, converted, line, index);
2787
	    free(converted);
2788
2789
2790
	    extralinesused++;
	}
    }
2791
#endif /* !NANO_TINY */
2792
    return extralinesused;
Chris Allegretta's avatar
Chris Allegretta committed
2793
2794
}

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

2807
2808
/* When edittop changes, try and figure out how many lines we really
 * have to work with, accounting for softwrap mode. */
2809
2810
void compute_maxrows(void)
{
2811
2812
#ifndef NANO_TINY
    if (ISSET(SOFTWRAP)) {
2813
2814
	int screenrow;
	filestruct *line = openfile->edittop;
2815
2816

	maxrows = 0;
2817
2818
2819
2820

	for (screenrow = 0; screenrow < editwinrows && line != NULL; screenrow++) {
	    screenrow += strlenpt(line->data) / editwincols;
	    line = line->next;
2821
2822
	    maxrows++;
	}
2823

2824
2825
	if (screenrow < editwinrows)
	    maxrows += editwinrows - screenrow;
2826

2827
#ifdef DEBUG
2828
	fprintf(stderr, "recomputed: maxrows = %d\n", maxrows);
2829
#endif
2830
2831
2832
    } else
#endif /* !NANO_TINY */
	maxrows = editwinrows;
2833
2834
}

2835
2836
/* Scroll the edit window in the given direction and the given number
 * of lines, and draw new lines on the blank lines left after the
2837
2838
 * scrolling.  direction is the direction to scroll, either UPWARD or
 * DOWNWARD, and nlines is the number of lines to scroll.  We change
2839
2840
 * edittop, and assume that current and current_x are up to date.  We
 * also assume that scrollok(edit) is FALSE. */
2841
void edit_scroll(scroll_dir direction, ssize_t nlines)
2842
{
2843
    ssize_t i;
2844
    filestruct *foo;
2845

2846
    assert(nlines > 0);
2847

2848
2849
2850
    /* Part 1: nlines is the number of lines we're going to scroll the
     * text of the edit window. */

2851
    /* Move the top line of the edit window up or down (depending on the
2852
2853
     * value of direction) nlines lines, or as many lines as we can if
     * there are fewer than nlines lines available. */
2854
    for (i = nlines; i > 0; i--) {
2855
	if (direction == UPWARD) {
2856
	    if (openfile->edittop == openfile->fileage)
2857
		break;
2858
	    openfile->edittop = openfile->edittop->prev;
2859
	} else {
2860
	    if (openfile->edittop == openfile->filebot)
2861
		break;
2862
	    openfile->edittop = openfile->edittop->next;
2863
	}
2864
2865

#ifndef NANO_TINY
2866
	/* Don't over-scroll on long lines. */
2867
	if (ISSET(SOFTWRAP) && direction == UPWARD) {
2868
	    ssize_t len = strlenpt(openfile->edittop->data) / editwincols;
2869
	    i -= len;
2870
	    if (len > 0)
2871
		refresh_needed = TRUE;
2872
	}
2873
#endif
2874
2875
    }

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2876
    /* Limit nlines to the number of lines we could scroll. */
2877
    nlines -= i;
2878

2879
2880
2881
2882
    /* Don't bother scrolling zero lines, nor more than the window can hold. */
    if (nlines == 0)
	return;
    if (nlines >= editwinrows)
2883
	refresh_needed = TRUE;
2884

2885
    if (refresh_needed == TRUE)
2886
	return;
2887
2888
2889

    /* Scroll the text of the edit window up or down nlines lines,
     * depending on the value of direction. */
2890
    scrollok(edit, TRUE);
2891
    wscrl(edit, (direction == UPWARD) ? -nlines : nlines);
2892
2893
    scrollok(edit, FALSE);

2894
2895
2896
    /* Part 2: nlines is the number of lines in the scrolled region of
     * the edit window that we need to draw. */

2897
2898
2899
2900
2901
2902
    /* If the scrolled region contains only one line, and the line
     * before it is visible in the edit window, we need to draw it too.
     * If the scrolled region contains more than one line, and the lines
     * before and after the scrolled region are visible in the edit
     * window, we need to draw them too. */
    nlines += (nlines == 1) ? 1 : 2;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2903

2904
2905
    if (nlines > editwinrows)
	nlines = editwinrows;
2906
2907
2908

    /* If we scrolled up, we're on the line before the scrolled
     * region. */
2909
    foo = openfile->edittop;
2910

2911
2912
    /* If we scrolled down, move down to the line before the scrolled
     * region. */
2913
    if (direction == DOWNWARD) {
2914
	for (i = editwinrows - nlines; i > 0 && foo != NULL; i--)
2915
2916
2917
	    foo = foo->next;
    }

2918
2919
2920
2921
2922
2923
    /* Draw new lines on any blank lines before or inside the scrolled
     * region.  If we scrolled down and we're on the top line, or if we
     * scrolled up and we're on the bottom line, the line won't be
     * blank, so we don't need to draw it unless the mark is on or we're
     * not on the first page. */
    for (i = nlines; i > 0 && foo != NULL; i--) {
2924
2925
	if ((i == nlines && direction == DOWNWARD) || (i == 1 &&
		direction == UPWARD)) {
2926
	    if (need_horizontal_scroll(openfile->placewewant, 0))
2927
2928
2929
2930
		update_line(foo, (foo == openfile->current) ?
			openfile->current_x : 0);
	} else
	    update_line(foo, (foo == openfile->current) ?
2931
		openfile->current_x : 0);
2932
	foo = foo->next;
2933
    }
2934
    compute_maxrows();
2935
2936
2937
}

/* Update any lines between old_current and current that need to be
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2938
 * updated.  Use this if we've moved without changing any text. */
2939
void edit_redraw(filestruct *old_current)
2940
{
2941
2942
2943
2944
    size_t was_pww = openfile->placewewant;

    openfile->placewewant = xplustabs();

2945
2946
    /* If the current line is offscreen, scroll until it's onscreen. */
    if (openfile->current->lineno >= openfile->edittop->lineno + maxrows ||
2947
2948
#ifndef NANO_TINY
		(openfile->current->lineno == openfile->edittop->lineno + maxrows - 1 &&
2949
		ISSET(SOFTWRAP) && strlenpt(openfile->current->data) >= editwincols) ||
2950
#endif
2951
		openfile->current->lineno < openfile->edittop->lineno) {
2952
	adjust_viewport((focusing || !ISSET(SMOOTH_SCROLL)) ? CENTERING : FLOWING);
2953
	refresh_needed = TRUE;
2954
	return;
2955
    }
2956

2957
#ifndef NANO_TINY
2958
2959
2960
    /* If the mark is on, update all lines between old_current and current. */
    if (openfile->mark_set) {
	filestruct *foo = old_current;
2961

2962
	while (foo != openfile->current) {
2963
	    update_line(foo, 0);
2964

2965
2966
2967
	    foo = (foo->lineno > openfile->current->lineno) ?
			foo->prev : foo->next;
	}
2968
2969
2970
2971
2972
2973
    } 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);
2974
2975
2976

    /* Update current if we've changed page, or if it differs from
     * old_current and needs to be horizontally scrolled. */
2977
    if (need_horizontal_scroll(was_pww, openfile->placewewant) ||
2978
			(old_current != openfile->current &&
2979
			get_page_start(openfile->placewewant) > 0))
2980
	update_line(openfile->current, openfile->current_x);
2981
2982
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2983
2984
/* 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
2985
2986
void edit_refresh(void)
{
2987
    filestruct *foo;
2988
    int nlines;
2989

2990
    /* Figure out what maxrows should really be. */
2991
    compute_maxrows();
2992

2993
    /* If the current line is out of view, get it back on screen. */
2994
    if (openfile->current->lineno < openfile->edittop->lineno ||
2995
		openfile->current->lineno >= openfile->edittop->lineno + maxrows) {
2996
#ifdef DEBUG
2997
	fprintf(stderr, "edit-refresh: line = %ld, edittop = %ld and maxrows = %d\n",
2998
		(long)openfile->current->lineno, (long)openfile->edittop->lineno, maxrows);
2999
#endif
3000
	adjust_viewport((focusing || !ISSET(SMOOTH_SCROLL)) ? CENTERING : STATIONARY);
3001
    }
Chris Allegretta's avatar
Chris Allegretta committed
3002

3003
3004
    foo = openfile->edittop;

3005
#ifdef DEBUG
3006
    fprintf(stderr, "edit-refresh: now edittop = %ld\n", (long)openfile->edittop->lineno);
3007
#endif
3008

3009
    for (nlines = 0; nlines < editwinrows && foo != NULL; nlines++) {
3010
	nlines += update_line(foo, (foo == openfile->current) ?
3011
					openfile->current_x : 0);
3012
3013
3014
	foo = foo->next;
    }

3015
    for (; nlines < editwinrows; nlines++)
3016
3017
3018
	blank_line(edit, nlines, 0, COLS);

    reset_cursor();
3019
    wnoutrefresh(edit);
3020
3021

    refresh_needed = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
3022
3023
}

3024
3025
3026
3027
3028
/* Move edittop so that current is on the screen.  manner says how it
 * should be moved: CENTERING means that current should end up in the
 * middle of the screen, STATIONARY means that it should stay at the
 * same vertical position, and FLOWING means that it should scroll no
 * more than needed to bring current into view. */
3029
void adjust_viewport(update_type manner)
Chris Allegretta's avatar
Chris Allegretta committed
3030
{
3031
    int goal = 0;
3032

3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
    /* If manner is CENTERING, move edittop half the number of window
     * lines back from current.  If manner is STATIONARY, move edittop
     * back current_y lines if current_y is in range of the screen,
     * 0 lines if current_y is below zero, or (editwinrows - 1) lines
     * if current_y is too big.  This puts current at the same place
     * on the screen as before, or at the top or bottom if current_y is
     * beyond either.  If manner is FLOWING, move edittop back 0 lines
     * or (editwinrows - 1) lines, depending or where current has moved.
     * This puts the cursor on the first or the last line. */
    if (manner == CENTERING)
3043
	goal = editwinrows / 2;
3044
    else if (manner == FLOWING) {
3045
	if (openfile->current->lineno >= openfile->edittop->lineno) {
3046
	    goal = editwinrows - 1;
3047
3048
#ifndef NANO_TINY
	    if (ISSET(SOFTWRAP))
3049
		goal -= strlenpt(openfile->current->data) / editwincols;
3050
3051
#endif
	}
3052
    } else {
3053
	goal = openfile->current_y;
3054

3055
	/* Limit goal to (editwinrows - 1) lines maximum. */
3056
3057
	if (goal > editwinrows - 1)
	    goal = editwinrows - 1;
Chris Allegretta's avatar
Chris Allegretta committed
3058
    }
3059

3060
3061
3062
3063
    openfile->edittop = openfile->current;

    while (goal > 0 && openfile->edittop->prev != NULL) {
	openfile->edittop = openfile->edittop->prev;
3064
	goal--;
3065
#ifndef NANO_TINY
3066
	if (ISSET(SOFTWRAP)) {
3067
	    goal -= strlenpt(openfile->edittop->data) / editwincols;
3068
3069
3070
	    if (goal < 0)
		openfile->edittop = openfile->edittop->next;
	}
3071
#endif
3072
    }
3073
#ifdef DEBUG
3074
    fprintf(stderr, "adjust_viewport(): setting edittop to lineno %ld\n", (long)openfile->edittop->lineno);
3075
#endif
3076
    compute_maxrows();
Chris Allegretta's avatar
Chris Allegretta committed
3077
3078
}

3079
/* Unconditionally redraw the entire screen. */
3080
void total_redraw(void)
3081
{
3082
3083
3084
3085
3086
3087
#ifdef USE_SLANG
    /* Slang curses emulation brain damage, part 4: Slang doesn't define
     * curscr. */
    SLsmg_touch_screen();
    SLsmg_refresh();
#else
3088
    wrefresh(curscr);
3089
#endif
3090
3091
}

3092
3093
/* Unconditionally redraw the entire screen, and then refresh it using
 * the current file. */
3094
3095
void total_refresh(void)
{
3096
    total_redraw();
3097
    titlebar(NULL);
3098
    edit_refresh();
3099
    bottombars(currmenu);
3100
3101
}

3102
3103
/* Display the main shortcut list on the last two rows of the bottom
 * portion of the window. */
3104
3105
void display_main_list(void)
{
3106
#ifndef DISABLE_COLOR
3107
3108
    if (openfile->syntax &&
		(openfile->syntax->formatter || openfile->syntax->linter))
3109
	set_lint_or_format_shortcuts();
3110
3111
3112
3113
    else
	set_spell_shortcuts();
#endif

3114
    bottombars(MMAIN);
3115
3116
}

3117
/* If constant is TRUE, we display the current cursor position only if
3118
3119
 * suppress_cursorpos is FALSE.  If constant is FALSE, we display the
 * position always.  In any case we reset suppress_cursorpos to FALSE. */
3120
void do_cursorpos(bool constant)
Chris Allegretta's avatar
Chris Allegretta committed
3121
{
3122
    filestruct *f;
3123
    char c;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3124
    size_t i, cur_xpt = xplustabs() + 1;
3125
    size_t cur_lenpt = strlenpt(openfile->current->data) + 1;
3126
    int linepct, colpct, charpct;
Chris Allegretta's avatar
Chris Allegretta committed
3127

3128
    assert(openfile->fileage != NULL && openfile->current != NULL);
3129

3130
    /* Determine the size of the file up to the cursor. */
3131
    f = openfile->current->next;
3132
    c = openfile->current->data[openfile->current_x];
3133
3134

    openfile->current->next = NULL;
3135
    openfile->current->data[openfile->current_x] = '\0';
3136
3137
3138

    i = get_totsize(openfile->fileage, openfile->current);

3139
    openfile->current->data[openfile->current_x] = c;
3140
    openfile->current->next = f;
3141

3142
    /* If the position needs to be suppressed, don't suppress it next time. */
3143
    if (suppress_cursorpos && constant) {
3144
	suppress_cursorpos = FALSE;
3145
	return;
3146
    }
Chris Allegretta's avatar
Chris Allegretta committed
3147

3148
    /* Display the current cursor position on the statusbar. */
3149
    linepct = 100 * openfile->current->lineno / openfile->filebot->lineno;
3150
    colpct = 100 * cur_xpt / cur_lenpt;
3151
    charpct = (openfile->totsize == 0) ? 0 : 100 * i / openfile->totsize;
3152

3153
    statusline(HUSH,
3154
	_("line %ld/%ld (%d%%), col %lu/%lu (%d%%), char %lu/%lu (%d%%)"),
3155
	(long)openfile->current->lineno,
3156
	(long)openfile->filebot->lineno, linepct,
3157
	(unsigned long)cur_xpt, (unsigned long)cur_lenpt, colpct,
3158
	(unsigned long)i, (unsigned long)openfile->totsize, charpct);
3159
3160
3161

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

3164
/* Unconditionally display the current cursor position. */
3165
void do_cursorpos_void(void)
3166
{
3167
    do_cursorpos(FALSE);
3168
3169
}

3170
3171
void enable_nodelay(void)
{
3172
3173
    nodelay_mode = TRUE;
    nodelay(edit, TRUE);
3174
3175
3176
3177
}

void disable_nodelay(void)
{
3178
3179
    nodelay_mode = FALSE;
    nodelay(edit, FALSE);
3180
3181
}

3182
3183
/* Highlight the current word being replaced or spell checked.  We
 * expect word to have tabs and control characters expanded. */
3184
void spotlight(bool active, const char *word)
Chris Allegretta's avatar
Chris Allegretta committed
3185
{
3186
    size_t word_len = strlenpt(word), room;
Chris Allegretta's avatar
Chris Allegretta committed
3187

3188
    /* Compute the number of columns that are available for the word. */
3189
    room = editwincols + get_page_start(xplustabs()) - xplustabs();
Chris Allegretta's avatar
Chris Allegretta committed
3190

3191
    assert(room > 0);
3192

3193
3194
    if (word_len > room)
	room--;
3195

Chris Allegretta's avatar
Chris Allegretta committed
3196
    reset_cursor();
Chris Allegretta's avatar
Chris Allegretta committed
3197

3198
    if (active)
3199
	wattron(edit, hilite_attribute);
Chris Allegretta's avatar
Chris Allegretta committed
3200

3201
    /* This is so we can show zero-length matches. */
3202
    if (word_len == 0)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3203
	waddch(edit, ' ');
3204
    else
3205
	waddnstr(edit, word, actual_x(word, room));
3206

3207
    if (word_len > room)
3208
	waddch(edit, '$');
Chris Allegretta's avatar
Chris Allegretta committed
3209

3210
    if (active)
3211
	wattroff(edit, hilite_attribute);
3212
3213

    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
3214
3215
}

3216
#ifndef DISABLE_EXTRA
3217
3218
#define CREDIT_LEN 54
#define XLCREDIT_LEN 9
3219

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3220
3221
/* Easter egg: Display credits.  Assume nodelay(edit) and scrollok(edit)
 * are FALSE. */
3222
3223
void do_credits(void)
{
3224
3225
    bool old_more_space = ISSET(MORE_SPACE);
    bool old_no_help = ISSET(NO_HELP);
3226
    int kbinput = ERR, crpos = 0, xlpos = 0;
3227
3228
3229
    const char *credits[CREDIT_LEN] = {
	NULL,				/* "The nano text editor" */
	NULL,				/* "version" */
Chris Allegretta's avatar
Chris Allegretta committed
3230
3231
	VERSION,
	"",
3232
	NULL,				/* "Brought to you by:" */
Chris Allegretta's avatar
Chris Allegretta committed
3233
3234
3235
3236
3237
	"Chris Allegretta",
	"Jordi Mallach",
	"Adam Rogoyski",
	"Rob Siemborski",
	"Rocco Corsi",
3238
	"David Lawrence Ramsey",
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3239
	"David Benbennick",
Benno Schulenberg's avatar
Benno Schulenberg committed
3240
	"Mark Majeres",
3241
	"Mike Frysinger",
3242
	"Benno Schulenberg",
Chris Allegretta's avatar
Chris Allegretta committed
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
	"Ken Tyler",
	"Sven Guckes",
	"Bill Soudan",
	"Christian Weisgerber",
	"Erik Andersen",
	"Big Gaute",
	"Joshua Jensen",
	"Ryan Krebs",
	"Albert Chin",
	"",
3253
	NULL,				/* "Special thanks to:" */
3254
	"Monique, Brielle & Joseph",
Chris Allegretta's avatar
Chris Allegretta committed
3255
3256
3257
3258
3259
3260
	"Plattsburgh State University",
	"Benet Laboratories",
	"Amy Allegretta",
	"Linda Young",
	"Jeremy Robichaud",
	"Richard Kolb II",
3261
	NULL,				/* "The Free Software Foundation" */
Chris Allegretta's avatar
Chris Allegretta committed
3262
	"Linus Torvalds",
3263
	NULL,				/* "the many translators and the TP" */
3264
	NULL,				/* "For ncurses:" */
3265
3266
3267
3268
	"Thomas Dickey",
	"Pavel Curtis",
	"Zeyd Ben-Halim",
	"Eric S. Raymond",
3269
3270
3271
3272
3273
3274
	NULL,				/* "and anyone else we forgot..." */
	NULL,				/* "Thank you for using nano!" */
	"",
	"",
	"",
	"",
3275
	"(C) 1999 - 2016",
3276
	"Free Software Foundation, Inc.",
3277
3278
3279
3280
	"",
	"",
	"",
	"",
3281
	"https://nano-editor.org/"
3282
3283
    };

3284
    const char *xlcredits[XLCREDIT_LEN] = {
3285
3286
3287
3288
3289
	N_("The nano text editor"),
	N_("version"),
	N_("Brought to you by:"),
	N_("Special thanks to:"),
	N_("The Free Software Foundation"),
3290
	N_("the many translators and the TP"),
3291
3292
3293
	N_("For ncurses:"),
	N_("and anyone else we forgot..."),
	N_("Thank you for using nano!")
3294
    };
3295

3296
3297
3298
3299
3300
3301
    if (!old_more_space || !old_no_help) {
	SET(MORE_SPACE);
	SET(NO_HELP);
	window_init();
    }

3302
3303
    curs_set(0);
    nodelay(edit, TRUE);
3304

3305
    blank_titlebar();
Chris Allegretta's avatar
Chris Allegretta committed
3306
    blank_edit();
3307
    blank_statusbar();
3308

3309
    wrefresh(topwin);
Chris Allegretta's avatar
Chris Allegretta committed
3310
    wrefresh(edit);
3311
    wrefresh(bottomwin);
3312
    napms(700);
3313

3314
    for (crpos = 0; crpos < CREDIT_LEN + editwinrows / 2; crpos++) {
3315
	if ((kbinput = wgetch(edit)) != ERR)
3316
	    break;
3317

3318
	if (crpos < CREDIT_LEN) {
3319
	    const char *what;
3320
3321
	    size_t start_x;

3322
	    if (credits[crpos] == NULL) {
3323
		assert(0 <= xlpos && xlpos < XLCREDIT_LEN);
3324

3325
		what = _(xlcredits[xlpos]);
3326
		xlpos++;
3327
	    } else
3328
		what = credits[crpos];
3329

3330
	    start_x = COLS / 2 - strlenpt(what) / 2 - 1;
3331
3332
	    mvwaddstr(edit, editwinrows - 1 - (editwinrows % 2),
		start_x, what);
3333
	}
3334

3335
3336
3337
3338
	wrefresh(edit);

	if ((kbinput = wgetch(edit)) != ERR)
	    break;
3339
	napms(700);
3340

3341
	scrollok(edit, TRUE);
3342
	wscrl(edit, 1);
3343
	scrollok(edit, FALSE);
3344
	wrefresh(edit);
3345

3346
	if ((kbinput = wgetch(edit)) != ERR)
3347
	    break;
3348
	napms(700);
3349

3350
	scrollok(edit, TRUE);
3351
	wscrl(edit, 1);
3352
	scrollok(edit, FALSE);
3353
	wrefresh(edit);
3354
3355
    }

3356
3357
3358
    if (kbinput != ERR)
	ungetch(kbinput);

3359
    if (!old_more_space)
3360
	UNSET(MORE_SPACE);
3361
    if (!old_no_help)
3362
	UNSET(NO_HELP);
3363
    window_init();
3364

3365
    nodelay(edit, FALSE);
3366

3367
    total_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
3368
}
3369
#endif /* !DISABLE_EXTRA */