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
635
636
637
#ifndef NANO_TINY
	case SHIFT_PAGEUP:		/* Fake key, from Shift+Alt+Up. */
	    shift_held = TRUE;
#endif
638
	case KEY_A3:	/* PageUp (9) on keypad with NumLock off. */
639
	    return KEY_PPAGE;
640
641
642
643
#ifndef NANO_TINY
	case SHIFT_PAGEDOWN:	/* Fake key, from Shift+Alt+Down. */
	    shift_held = TRUE;
#endif
644
	case KEY_C3:	/* PageDown (3) on keypad with NumLock off. */
645
	    return KEY_NPAGE;
646
#ifdef KEY_SDC
647
648
	/* Slang doesn't support KEY_SDC. */
	case KEY_SDC:
649
#endif
650
	case DEL_CODE:
651
	    if (ISSET(REBIND_DELETE))
652
		return sc_seq_or(do_delete, KEY_DC);
653
	    else
654
		return KEY_BACKSPACE;
655
#ifdef KEY_SIC
656
657
	/* Slang doesn't support KEY_SIC. */
	case KEY_SIC:
658
	    return sc_seq_or(do_insertfile_void, KEY_IC);
659
#endif
660
#ifdef KEY_SBEG
661
662
	/* Slang doesn't support KEY_SBEG. */
	case KEY_SBEG:
663
#endif
664
#ifdef KEY_BEG
665
666
	/* Slang doesn't support KEY_BEG. */
	case KEY_BEG:
667
#endif
668
669
	case KEY_B2:	/* Center (5) on keypad with NumLock off. */
	    return ERR;
670
#ifdef KEY_CANCEL
671
#ifdef KEY_SCANCEL
672
673
	/* Slang doesn't support KEY_SCANCEL. */
	case KEY_SCANCEL:
674
#endif
675
676
677
	/* Slang doesn't support KEY_CANCEL. */
	case KEY_CANCEL:
	    return first_sc_for(currmenu, do_cancel)->keycode;
678
#endif
679
#ifdef KEY_SUSPEND
680
#ifdef KEY_SSUSPEND
681
682
	/* Slang doesn't support KEY_SSUSPEND. */
	case KEY_SSUSPEND:
683
#endif
684
685
	/* Slang doesn't support KEY_SUSPEND. */
	case KEY_SUSPEND:
686
	    return sc_seq_or(do_suspend_void, KEY_SUSPEND);
687
688
#endif
#ifdef PDCURSES
689
690
691
692
693
694
695
	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;
696
#endif
697
698
#ifdef KEY_RESIZE
	/* Slang and SunOS 5.7-5.9 don't support KEY_RESIZE. */
699
700
	case KEY_RESIZE:
	    return ERR;
701
#endif
702
    }
703

704
705
706
    return retval;
}

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

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

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

926
	switch (seq[3]) {
927
	    case '2':
928
929
930
931
932
		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. */
933
			shift_held = TRUE;
934
935
			return arrow_from_abcd(seq[4]);
		}
936
		break;
937
938
939
940
941
942
943
944
945
946
#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. */
947
			return SHIFT_END;
948
		    case 'D': /* Esc [ 1 ; 4 D == Shift-Alt-Left on xterm. */
949
			return SHIFT_HOME;
950
951
952
		}
		break;
#endif
953
	    case '5':
954
955
956
957
958
959
960
961
962
963
		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;
		}
964
		break;
965
966
967
968
969
970
971
972
973
974
975
976
977
978
#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
979
	}
980
981
982
983

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

1160
    return ERR;
1161
1162
}

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

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

    free(seq);

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

1214
#ifdef DEBUG
1215
1216
    fprintf(stderr, "parse_escape_sequence(): kbinput = %d, seq_len = %lu, retval = %d\n",
		kbinput, (unsigned long)seq_len, retval);
1217
1218
1219
1220
1221
#endif

    return retval;
}

1222
1223
/* Translate a byte sequence: turn a three-digit decimal number (from
 * 000 to 255) into its corresponding byte value. */
1224
int get_byte_kbinput(int kbinput)
1225
{
1226
    static int byte_digits = 0, byte = 0;
1227
    int retval = ERR;
1228

1229
1230
    /* Increment the byte digit counter. */
    byte_digits++;
1231

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

    /* If we have a result, reset the byte digit counter and the byte
     * sequence holder. */
    if (retval != ERR) {
	byte_digits = 0;
1283
	byte = 0;
1284
1285
1286
    }

#ifdef DEBUG
1287
    fprintf(stderr, "get_byte_kbinput(): kbinput = %d, byte_digits = %d, byte = %d, retval = %d\n", kbinput, byte_digits, byte, retval);
1288
1289
1290
1291
1292
#endif

    return retval;
}

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

1306
    return ERR;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1307
1308
}

1309
/* Translate a Unicode sequence: turn a six-digit hexadecimal number
1310
 * (from 000000 to 10FFFF, case-insensitive) into its corresponding
1311
 * multibyte value. */
1312
long get_unicode_kbinput(WINDOW *win, int kbinput)
1313
{
1314
1315
1316
    static int uni_digits = 0;
    static long uni = 0;
    long retval = ERR;
1317

1318
    /* Increment the Unicode digit counter. */
1319
    uni_digits++;
1320

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

1358
    /* Show feedback only when editing, not when at a prompt. */
1359
    if (retval == ERR && win == edit) {
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
	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);
    }
1370

1371
#ifdef DEBUG
1372
1373
    fprintf(stderr, "get_unicode_kbinput(): kbinput = %d, uni_digits = %d, uni = %ld, retval = %ld\n",
						kbinput, uni_digits, uni, retval);
1374
1375
#endif

1376
1377
1378
1379
    /* If we have an end result, reset the Unicode digit counter. */
    if (retval != ERR)
	uni_digits = 0;

1380
1381
    return retval;
}
1382
#endif /* ENABLE_UTF8 */
1383

1384
1385
1386
1387
1388
1389
/* Translate a control character sequence: turn an ASCII non-control
 * character into its corresponding control character. */
int get_control_kbinput(int kbinput)
{
    int retval;

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

1411
#ifdef DEBUG
1412
    fprintf(stderr, "get_control_kbinput(): kbinput = %d, retval = %d\n", kbinput, retval);
1413
1414
#endif

1415
1416
    return retval;
}
1417

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1418
1419
/* Put the output-formatted characters in output back into the keystroke
 * buffer, so that they can be parsed and displayed as output again. */
1420
void unparse_kbinput(char *output, size_t output_len)
1421
{
1422
1423
    int *input;
    size_t i;
1424

1425
1426
1427
1428
    if (output_len == 0)
	return;

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

1430
1431
    for (i = 0; i < output_len; i++)
	input[i] = (int)output[i];
1432

1433
    unget_input(input, output_len);
1434

1435
    free(input);
1436
1437
}

1438
/* Read in a stream of characters verbatim, and return the length of the
1439
1440
1441
1442
 * string in kbinput_len.  Assume nodelay(win) is FALSE. */
int *get_verbatim_kbinput(WINDOW *win, size_t *kbinput_len)
{
    int *retval;
1443

1444
    /* Turn off flow control characters if necessary so that we can type
1445
1446
     * them in verbatim, and turn the keypad off if necessary so that we
     * don't get extended keypad values. */
1447
1448
    if (ISSET(PRESERVE))
	disable_flow_control();
1449
1450
    if (!ISSET(REBIND_KEYPAD))
	keypad(win, FALSE);
1451
1452
1453

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

    /* Turn flow control characters back on if necessary and turn the
1456
     * keypad back on if necessary now that we're done. */
1457
1458
    if (ISSET(PRESERVE))
	enable_flow_control();
1459
1460
1461
1462
1463
1464
    /* 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);
    }
1465

1466
    return retval;
1467
1468
}

1469
1470
1471
1472
/* 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). */
1473
int *parse_verbatim_kbinput(WINDOW *win, size_t *count)
1474
{
1475
    int *kbinput;
1476

1477
    /* Read in the first code. */
1478
1479
    while ((kbinput = get_input(win, 1)) == NULL)
	;
1480

1481
#ifndef NANO_TINY
1482
1483
1484
    /* When the window was resized, abort and return nothing. */
    if (*kbinput == KEY_WINCH) {
	free(kbinput);
1485
	*count = 0;
1486
1487
	return NULL;
    }
1488
#endif
1489

1490
#ifdef ENABLE_UTF8
1491
    if (using_utf8()) {
1492
	/* Check whether the first code is a valid starter digit: 0 or 1. */
1493
	long uni = get_unicode_kbinput(win, *kbinput);
1494

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

	    while (uni == ERR) {
1505
		free(kbinput);
1506
1507
		while ((kbinput = get_input(win, 1)) == NULL)
		    ;
1508
		uni = get_unicode_kbinput(win, *kbinput);
1509
	    }
1510

1511
	    /* Convert the Unicode value to a multibyte sequence. */
1512
	    uni_mb = make_mbchar(uni, &uni_mb_len);
1513

1514
	    seq = (int *)nmalloc(uni_mb_len * sizeof(int));
1515

1516
1517
	    for (i = 0; i < uni_mb_len; i++)
		seq[i] = (unsigned char)uni_mb[i];
1518

1519
	    /* Insert the multibyte sequence into the input buffer. */
1520
	    unget_input(seq, uni_mb_len);
1521

1522
1523
	    free(seq);
	    free(uni_mb);
1524
	}
1525
    } else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1526
#endif /* ENABLE_UTF8 */
1527
	/* Put back the first code. */
1528
	unget_input(kbinput, 1);
1529

1530
1531
    free(kbinput);

1532
    *count = 1;
1533

1534
    /* If this is an iTerm/Eterm/rxvt double escape, take both Escapes. */
1535
1536
1537
1538
1539
    if (key_buffer_len > 3 && *key_buffer == ESC_CODE &&
		key_buffer[1] == ESC_CODE && key_buffer[2] == '[')
	*count = 2;

    return get_input(NULL, *count);
1540
1541
}

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

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

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

1570
    /* Save the screen coordinates where the mouse event took place. */
1571
    *mouse_x = mevent.x - margin;
1572
    *mouse_y = mevent.y;
1573

1574
1575
    in_bottomwin = wenclose(bottomwin, *mouse_y, *mouse_x);

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

1592
1593
1594
1595
1596
1597
1598
1599
1600
	    /* 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. */
1601
		*mouse_x = mevent.x - margin;
1602
1603
1604
1605
		*mouse_y = mevent.y;

		return 0;
	    }
1606

1607
	    /* Determine how many shortcuts are being shown. */
1608
	    number = length_of_list(currmenu);
1609

1610
1611
	    if (number > MAIN_VISIBLE)
		number = MAIN_VISIBLE;
1612

1613
1614
1615
	    /* 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. */
1616
	    if (number < 2)
1617
1618
		i = COLS / (MAIN_VISIBLE / 2);
	    else
1619
		i = COLS / ((number / 2) + (number % 2));
1620

1621
1622
	    /* Calculate the one-based index in the shortcut list. */
	    j = (*mouse_x / i) * 2 + *mouse_y;
1623

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

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

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

1669
1670
1671
1672
	if (in_bottomwin)
	    /* Translate the mouse event coordinates so that they're
	     * relative to bottomwin. */
	    wmouse_trafo(bottomwin, mouse_y, mouse_x, FALSE);
1673

1674
	if (in_edit || (in_bottomwin && *mouse_y == 0)) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1675
1676
	    int i;

1677
1678
1679
1680
1681
	    /* 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) ?
1682
				KEY_PPAGE : KEY_NPAGE, FALSE);
1683
1684
1685
1686
1687
1688
1689

	    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;
1690
1691
    }
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1692
1693
1694

    /* Ignore all other mouse events. */
    return 2;
1695
}
1696
1697
#endif /* !DISABLE_MOUSE */

1698
1699
1700
1701
/* 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. */
1702
const sc *get_shortcut(int *kbinput)
1703
{
1704
    sc *s;
1705

1706
#ifdef DEBUG
1707
1708
    fprintf(stderr, "get_shortcut(): kbinput = %d, meta_key = %s -- ",
				*kbinput, meta_key ? "TRUE" : "FALSE");
1709
1710
#endif

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

    return NULL;
}

1728
1729
1730
1731
1732
/* 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
1733

1734
1735
1736
1737
    for (; n > 0; n--)
	waddch(win, ' ');
}

1738
/* Blank the first line of the top portion of the window. */
1739
void blank_titlebar(void)
Chris Allegretta's avatar
Chris Allegretta committed
1740
{
1741
    blank_line(topwin, 0, 0, COLS);
1742
1743
}

1744
/* Blank all the lines of the middle portion of the window, i.e. the
1745
 * edit window. */
Chris Allegretta's avatar
Chris Allegretta committed
1746
1747
void blank_edit(void)
{
1748
    int i;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1749

1750
    for (i = 0; i < editwinrows; i++)
1751
	blank_line(edit, i, 0, COLS);
Chris Allegretta's avatar
Chris Allegretta committed
1752
1753
}

1754
/* Blank the first line of the bottom portion of the window. */
Chris Allegretta's avatar
Chris Allegretta committed
1755
1756
void blank_statusbar(void)
{
1757
    blank_line(bottomwin, 0, 0, COLS);
Chris Allegretta's avatar
Chris Allegretta committed
1758
1759
}

1760
1761
/* If the NO_HELP flag isn't set, blank the last two lines of the bottom
 * portion of the window. */
1762
1763
void blank_bottombars(void)
{
1764
    if (!ISSET(NO_HELP) && LINES > 4) {
1765
1766
	blank_line(bottomwin, 1, 0, COLS);
	blank_line(bottomwin, 2, 0, COLS);
1767
1768
1769
    }
}

1770
1771
/* Check if the number of keystrokes needed to blank the statusbar has
 * been pressed.  If so, blank the statusbar, unless constant cursor
1772
 * position display is on and we are in the editing screen. */
1773
void check_statusblank(void)
Chris Allegretta's avatar
Chris Allegretta committed
1774
{
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
    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
1789
    }
1790
1791
1792
1793

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

1796
1797
/* Convert buf into a string that can be displayed on screen.  The
 * caller wants to display buf starting with column start_col, and
1798
1799
 * extending for at most span columns.  start_col is zero-based.  span
 * is one-based, so span == 0 means you get "" returned.  The returned
1800
1801
1802
 * 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. */
1803
1804
char *display_string(const char *buf, size_t start_col, size_t span,
	bool dollars)
1805
1806
{
    size_t start_index;
1807
	/* Index in buf of the first character shown. */
1808
    size_t column;
1809
	/* Screen column that start_index corresponds to. */
1810
1811
1812
1813
    char *converted;
	/* The string we return. */
    size_t index;
	/* Current position in converted. */
1814

1815
1816
    /* If dollars is TRUE, make room for the "$" at the end of the
     * line. */
1817
1818
    if (dollars && span > 0 && strlenpt(buf) > start_col + span)
	span--;
1819

1820
    if (span == 0)
1821
1822
1823
1824
	return mallocstrcpy(NULL, "");

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

1826
    assert(column <= start_col);
1827

1828
1829
    /* Allocate enough space to hold the entire converted buffer. */
    converted = charalloc(strlen(buf) * (mb_cur_max() + tabsize) + 1);
1830

1831
    index = 0;
1832
#ifdef USING_OLD_NCURSES
1833
    seen_wide = FALSE;
1834
#endif
1835
    buf += start_index;
1836

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

1855
	    converted[index++] = ' ';
1856
	    start_col++;
1857

1858
	    buf += parse_mbchar(buf, NULL, NULL);
1859
	}
1860
#endif
1861
1862
    }

1863
    while (*buf != '\0') {
1864
	int charlength, charwidth = 1;
1865

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

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

1901
	charlength = length_of_char(buf, &charwidth);
1902

1903
	/* If buf contains a control character, represent it. */
1904
	if (is_cntrl_mbchar(buf)) {
1905
	    converted[index++] = '^';
1906
	    converted[index++] = control_mbrep(buf);
1907
	    start_col += 2;
1908
1909
1910
	    buf += charlength;
	    continue;
	}
1911

1912
1913
1914
1915
	/* If buf contains a valid non-control character, simply copy it. */
	if (charlength > 0) {
	    for (; charlength > 0; charlength--)
		converted[index++] = *(buf++);
1916

1917
	    start_col += charwidth;
1918
#ifdef USING_OLD_NCURSES
1919
	    if (charwidth > 1)
1920
		seen_wide = TRUE;
1921
#endif
1922
	    continue;
1923
1924
	}

1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
	/* 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;
1936
1937
    }

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1938
    /* Null-terminate converted. */
1939
    converted[index] = '\0';
1940

1941
1942
    /* Make sure converted takes up no more than span columns. */
    index = actual_x(converted, span);
1943
    null_at(&converted, index);
1944

1945
    return converted;
1946
1947
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1948
1949
1950
1951
1952
1953
/* 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. */
1954
void titlebar(const char *path)
Chris Allegretta's avatar
Chris Allegretta committed
1955
{
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
    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. */
1968

1969
1970
1971
1972
    /* If the screen is too small, there is no titlebar. */
    if (topwin == NULL)
	return;

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

1975
    wattron(topwin, interface_color_pair[TITLE_BAR]);
1976

1977
    blank_titlebar();
Chris Allegretta's avatar
Chris Allegretta committed
1978

1979
1980
1981
    /* 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
1982

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

1997
1998
1999
2000
	if (openfile->modified)
	    state = _("Modified");
	else if (ISSET(VIEW_MODE))
	    state = _("View");
2001

2002
2003
	pluglen = strlenpt(_("Modified")) + 1;
    }
2004

2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
    /* 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;
2015
2016
    }

2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
    /* 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;
2029
2030
2031
	}
    }

2032
2033
2034
2035
    /* If we have side spaces left, center the path name. */
    if (verlen > 0)
	offset = verlen + (COLS - (verlen + pluglen + statelen) -
					(prefixlen + pathlen)) / 2;
2036

2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
    /* 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);
2054
    }
2055

2056
2057
2058
2059
2060
2061
    /* 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));

2062
    wattroff(topwin, interface_color_pair[TITLE_BAR]);
2063

2064
    wnoutrefresh(topwin);
Chris Allegretta's avatar
Chris Allegretta committed
2065
    reset_cursor();
2066
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
2067
2068
}

2069
2070
2071
2072
2073
2074
/* Display a normal message on the statusbar, quietly. */
void statusbar(const char *msg)
{
    statusline(HUSH, msg);
}

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

    /* Switch the cursor back on after displaying the message. */
    curs_set(1);
2085
2086
}

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

    UNSET(WHITESPACE_DISPLAY);
2100
#endif
2101
2102
2103
2104
2105

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

2112
2113
2114
2115
2116
2117
2118
    /* 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)
2119
2120
	napms(1200);

2121
    if (importance == ALERT)
2122
	beep();
2123
2124

    lastmessage = importance;
2125

2126
2127
2128
    /* Turn the cursor off while fiddling in the statusbar. */
    curs_set(0);

2129
2130
    blank_statusbar();

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

2138
    start_x = (COLS - strlenpt(message)) / 2;
2139
    bracketed = (start_x > 1);
2140

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

2151
    /* Push the message to the screen straightaway. */
2152
    wnoutrefresh(bottomwin);
2153
    doupdate();
2154

2155
    suppress_cursorpos = TRUE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2156

2157
#ifndef NANO_TINY
2158
2159
2160
2161
2162
    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. */
2163
2164
2165
    if (ISSET(QUICK_BLANK))
	statusblank = 1;
    else
2166
#endif
2167
	statusblank = 26;
2168
2169
}

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

2178
2179
2180
    /* Set the global variable to the given menu. */
    currmenu = menu;

2181
    if (ISSET(NO_HELP) || LINES < 5)
Chris Allegretta's avatar
Chris Allegretta committed
2182
2183
	return;

2184
    /* Determine how many shortcuts there are to show. */
2185
    number = length_of_list(menu);
2186

2187
2188
    if (number > MAIN_VISIBLE)
	number = MAIN_VISIBLE;
2189

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

2193
    /* If there is no room, don't print anything. */
2194
    if (itemwidth == 0)
2195
2196
	return;

2197
    blank_bottombars();
2198

2199
#ifdef DEBUG
2200
    fprintf(stderr, "In bottombars, number of items == \"%d\"\n", (int) number);
2201
#endif
2202

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

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

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

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

2234
    reset_cursor();
2235
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
2236
2237
}

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

2247
    wattron(bottomwin, interface_color_pair[KEY_COMBO]);
2248
    waddnstr(bottomwin, keystroke, actual_x(keystroke, length));
2249
    wattroff(bottomwin, interface_color_pair[KEY_COMBO]);
2250

2251
    length -= strlenpt(keystroke) + 1;
2252

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

2261
2262
/* 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
2263
2264
void reset_cursor(void)
{
2265
    size_t xpt = xplustabs();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2266

2267
#ifndef NANO_TINY
2268
    if (ISSET(SOFTWRAP)) {
2269
	filestruct *line = openfile->edittop;
2270
2271
	openfile->current_y = 0;

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

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

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

2291
2292
2293
2294
2295
2296
2297
2298
/* 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. */
2299
void edit_draw(filestruct *fileptr, const char *converted, int
2300
	line, size_t start)
Chris Allegretta's avatar
Chris Allegretta committed
2301
{
2302
#if !defined(NANO_TINY) || !defined(DISABLE_COLOR)
2303
2304
2305
    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. */
2306
    size_t endpos = actual_x(fileptr->data, start + editwincols - 1) + 1;
2307
2308
2309
2310
2311
	/* 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. */
2312
2313
#endif

2314
    assert(openfile != NULL && fileptr != NULL && converted != NULL);
2315
2316
2317
    assert(strlenpt(converted) <= editwincols);

#ifdef ENABLE_LINENUMBERS
2318
2319
2320
    /* 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) {
2321
	wattron(edit, interface_color_pair[LINE_NUMBER]);
2322
2323
2324
2325
	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, " ");
2326
	wattroff(edit, interface_color_pair[LINE_NUMBER]);
2327
2328
    }
#endif
2329

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

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

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

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

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

2365
	    wattron(edit, varnish->attributes);
2366
2367
2368
	    /* 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. */
2369

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

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

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

2402
2403
2404
			index = actual_x(converted, x_start);

			paintlen = actual_x(converted + index,
2405
2406
				strnlenpt(fileptr->data,
				startmatch.rm_eo) - start - x_start);
2407
2408
2409

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

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

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

2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
		/* 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. */

2452
		while (start_line != NULL && regexec(varnish->start,
2453
2454
2455
			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. */
2456
		    if (regexec(varnish->end, start_line->data, 0, NULL, 0) == 0)
2457
2458
2459
			goto step_two;
		    start_line = start_line->prev;
		}
2460

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

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

2472
		/* Skip over a zero-length regex match. */
2473
		if (startmatch.rm_so == startmatch.rm_eo)
2474
2475
		    goto tail_of_loop;

2476
2477
2478
2479
2480
2481
2482
		/* 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;
2483
		    if (regexec(varnish->end, start_line->data +
2484
2485
2486
				start_col + startmatch.rm_eo, 0, NULL,
				(start_col + startmatch.rm_eo == 0) ?
				0 : REG_NOTBOL) == REG_NOMATCH)
2487
2488
2489
			/* No end found after this start. */
			break;
		    start_col++;
2490
2491
		    if (regexec(varnish->start, start_line->data + start_col,
				1, &startmatch, REG_NOTBOL) == REG_NOMATCH)
2492
2493
2494
2495
2496
2497
2498
2499
2500
			/* 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;
2501
		while (end_line != NULL && regexec(varnish->end,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2502
			end_line->data, 1, &endmatch, 0) == REG_NOMATCH)
2503
		    end_line = end_line->next;
2504

2505
2506
2507
2508
		/* 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) {
2509
		    fileptr->multidata[varnish->id] = CBEGINBEFORE;
2510
2511
		    goto step_two;
		}
2512

2513
2514
2515
2516
2517
2518
2519
		/* 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;
2520
		    fileptr->multidata[varnish->id] = CWHOLELINE;
2521
#ifdef DEBUG
2522
    fprintf(stderr, "  Marking for id %i  line %i as CWHOLELINE\n", varnish->id, line);
2523
#endif
2524
2525
2526
		} else {
		    paintlen = actual_x(converted, strnlenpt(fileptr->data,
						endmatch.rm_eo) - start);
2527
		    fileptr->multidata[varnish->id] = CBEGINBEFORE;
2528
#ifdef DEBUG
2529
    fprintf(stderr, "  Marking for id %i  line %i as CBEGINBEFORE\n", varnish->id, line);
2530
#endif
2531
		}
2532
		mvwaddnstr(edit, line, margin, converted, paintlen);
2533
2534
2535
2536
		/* If the whole line has been painted, don't bother looking
		 * for any more starts. */
		if (paintlen < 0)
		    goto tail_of_loop;
2537
  step_two:
2538
2539
2540
2541
2542
		/* 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) {
2543
		    if (regexec(varnish->start, fileptr->data + start_col,
2544
2545
				1, &startmatch, (start_col == 0) ?
				0 : REG_NOTBOL) == REG_NOMATCH ||
2546
				start_col + startmatch.rm_so >= endpos)
2547
2548
			/* No more starts on this line. */
			break;
2549

2550
2551
2552
2553
2554
2555
2556
		    /* 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,
2557
				startmatch.rm_so) - start;
2558

2559
		    index = actual_x(converted, x_start);
2560

2561
		    if (regexec(varnish->end, fileptr->data +
2562
				startmatch.rm_eo, 1, &endmatch,
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
				(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 &&
2573
				endmatch.rm_eo > startmatch.rm_so) {
2574
			    paintlen = actual_x(converted + index,
2575
					strnlenpt(fileptr->data,
2576
					endmatch.rm_eo) - start - x_start);
2577

2578
			    assert(0 <= x_start && x_start < editwincols);
2579

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

2598
			while (end_line != NULL &&
2599
				regexec(varnish->end, end_line->data,
2600
				0, NULL, 0) == REG_NOMATCH)
2601
			    end_line = end_line->next;
2602

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

2607
			assert(0 <= x_start && x_start < editwincols);
2608
2609

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

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

2647
	mark_order(&top, &top_x, &bot, &bot_x, NULL);
2648
2649
2650
2651
2652

	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
2653

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

	    /* 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;
2661

2662
2663
2664
2665
2666
	    /* 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. */
2667
2668
2669
	    if (bot_x >= endpos)
		paintlen = -1;
	    else
2670
		paintlen = strnlenpt(fileptr->data, bot_x) - (x_start + start);
2671
2672
2673
2674
2675
2676
2677
2678

	    /* 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;
	    }
2679
2680
2681

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

2682
	    index = actual_x(converted, x_start);
2683

2684
2685
2686
	    if (paintlen > 0)
		paintlen = actual_x(converted + index, paintlen);

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

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

2713
    assert(fileptr != NULL);
2714

2715
#ifndef NANO_TINY
2716
    if (ISSET(SOFTWRAP)) {
2717
2718
	filestruct *tmp;

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

2725
    if (line < 0 || line >= editwinrows)
Chris Allegretta's avatar
Chris Allegretta committed
2726
	return 1;
2727

2728
    /* First, blank out the line. */
2729
    blank_line(edit, line, 0, COLS);
2730

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

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

2753
    /* Paint the line. */
2754
    edit_draw(fileptr, converted, line, page_start);
2755
    free(converted);
Chris Allegretta's avatar
Chris Allegretta committed
2756

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

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

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

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

2804
/* When edittop changes, try and figure out how many lines
2805
 * we really have to work with (i.e. set maxrows). */
2806
2807
2808
2809
2810
void compute_maxrows(void)
{
    int n;
    filestruct *foo = openfile->edittop;

2811
2812
2813
2814
2815
    if (!ISSET(SOFTWRAP)) {
	maxrows = editwinrows;
	return;
    }

2816
2817
    maxrows = 0;
    for (n = 0; n < editwinrows && foo; n++) {
2818
	maxrows++;
2819
	n += strlenpt(foo->data) / editwincols;
2820
2821
2822
	foo = foo->next;
    }

2823
2824
2825
    if (n < editwinrows)
	maxrows += editwinrows - n;

2826
#ifdef DEBUG
2827
    fprintf(stderr, "compute_maxrows(): maxrows = %d\n", maxrows);
2828
2829
2830
#endif
}

2831
2832
/* 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
2833
2834
 * scrolling.  direction is the direction to scroll, either UPWARD or
 * DOWNWARD, and nlines is the number of lines to scroll.  We change
2835
2836
 * edittop, and assume that current and current_x are up to date.  We
 * also assume that scrollok(edit) is FALSE. */
2837
void edit_scroll(scroll_dir direction, ssize_t nlines)
2838
{
2839
    ssize_t i;
2840
    filestruct *foo;
2841

2842
    assert(nlines > 0);
2843

2844
2845
2846
    /* Part 1: nlines is the number of lines we're going to scroll the
     * text of the edit window. */

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

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

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

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

2881
    if (refresh_needed == TRUE)
2882
	return;
2883
2884
2885

    /* Scroll the text of the edit window up or down nlines lines,
     * depending on the value of direction. */
2886
    scrollok(edit, TRUE);
2887
    wscrl(edit, (direction == UPWARD) ? -nlines : nlines);
2888
2889
    scrollok(edit, FALSE);

2890
2891
2892
    /* Part 2: nlines is the number of lines in the scrolled region of
     * the edit window that we need to draw. */

2893
2894
2895
2896
2897
2898
    /* 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
2899

2900
2901
    if (nlines > editwinrows)
	nlines = editwinrows;
2902
2903
2904

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

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

2914
2915
2916
2917
2918
2919
    /* 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--) {
2920
2921
	if ((i == nlines && direction == DOWNWARD) || (i == 1 &&
		direction == UPWARD)) {
2922
	    if (need_horizontal_scroll(openfile->placewewant, 0))
2923
2924
2925
2926
		update_line(foo, (foo == openfile->current) ?
			openfile->current_x : 0);
	} else
	    update_line(foo, (foo == openfile->current) ?
2927
		openfile->current_x : 0);
2928
	foo = foo->next;
2929
    }
2930
    compute_maxrows();
2931
2932
2933
}

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

    openfile->placewewant = xplustabs();

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

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

2958
	while (foo != openfile->current) {
2959
	    update_line(foo, 0);
2960

2961
2962
2963
	    foo = (foo->lineno > openfile->current->lineno) ?
			foo->prev : foo->next;
	}
2964
2965
2966
2967
2968
2969
    } 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);
2970
2971
2972

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2979
2980
/* 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
2981
2982
void edit_refresh(void)
{
2983
    filestruct *foo;
2984
    int nlines;
2985

2986
    /* Figure out what maxrows should really be. */
2987
    compute_maxrows();
2988

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

2999
3000
    foo = openfile->edittop;

3001
#ifdef DEBUG
3002
    fprintf(stderr, "edit_refresh(): edittop->lineno = %ld\n", (long)openfile->edittop->lineno);
3003
#endif
3004

3005
    for (nlines = 0; nlines < editwinrows && foo != NULL; nlines++) {
3006
	nlines += update_line(foo, (foo == openfile->current) ?
3007
					openfile->current_x : 0);
3008
3009
3010
	foo = foo->next;
    }

3011
    for (; nlines < editwinrows; nlines++)
3012
3013
3014
	blank_line(edit, nlines, 0, COLS);

    reset_cursor();
3015
    curs_set(1);
3016
    wnoutrefresh(edit);
3017
3018

    refresh_needed = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
3019
3020
}

3021
3022
3023
3024
3025
/* 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. */
3026
void adjust_viewport(update_type manner)
Chris Allegretta's avatar
Chris Allegretta committed
3027
{
3028
    int goal = 0;
3029

3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
    /* 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)
3040
	goal = editwinrows / 2;
3041
    else if (manner == FLOWING) {
3042
	if (openfile->current->lineno >= openfile->edittop->lineno) {
3043
	    goal = editwinrows - 1;
3044
3045
#ifndef NANO_TINY
	    if (ISSET(SOFTWRAP))
3046
		goal -= strlenpt(openfile->current->data) / editwincols;
3047
3048
#endif
	}
3049
    } else {
3050
	goal = openfile->current_y;
3051

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

3057
3058
3059
3060
    openfile->edittop = openfile->current;

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

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

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

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

3111
    bottombars(MMAIN);
3112
3113
}

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

3125
    assert(openfile->fileage != NULL && openfile->current != NULL);
3126

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

    openfile->current->next = NULL;
3132
    openfile->current->data[openfile->current_x] = '\0';
3133
3134
3135

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

3136
    openfile->current->data[openfile->current_x] = c;
3137
    openfile->current->next = f;
3138

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

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

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

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

3161
/* Unconditionally display the current cursor position. */
3162
void do_cursorpos_void(void)
3163
{
3164
    do_cursorpos(FALSE);
3165
3166
}

3167
3168
void enable_nodelay(void)
{
3169
3170
    nodelay_mode = TRUE;
    nodelay(edit, TRUE);
3171
3172
3173
3174
}

void disable_nodelay(void)
{
3175
3176
    nodelay_mode = FALSE;
    nodelay(edit, FALSE);
3177
3178
}

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

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

3188
    assert(room > 0);
3189

3190
3191
    if (word_len > room)
	room--;
3192

Chris Allegretta's avatar
Chris Allegretta committed
3193
    reset_cursor();
Chris Allegretta's avatar
Chris Allegretta committed
3194

3195
    if (active)
3196
	wattron(edit, hilite_attribute);
Chris Allegretta's avatar
Chris Allegretta committed
3197

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

3204
    if (word_len > room)
3205
	waddch(edit, '$');
Chris Allegretta's avatar
Chris Allegretta committed
3206

3207
    if (active)
3208
	wattroff(edit, hilite_attribute);
3209
3210

    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
3211
3212
}

3213
#ifndef DISABLE_EXTRA
3214
3215
#define CREDIT_LEN 54
#define XLCREDIT_LEN 9
3216

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

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

3293
3294
3295
3296
3297
3298
    if (!old_more_space || !old_no_help) {
	SET(MORE_SPACE);
	SET(NO_HELP);
	window_init();
    }

3299
3300
    curs_set(0);
    nodelay(edit, TRUE);
3301

3302
    blank_titlebar();
Chris Allegretta's avatar
Chris Allegretta committed
3303
    blank_edit();
3304
    blank_statusbar();
3305

3306
    wrefresh(topwin);
Chris Allegretta's avatar
Chris Allegretta committed
3307
    wrefresh(edit);
3308
    wrefresh(bottomwin);
3309
    napms(700);
3310

3311
    for (crpos = 0; crpos < CREDIT_LEN + editwinrows / 2; crpos++) {
3312
	if ((kbinput = wgetch(edit)) != ERR)
3313
	    break;
3314

3315
	if (crpos < CREDIT_LEN) {
3316
	    const char *what;
3317
3318
	    size_t start_x;

3319
	    if (credits[crpos] == NULL) {
3320
		assert(0 <= xlpos && xlpos < XLCREDIT_LEN);
3321

3322
		what = _(xlcredits[xlpos]);
3323
		xlpos++;
3324
	    } else
3325
		what = credits[crpos];
3326

3327
	    start_x = COLS / 2 - strlenpt(what) / 2 - 1;
3328
3329
	    mvwaddstr(edit, editwinrows - 1 - (editwinrows % 2),
		start_x, what);
3330
	}
3331

3332
3333
3334
3335
	wrefresh(edit);

	if ((kbinput = wgetch(edit)) != ERR)
	    break;
3336
	napms(700);
3337

3338
	scrollok(edit, TRUE);
3339
	wscrl(edit, 1);
3340
	scrollok(edit, FALSE);
3341
	wrefresh(edit);
3342

3343
	if ((kbinput = wgetch(edit)) != ERR)
3344
	    break;
3345
	napms(700);
3346

3347
	scrollok(edit, TRUE);
3348
	wscrl(edit, 1);
3349
	scrollok(edit, FALSE);
3350
	wrefresh(edit);
3351
3352
    }

3353
3354
3355
    if (kbinput != ERR)
	ungetch(kbinput);

3356
    if (!old_more_space)
3357
	UNSET(MORE_SPACE);
3358
    if (!old_no_help)
3359
	UNSET(NO_HELP);
3360
    window_init();
3361

3362
    nodelay(edit, FALSE);
3363

3364
    total_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
3365
}
3366
#endif /* !DISABLE_EXTRA */