winio.c 101 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
410
411
412
413
414
415
416
417
418
419
420
		    case 'a':
			retval = shiftaltup;
			break;
		    case 'b':
			retval = shiftaltdown;
			break;
		    case 'c':
			retval = shiftaltright;
			break;
		    case 'd':
			retval = shiftaltleft;
			break;
421
422
423
		}
		double_esc = FALSE;
		escapes = 0;
424
	    } else if (key_buffer_len == 0) {
425
426
427
428
429
430
431
432
433
434
435
436
437
438
		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);

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

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

448
			seq = (int *)nmalloc(byte_mb_len * sizeof(int));
449
450
451
452

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

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

			free(byte_mb);
			free(seq);
458
459
460

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

508
509
510
    if (retval == ERR)
	return ERR;

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

547
#ifdef __linux__
548
    /* When not running under X, check for the bare arrow keys whether
549
550
551
552
553
554
     * 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) {
555
	    if (retval == KEY_UP)
556
		return sc_seq_or(do_prev_block, controlup);
557
	    else if (retval == KEY_DOWN)
558
		return sc_seq_or(do_next_block, controldown);
559
	    else if (retval == KEY_LEFT)
560
		return sc_seq_or(do_prev_word_void, controlleft);
561
	    else if (retval == KEY_RIGHT)
562
		return sc_seq_or(do_next_word_void, controlright);
563
	}
564

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

570
571
572
	/* Are both Shift and Alt being held? */
	if ((modifiers & 0x09) == 0x09) {
	    if (retval == KEY_UP)
573
		return sc_seq_or(do_page_up, shiftaltup);
574
	    else if (retval == KEY_DOWN)
575
		return sc_seq_or(do_page_down, shiftaltdown);
576
	    else if (retval == KEY_LEFT)
577
		return sc_seq_or(do_home, shiftaltleft);
578
	    else if (retval == KEY_RIGHT)
579
		return sc_seq_or(do_end, shiftaltright);
580
	}
581
#endif
582
    }
583
#endif /* __linux__ */
584

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

702
703
704
    return retval;
}

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

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

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

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

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

1158
    return ERR;
1159
1160
}

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

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

    free(seq);

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

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

    return retval;
}

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

1227
1228
    /* Increment the byte digit counter. */
    byte_digits++;
1229

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

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

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

    return retval;
}

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

1304
    return ERR;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1305
1306
}

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

1316
    /* Increment the Unicode digit counter. */
1317
    uni_digits++;
1318

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

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

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

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

1378
1379
    return retval;
}
1380
#endif /* ENABLE_UTF8 */
1381

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

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

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

1413
1414
    return retval;
}
1415

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

1423
1424
1425
1426
    if (output_len == 0)
	return;

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

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

1431
    unget_input(input, output_len);
1432

1433
    free(input);
1434
1435
}

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

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

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

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

1464
    return retval;
1465
1466
}

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

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

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

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

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

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

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

1512
	    seq = (int *)nmalloc(uni_mb_len * sizeof(int));
1513

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

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

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

1528
1529
    free(kbinput);

1530
    *count = 1;
1531

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

    return get_input(NULL, *count);
1538
1539
}

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

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

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

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

1572
1573
    in_bottomwin = wenclose(bottomwin, *mouse_y, *mouse_x);

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

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

		return 0;
	    }
1604

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

1608
1609
	    if (number > MAIN_VISIBLE)
		number = MAIN_VISIBLE;
1610

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

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

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

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

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

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

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

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

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

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

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

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

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

    return NULL;
}

1726
1727
1728
1729
1730
/* 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
1731

1732
1733
1734
1735
    for (; n > 0; n--)
	waddch(win, ' ');
}

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

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

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

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

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

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

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

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

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

1818
    if (span == 0)
1819
1820
1821
1822
	return mallocstrcpy(NULL, "");

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

1824
    assert(column <= start_col);
1825

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

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

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

1853
	    converted[index++] = ' ';
1854
	    start_col++;
1855

1856
	    buf += parse_mbchar(buf, NULL, NULL);
1857
	}
1858
#endif
1859
1860
    }

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

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

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

1899
	charlength = length_of_char(buf, &charwidth);
1900

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

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

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

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

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

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

1943
    return converted;
1944
1945
}

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

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

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

1973
    wattron(topwin, interface_color_pair[TITLE_BAR]);
1974

1975
    blank_titlebar();
Chris Allegretta's avatar
Chris Allegretta committed
1976

1977
1978
1979
    /* 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
1980

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

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

2000
2001
	pluglen = strlenpt(_("Modified")) + 1;
    }
2002

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

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

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

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

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

2060
    wattroff(topwin, interface_color_pair[TITLE_BAR]);
2061

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

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

2073
/* Display a message on the statusbar, and set suppress_cursorpos to
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2074
2075
 * TRUE, so that the message won't be immediately overwritten if
 * constant cursor position display is on. */
2076
void statusline(message_type importance, const char *msg, ...)
2077
2078
{
    va_list ap;
2079
    char *compound, *message;
Benno Schulenberg's avatar
Benno Schulenberg committed
2080
    size_t start_x;
2081
    bool bracketed;
2082
2083
2084
2085
#ifndef NANO_TINY
    bool old_whitespace = ISSET(WHITESPACE_DISPLAY);

    UNSET(WHITESPACE_DISPLAY);
2086
#endif
2087
2088
2089
2090
2091

    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(). */
2092
    if (isendwin()) {
2093
2094
2095
2096
2097
	vfprintf(stderr, msg, ap);
	va_end(ap);
	return;
    }

2098
2099
2100
2101
2102
2103
2104
    /* 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)
2105
2106
	napms(1200);

2107
    if (importance == ALERT)
2108
	beep();
2109
2110

    lastmessage = importance;
2111

2112
2113
2114
    /* Turn the cursor off while fiddling in the statusbar. */
    curs_set(0);

2115
2116
    blank_statusbar();

2117
2118
2119
    /* Construct the message out of all the arguments. */
    compound = charalloc(mb_cur_max() * (COLS + 1));
    vsnprintf(compound, mb_cur_max() * (COLS + 1), msg, ap);
2120
    va_end(ap);
2121
2122
    message = display_string(compound, 0, COLS, FALSE);
    free(compound);
2123

2124
    start_x = (COLS - strlenpt(message)) / 2;
2125
    bracketed = (start_x > 1);
2126

2127
    wmove(bottomwin, 0, (bracketed ? start_x - 2 : start_x));
2128
    wattron(bottomwin, interface_color_pair[STATUS_BAR]);
2129
2130
    if (bracketed)
	waddstr(bottomwin, "[ ");
2131
2132
    waddstr(bottomwin, message);
    free(message);
2133
2134
    if (bracketed)
	waddstr(bottomwin, " ]");
2135
    wattroff(bottomwin, interface_color_pair[STATUS_BAR]);
2136

2137
    /* Push the message to the screen straightaway. */
2138
    wnoutrefresh(bottomwin);
2139
    doupdate();
2140

2141
    suppress_cursorpos = TRUE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2142

2143
#ifndef NANO_TINY
2144
2145
2146
2147
2148
    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. */
2149
2150
2151
    if (ISSET(QUICK_BLANK))
	statusblank = 1;
    else
2152
#endif
2153
	statusblank = 26;
2154
2155
}

2156
2157
/* Display the shortcut list corresponding to menu on the last two rows
 * of the bottom portion of the window. */
2158
void bottombars(int menu)
Chris Allegretta's avatar
Chris Allegretta committed
2159
{
2160
    size_t number, itemwidth, i;
2161
2162
    subnfunc *f;
    const sc *s;
2163

2164
2165
2166
    /* Set the global variable to the given menu. */
    currmenu = menu;

2167
    if (ISSET(NO_HELP) || LINES < 5)
Chris Allegretta's avatar
Chris Allegretta committed
2168
2169
	return;

2170
    /* Determine how many shortcuts there are to show. */
2171
    number = length_of_list(menu);
2172

2173
2174
    if (number > MAIN_VISIBLE)
	number = MAIN_VISIBLE;
2175

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

2179
    /* If there is no room, don't print anything. */
2180
    if (itemwidth == 0)
2181
2182
	return;

2183
    blank_bottombars();
2184

2185
#ifdef DEBUG
2186
    fprintf(stderr, "In bottombars, number of items == \"%d\"\n", (int) number);
2187
#endif
2188

2189
    for (f = allfuncs, i = 0; i < number && f != NULL; f = f->next) {
2190
#ifdef DEBUG
2191
	fprintf(stderr, "Checking menu items....");
2192
#endif
2193
	if ((f->menus & menu) == 0)
2194
	    continue;
2195

2196
#ifdef DEBUG
2197
	fprintf(stderr, "found one! f->menus = %x, desc = \"%s\"\n", f->menus, f->desc);
2198
#endif
2199
2200
	s = first_sc_for(menu, f->scfunc);
	if (s == NULL) {
2201
2202
2203
#ifdef DEBUG
	    fprintf(stderr, "Whoops, guess not, no shortcut key found for func!\n");
#endif
2204
2205
	    continue;
	}
2206
2207

	wmove(bottomwin, 1 + i % 2, (i / 2) * itemwidth);
2208
#ifdef DEBUG
2209
	fprintf(stderr, "Calling onekey with keystr \"%s\" and desc \"%s\"\n", s->keystr, f->desc);
2210
#endif
2211
	onekey(s->keystr, _(f->desc), itemwidth + (COLS % itemwidth));
2212
	i++;
Chris Allegretta's avatar
Chris Allegretta committed
2213
    }
2214

2215
2216
    /* Defeat a VTE bug by moving the cursor and forcing a screen update. */
    wmove(bottomwin, 0, 0);
2217
    wnoutrefresh(bottomwin);
2218
2219
    doupdate();

2220
    reset_cursor();
2221
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
2222
2223
}

2224
2225
/* 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
2226
 * to write at most length characters, even if length is very small and
2227
2228
 * keystroke and desc are long.  Note that waddnstr(,,(size_t)-1) adds
 * the whole string!  We do not bother padding the entry with blanks. */
2229
void onekey(const char *keystroke, const char *desc, int length)
Chris Allegretta's avatar
Chris Allegretta committed
2230
{
2231
2232
    assert(keystroke != NULL && desc != NULL);

2233
    wattron(bottomwin, interface_color_pair[KEY_COMBO]);
2234
    waddnstr(bottomwin, keystroke, actual_x(keystroke, length));
2235
    wattroff(bottomwin, interface_color_pair[KEY_COMBO]);
2236

2237
    length -= strlenpt(keystroke) + 1;
2238

2239
    if (length > 0) {
2240
	waddch(bottomwin, ' ');
2241
	wattron(bottomwin, interface_color_pair[FUNCTION_TAG]);
2242
	waddnstr(bottomwin, desc, actual_x(desc, length));
2243
	wattroff(bottomwin, interface_color_pair[FUNCTION_TAG]);
Chris Allegretta's avatar
Chris Allegretta committed
2244
2245
2246
    }
}

2247
2248
/* 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
2249
2250
void reset_cursor(void)
{
2251
    size_t xpt = xplustabs();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2252

2253
#ifndef NANO_TINY
2254
    if (ISSET(SOFTWRAP)) {
2255
	filestruct *line = openfile->edittop;
2256
2257
	openfile->current_y = 0;

2258
	while (line != NULL && line != openfile->current) {
2259
	    openfile->current_y += strlenpt(line->data) / editwincols + 1;
2260
2261
	    line = line->next;
	}
2262
	openfile->current_y += xpt / editwincols;
2263

2264
	if (openfile->current_y < editwinrows)
2265
	    wmove(edit, openfile->current_y, xpt % editwincols + margin);
2266
2267
2268
    } else
#endif
    {
2269
	openfile->current_y = openfile->current->lineno -
2270
				openfile->edittop->lineno;
2271
2272

	if (openfile->current_y < editwinrows)
2273
	    wmove(edit, openfile->current_y, xpt - get_page_start(xpt) + margin);
2274
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2275
}
Chris Allegretta's avatar
Chris Allegretta committed
2276

2277
2278
2279
2280
2281
2282
2283
2284
/* 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. */
2285
void edit_draw(filestruct *fileptr, const char *converted, int
2286
	line, size_t start)
Chris Allegretta's avatar
Chris Allegretta committed
2287
{
2288
#if !defined(NANO_TINY) || !defined(DISABLE_COLOR)
2289
2290
2291
    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. */
2292
    size_t endpos = actual_x(fileptr->data, start + editwincols - 1) + 1;
2293
2294
2295
2296
2297
	/* 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. */
2298
2299
#endif

2300
    assert(openfile != NULL && fileptr != NULL && converted != NULL);
2301
2302
2303
    assert(strlenpt(converted) <= editwincols);

#ifdef ENABLE_LINENUMBERS
2304
2305
2306
    /* 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) {
2307
	wattron(edit, interface_color_pair[LINE_NUMBER]);
2308
2309
2310
2311
	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, " ");
2312
	wattroff(edit, interface_color_pair[LINE_NUMBER]);
2313
2314
    }
#endif
2315

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

2320
#ifdef USING_OLD_NCURSES
2321
2322
2323
    /* 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. */
2324
2325
    if (seen_wide)
	wredrawln(edit, line, 1);
2326
#endif
2327

2328
#ifndef DISABLE_COLOR
2329
2330
2331
    /* If color syntaxes are available and turned on, we need to display
     * them. */
    if (openfile->colorstrings != NULL && !ISSET(NO_COLOR_SYNTAX)) {
2332
	const colortype *varnish = openfile->colorstrings;
2333

2334
2335
2336
2337
	/* If there are multiline regexes, make sure there is a cache. */
	if (openfile->syntax->nmultis > 0)
	    alloc_multidata_if_needed(fileptr);

2338
	for (; varnish != NULL; varnish = varnish->next) {
2339
2340
	    int x_start;
		/* Starting column for mvwaddnstr.  Zero-based. */
2341
	    int paintlen = 0;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2342
2343
		/* Number of chars to paint on this line.  There are
		 * COLS characters on a whole line. */
2344
	    size_t index;
2345
		/* Index in converted where we paint. */
2346
2347
2348
2349
	    regmatch_t startmatch;
		/* Match position for start_regex. */
	    regmatch_t endmatch;
		/* Match position for end_regex. */
2350

2351
	    wattron(edit, varnish->attributes);
2352
2353
2354
	    /* 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. */
2355

2356
2357
	    /* First case: varnish is a single-line expression. */
	    if (varnish->end == NULL) {
2358
2359
2360
		size_t k = 0;

		/* We increment k by rm_eo, to move past the end of the
2361
		 * last match.  Even though two matches may overlap, we
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2362
2363
		 * want to ignore them, so that we can highlight e.g. C
		 * strings correctly. */
2364
2365
2366
		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
2367
2368
2369
		     * unless k is zero.  If regexec() returns
		     * REG_NOMATCH, there are no more matches in the
		     * line. */
2370
		    if (regexec(varnish->start, &fileptr->data[k], 1,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2371
2372
			&startmatch, (k == 0) ? 0 : REG_NOTBOL) ==
			REG_NOMATCH)
2373
			break;
2374
2375
		    /* Translate the match to the beginning of the
		     * line. */
2376
2377
		    startmatch.rm_so += k;
		    startmatch.rm_eo += k;
2378
2379
2380

		    /* Skip over a zero-length regex match. */
		    if (startmatch.rm_so == startmatch.rm_eo)
2381
			startmatch.rm_eo++;
2382
		    else if (startmatch.rm_so < endpos &&
2383
			startmatch.rm_eo > startpos) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2384
2385
			x_start = (startmatch.rm_so <= startpos) ? 0 :
				strnlenpt(fileptr->data,
2386
				startmatch.rm_so) - start;
2387

2388
2389
2390
			index = actual_x(converted, x_start);

			paintlen = actual_x(converted + index,
2391
2392
				strnlenpt(fileptr->data,
				startmatch.rm_eo) - start - x_start);
2393
2394
2395

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

2396
			mvwaddnstr(edit, line, x_start + margin, converted +
2397
				index, paintlen);
2398
		    }
2399
		    k = startmatch.rm_eo;
Chris Allegretta's avatar
Chris Allegretta committed
2400
		}
2401
	    } else {	/* Second case: varnish is a multiline expression. */
2402
		const filestruct *start_line = fileptr->prev;
2403
		    /* The first line before fileptr that matches 'start'. */
2404
		size_t start_col;
2405
		    /* Where the match starts in that line. */
2406
		const filestruct *end_line;
2407
		    /* The line that matches 'end'. */
2408

2409
		/* First see if the multidata was maybe already calculated. */
2410
		if (fileptr->multidata[varnish->id] == CNONE)
2411
		    goto tail_of_loop;
2412
		else if (fileptr->multidata[varnish->id] == CWHOLELINE) {
2413
		    mvwaddnstr(edit, line, margin, converted, -1);
2414
		    goto tail_of_loop;
2415
2416
		} else if (fileptr->multidata[varnish->id] == CBEGINBEFORE) {
		    regexec(varnish->end, fileptr->data, 1, &endmatch, 0);
2417
2418
		    /* If the coloured part is scrolled off, skip it. */
		    if (endmatch.rm_eo <= startpos)
2419
			goto tail_of_loop;
2420
2421
		    paintlen = actual_x(converted, strnlenpt(fileptr->data,
			endmatch.rm_eo) - start);
2422
		    mvwaddnstr(edit, line, margin, converted, paintlen);
2423
		    goto tail_of_loop;
2424
		} if (fileptr->multidata[varnish->id] == -1)
2425
		    /* Assume this until proven otherwise below. */
2426
		    fileptr->multidata[varnish->id] = CNONE;
2427

2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
		/* 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. */

2438
		while (start_line != NULL && regexec(varnish->start,
2439
2440
2441
			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. */
2442
		    if (regexec(varnish->end, start_line->data, 0, NULL, 0) == 0)
2443
2444
2445
			goto step_two;
		    start_line = start_line->prev;
		}
2446

2447
2448
2449
2450
		/* If no start was found, skip to the next step. */
		if (start_line == NULL)
		    goto step_two;

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

2458
		/* Skip over a zero-length regex match. */
2459
		if (startmatch.rm_so == startmatch.rm_eo)
2460
2461
		    goto tail_of_loop;

2462
2463
2464
2465
2466
2467
2468
		/* 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;
2469
		    if (regexec(varnish->end, start_line->data +
2470
2471
2472
				start_col + startmatch.rm_eo, 0, NULL,
				(start_col + startmatch.rm_eo == 0) ?
				0 : REG_NOTBOL) == REG_NOMATCH)
2473
2474
2475
			/* No end found after this start. */
			break;
		    start_col++;
2476
2477
		    if (regexec(varnish->start, start_line->data + start_col,
				1, &startmatch, REG_NOTBOL) == REG_NOMATCH)
2478
2479
2480
2481
2482
2483
2484
2485
2486
			/* 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;
2487
		while (end_line != NULL && regexec(varnish->end,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2488
			end_line->data, 1, &endmatch, 0) == REG_NOMATCH)
2489
		    end_line = end_line->next;
2490

2491
2492
2493
2494
		/* 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) {
2495
		    fileptr->multidata[varnish->id] = CBEGINBEFORE;
2496
2497
		    goto step_two;
		}
2498

2499
2500
2501
2502
2503
2504
2505
		/* 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;
2506
		    fileptr->multidata[varnish->id] = CWHOLELINE;
2507
#ifdef DEBUG
2508
    fprintf(stderr, "  Marking for id %i  line %i as CWHOLELINE\n", varnish->id, line);
2509
#endif
2510
2511
2512
		} else {
		    paintlen = actual_x(converted, strnlenpt(fileptr->data,
						endmatch.rm_eo) - start);
2513
		    fileptr->multidata[varnish->id] = CBEGINBEFORE;
2514
#ifdef DEBUG
2515
    fprintf(stderr, "  Marking for id %i  line %i as CBEGINBEFORE\n", varnish->id, line);
2516
#endif
2517
		}
2518
		mvwaddnstr(edit, line, margin, converted, paintlen);
2519
2520
2521
2522
		/* If the whole line has been painted, don't bother looking
		 * for any more starts. */
		if (paintlen < 0)
		    goto tail_of_loop;
2523
  step_two:
2524
2525
2526
2527
2528
		/* 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) {
2529
		    if (regexec(varnish->start, fileptr->data + start_col,
2530
2531
				1, &startmatch, (start_col == 0) ?
				0 : REG_NOTBOL) == REG_NOMATCH ||
2532
				start_col + startmatch.rm_so >= endpos)
2533
2534
			/* No more starts on this line. */
			break;
2535

2536
2537
2538
2539
2540
2541
2542
		    /* 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,
2543
				startmatch.rm_so) - start;
2544

2545
		    index = actual_x(converted, x_start);
2546

2547
		    if (regexec(varnish->end, fileptr->data +
2548
				startmatch.rm_eo, 1, &endmatch,
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
				(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 &&
2559
				endmatch.rm_eo > startmatch.rm_so) {
2560
			    paintlen = actual_x(converted + index,
2561
					strnlenpt(fileptr->data,
2562
					endmatch.rm_eo) - start - x_start);
2563

2564
			    assert(0 <= x_start && x_start < editwincols);
2565

2566
			    mvwaddnstr(edit, line, x_start + margin,
2567
					converted + index, paintlen);
2568
			    if (paintlen > 0) {
2569
				fileptr->multidata[varnish->id] = CSTARTENDHERE;
2570
#ifdef DEBUG
2571
    fprintf(stderr, "  Marking for id %i  line %i as CSTARTENDHERE\n", varnish->id, line);
2572
#endif
2573
			    }
2574
2575
			}
			start_col = endmatch.rm_eo;
2576
2577
2578
			/* Skip over a zero-length match. */
			if (endmatch.rm_so == endmatch.rm_eo)
			    start_col += 1;
2579
2580
2581
2582
		    } else {
			/* There is no end on this line.  But we haven't yet
			 * looked for one on later lines. */
			end_line = fileptr->next;
2583

2584
			while (end_line != NULL &&
2585
				regexec(varnish->end, end_line->data,
2586
				0, NULL, 0) == REG_NOMATCH)
2587
			    end_line = end_line->next;
2588

2589
2590
2591
			/* If there is no end, we're done on this line. */
			if (end_line == NULL)
			    break;
2592

2593
			assert(0 <= x_start && x_start < editwincols);
2594
2595

			/* Paint the rest of the line. */
2596
			mvwaddnstr(edit, line, x_start + margin, converted + index, -1);
2597
			fileptr->multidata[varnish->id] = CENDAFTER;
2598
#ifdef DEBUG
2599
    fprintf(stderr, "  Marking for id %i  line %i as CENDAFTER\n", varnish->id, line);
2600
#endif
2601
2602
2603
			/* We've painted to the end of the line, so don't
			 * bother checking for any more starts. */
			break;
2604
		    }
2605
2606
		}
	    }
2607
  tail_of_loop:
2608
	    wattroff(edit, varnish->attributes);
2609
	}
2610
    }
2611
#endif /* !DISABLE_COLOR */
2612

2613
#ifndef NANO_TINY
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
    /* 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. */
2625
	int x_start;
2626
	    /* The starting column for mvwaddnstr().  Zero-based. */
2627
	int paintlen;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2628
2629
	    /* Number of characters to paint on this line.  There are
	     * COLS characters on a whole line. */
2630
	size_t index;
2631
	    /* Index in converted where we paint. */
2632

2633
	mark_order(&top, &top_x, &bot, &bot_x, NULL);
2634
2635
2636
2637
2638

	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
2639

2640
	/* Only paint if the marked bit of fileptr is on this page. */
2641
2642
	if (top_x < endpos && bot_x > startpos) {
	    assert(startpos <= top_x);
2643
2644
2645
2646

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

2648
2649
2650
2651
2652
	    /* 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. */
2653
2654
2655
	    if (bot_x >= endpos)
		paintlen = -1;
	    else
2656
		paintlen = strnlenpt(fileptr->data, bot_x) - (x_start + start);
2657
2658
2659
2660
2661
2662
2663
2664

	    /* 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;
	    }
2665
2666
2667

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

2668
	    index = actual_x(converted, x_start);
2669

2670
2671
2672
	    if (paintlen > 0)
		paintlen = actual_x(converted + index, paintlen);

2673
	    wattron(edit, hilite_attribute);
2674
	    mvwaddnstr(edit, line, x_start + margin, converted + index, paintlen);
2675
	    wattroff(edit, hilite_attribute);
Chris Allegretta's avatar
Chris Allegretta committed
2676
	}
2677
    }
2678
#endif /* !NANO_TINY */
2679
2680
2681
2682
#ifdef ENABLE_LINENUMBERS
    last_drawn_line = fileptr->lineno;
    last_line_y = line;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2683
2684
}

2685
/* Just update one line in the edit buffer.  This is basically a wrapper
2686
 * for edit_draw().  The line will be displayed starting with
2687
 * fileptr->data[index].  Likely arguments are current_x or zero.
2688
 * Returns: Number of additional lines consumed (needed for SOFTWRAP). */
2689
int update_line(filestruct *fileptr, size_t index)
Chris Allegretta's avatar
Chris Allegretta committed
2690
{
2691
    int line = 0;
2692
	/* The line in the edit window that we want to update. */
2693
    int extralinesused = 0;
2694
2695
2696
2697
    char *converted;
	/* fileptr->data converted to have tabs and control characters
	 * expanded. */
    size_t page_start;
Chris Allegretta's avatar
Chris Allegretta committed
2698

2699
    assert(fileptr != NULL);
2700

2701
#ifndef NANO_TINY
2702
    if (ISSET(SOFTWRAP)) {
2703
2704
	filestruct *tmp;

2705
	for (tmp = openfile->edittop; tmp && tmp != fileptr; tmp = tmp->next)
2706
	    line += (strlenpt(tmp->data) / editwincols) + 1;
2707
    } else
2708
#endif
2709
2710
	line = fileptr->lineno - openfile->edittop->lineno;

2711
    if (line < 0 || line >= editwinrows)
Chris Allegretta's avatar
Chris Allegretta committed
2712
	return 1;
2713

2714
    /* First, blank out the line. */
2715
    blank_line(edit, line, 0, COLS);
2716

2717
2718
    /* Next, convert variables that index the line to their equivalent
     * positions in the expanded line. */
2719
#ifndef NANO_TINY
2720
2721
2722
    if (ISSET(SOFTWRAP))
	index = 0;
    else
2723
#endif
2724
	index = strnlenpt(fileptr->data, index);
2725
    page_start = get_page_start(index);
2726

2727
2728
    /* Expand the line, replacing tabs with spaces, and control
     * characters with their displayed forms. */
2729
#ifdef NANO_TINY
2730
    converted = display_string(fileptr->data, page_start, editwincols, TRUE);
2731
#else
2732
    converted = display_string(fileptr->data, page_start, editwincols, !ISSET(SOFTWRAP));
2733
#ifdef DEBUG
2734
    if (ISSET(SOFTWRAP) && strlen(converted) >= editwincols - 2)
2735
	fprintf(stderr, "update_line(): converted(1) line = %s\n", converted);
2736
#endif
2737
#endif /* !NANO_TINY */
2738

2739
    /* Paint the line. */
2740
    edit_draw(fileptr, converted, line, page_start);
2741
    free(converted);
Chris Allegretta's avatar
Chris Allegretta committed
2742

2743
#ifndef NANO_TINY
2744
    if (!ISSET(SOFTWRAP)) {
2745
#endif
2746
	if (page_start > 0)
2747
2748
	    mvwaddch(edit, line, margin, '$');
	if (strlenpt(fileptr->data) > page_start + editwincols)
2749
	    mvwaddch(edit, line, COLS - 1, '$');
2750
#ifndef NANO_TINY
2751
    } else {
2752
	size_t full_length = strlenpt(fileptr->data);
2753
	for (index += editwincols; index <= full_length && line < editwinrows - 1; index += editwincols) {
2754
2755
	    line++;
#ifdef DEBUG
2756
	    fprintf(stderr, "update_line(): softwrap code, moving to %d index %lu\n", line, (unsigned long)index);
2757
#endif
2758
	    blank_line(edit, line, 0, COLS);
2759
2760

	    /* Expand the line, replacing tabs with spaces, and control
2761
	     * characters with their displayed forms. */
2762
	    converted = display_string(fileptr->data, index, editwincols, !ISSET(SOFTWRAP));
2763
#ifdef DEBUG
2764
	    if (ISSET(SOFTWRAP) && strlen(converted) >= editwincols - 2)
2765
2766
		fprintf(stderr, "update_line(): converted(2) line = %s\n", converted);
#endif
2767
2768
2769

	    /* Paint the line. */
	    edit_draw(fileptr, converted, line, index);
2770
	    free(converted);
2771
2772
2773
	    extralinesused++;
	}
    }
2774
#endif /* !NANO_TINY */
2775
    return extralinesused;
Chris Allegretta's avatar
Chris Allegretta committed
2776
2777
}

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

2790
/* When edittop changes, try and figure out how many lines
2791
 * we really have to work with (i.e. set maxrows). */
2792
2793
2794
2795
2796
void compute_maxrows(void)
{
    int n;
    filestruct *foo = openfile->edittop;

2797
2798
2799
2800
2801
    if (!ISSET(SOFTWRAP)) {
	maxrows = editwinrows;
	return;
    }

2802
2803
    maxrows = 0;
    for (n = 0; n < editwinrows && foo; n++) {
2804
	maxrows++;
2805
	n += strlenpt(foo->data) / editwincols;
2806
2807
2808
	foo = foo->next;
    }

2809
2810
2811
    if (n < editwinrows)
	maxrows += editwinrows - n;

2812
#ifdef DEBUG
2813
    fprintf(stderr, "compute_maxrows(): maxrows = %d\n", maxrows);
2814
2815
2816
#endif
}

2817
2818
/* 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
2819
2820
 * scrolling.  direction is the direction to scroll, either UPWARD or
 * DOWNWARD, and nlines is the number of lines to scroll.  We change
2821
2822
 * edittop, and assume that current and current_x are up to date.  We
 * also assume that scrollok(edit) is FALSE. */
2823
void edit_scroll(scroll_dir direction, ssize_t nlines)
2824
{
2825
    ssize_t i;
2826
    filestruct *foo;
2827

2828
    assert(nlines > 0);
2829

2830
2831
2832
    /* Part 1: nlines is the number of lines we're going to scroll the
     * text of the edit window. */

2833
    /* Move the top line of the edit window up or down (depending on the
2834
2835
     * value of direction) nlines lines, or as many lines as we can if
     * there are fewer than nlines lines available. */
2836
    for (i = nlines; i > 0; i--) {
2837
	if (direction == UPWARD) {
2838
	    if (openfile->edittop == openfile->fileage)
2839
		break;
2840
	    openfile->edittop = openfile->edittop->prev;
2841
	} else {
2842
	    if (openfile->edittop == openfile->filebot)
2843
		break;
2844
	    openfile->edittop = openfile->edittop->next;
2845
	}
2846
2847

#ifndef NANO_TINY
2848
	/* Don't over-scroll on long lines. */
2849
	if (ISSET(SOFTWRAP) && direction == UPWARD) {
2850
	    ssize_t len = strlenpt(openfile->edittop->data) / editwincols;
2851
	    i -= len;
2852
	    if (len > 0)
2853
		refresh_needed = TRUE;
2854
	}
2855
#endif
2856
2857
    }

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

2861
2862
2863
2864
    /* Don't bother scrolling zero lines, nor more than the window can hold. */
    if (nlines == 0)
	return;
    if (nlines >= editwinrows)
2865
	refresh_needed = TRUE;
2866

2867
    if (refresh_needed == TRUE)
2868
	return;
2869
2870
2871

    /* Scroll the text of the edit window up or down nlines lines,
     * depending on the value of direction. */
2872
    scrollok(edit, TRUE);
2873
    wscrl(edit, (direction == UPWARD) ? -nlines : nlines);
2874
2875
    scrollok(edit, FALSE);

2876
2877
2878
    /* Part 2: nlines is the number of lines in the scrolled region of
     * the edit window that we need to draw. */

2879
2880
2881
2882
2883
2884
    /* 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
2885

2886
2887
    if (nlines > editwinrows)
	nlines = editwinrows;
2888
2889
2890

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

2893
2894
    /* If we scrolled down, move down to the line before the scrolled
     * region. */
2895
    if (direction == DOWNWARD) {
2896
	for (i = editwinrows - nlines; i > 0 && foo != NULL; i--)
2897
2898
2899
	    foo = foo->next;
    }

2900
2901
2902
2903
2904
2905
    /* 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--) {
2906
2907
	if ((i == nlines && direction == DOWNWARD) || (i == 1 &&
		direction == UPWARD)) {
2908
	    if (need_horizontal_scroll(openfile->placewewant, 0))
2909
2910
2911
2912
		update_line(foo, (foo == openfile->current) ?
			openfile->current_x : 0);
	} else
	    update_line(foo, (foo == openfile->current) ?
2913
		openfile->current_x : 0);
2914
	foo = foo->next;
2915
    }
2916
    compute_maxrows();
2917
2918
2919
}

/* Update any lines between old_current and current that need to be
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2920
 * updated.  Use this if we've moved without changing any text. */
2921
void edit_redraw(filestruct *old_current)
2922
{
2923
2924
2925
2926
    size_t was_pww = openfile->placewewant;

    openfile->placewewant = xplustabs();

2927
2928
    /* If the current line is offscreen, scroll until it's onscreen. */
    if (openfile->current->lineno >= openfile->edittop->lineno + maxrows ||
2929
2930
#ifndef NANO_TINY
		(openfile->current->lineno == openfile->edittop->lineno + maxrows - 1 &&
2931
		ISSET(SOFTWRAP) && strlenpt(openfile->current->data) >= editwincols) ||
2932
#endif
2933
		openfile->current->lineno < openfile->edittop->lineno) {
2934
	adjust_viewport((focusing || !ISSET(SMOOTH_SCROLL)) ? CENTERING : FLOWING);
2935
	refresh_needed = TRUE;
2936
	return;
2937
    }
2938

2939
#ifndef NANO_TINY
2940
2941
2942
    /* If the mark is on, update all lines between old_current and current. */
    if (openfile->mark_set) {
	filestruct *foo = old_current;
2943

2944
	while (foo != openfile->current) {
2945
	    update_line(foo, 0);
2946

2947
2948
2949
	    foo = (foo->lineno > openfile->current->lineno) ?
			foo->prev : foo->next;
	}
2950
2951
2952
2953
2954
2955
    } 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);
2956
2957
2958

    /* Update current if we've changed page, or if it differs from
     * old_current and needs to be horizontally scrolled. */
2959
    if (need_horizontal_scroll(was_pww, openfile->placewewant) ||
2960
			(old_current != openfile->current &&
2961
			get_page_start(openfile->placewewant) > 0))
2962
	update_line(openfile->current, openfile->current_x);
2963
2964
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2965
2966
/* 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
2967
2968
void edit_refresh(void)
{
2969
    filestruct *foo;
2970
    int nlines;
2971

2972
    /* Figure out what maxrows should really be. */
2973
    compute_maxrows();
2974

2975
    /* If the current line is out of view, get it back on screen. */
2976
    if (openfile->current->lineno < openfile->edittop->lineno ||
2977
		openfile->current->lineno >= openfile->edittop->lineno + maxrows) {
2978
#ifdef DEBUG
2979
2980
	fprintf(stderr, "edit_refresh(): line = %ld, edittop %ld + maxrows %d\n",
		(long)openfile->current->lineno, (long)openfile->edittop->lineno, maxrows);
2981
#endif
2982
	adjust_viewport((focusing || !ISSET(SMOOTH_SCROLL)) ? CENTERING : STATIONARY);
2983
    }
Chris Allegretta's avatar
Chris Allegretta committed
2984

2985
2986
    foo = openfile->edittop;

2987
#ifdef DEBUG
2988
    fprintf(stderr, "edit_refresh(): edittop->lineno = %ld\n", (long)openfile->edittop->lineno);
2989
#endif
2990

2991
    for (nlines = 0; nlines < editwinrows && foo != NULL; nlines++) {
2992
	nlines += update_line(foo, (foo == openfile->current) ?
2993
					openfile->current_x : 0);
2994
2995
2996
	foo = foo->next;
    }

2997
    for (; nlines < editwinrows; nlines++)
2998
2999
3000
	blank_line(edit, nlines, 0, COLS);

    reset_cursor();
3001
    curs_set(1);
3002
    wnoutrefresh(edit);
3003
3004

    refresh_needed = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
3005
3006
}

3007
3008
3009
3010
3011
/* 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. */
3012
void adjust_viewport(update_type manner)
Chris Allegretta's avatar
Chris Allegretta committed
3013
{
3014
    int goal = 0;
3015

3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
    /* 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)
3026
	goal = editwinrows / 2;
3027
    else if (manner == FLOWING) {
3028
	if (openfile->current->lineno >= openfile->edittop->lineno) {
3029
	    goal = editwinrows - 1;
3030
3031
#ifndef NANO_TINY
	    if (ISSET(SOFTWRAP))
3032
		goal -= strlenpt(openfile->current->data) / editwincols;
3033
3034
#endif
	}
3035
    } else {
3036
	goal = openfile->current_y;
3037

3038
	/* Limit goal to (editwinrows - 1) lines maximum. */
3039
3040
	if (goal > editwinrows - 1)
	    goal = editwinrows - 1;
Chris Allegretta's avatar
Chris Allegretta committed
3041
    }
3042

3043
3044
3045
3046
    openfile->edittop = openfile->current;

    while (goal > 0 && openfile->edittop->prev != NULL) {
	openfile->edittop = openfile->edittop->prev;
3047
	goal--;
3048
#ifndef NANO_TINY
3049
	if (ISSET(SOFTWRAP)) {
3050
	    goal -= strlenpt(openfile->edittop->data) / editwincols;
3051
3052
3053
	    if (goal < 0)
		openfile->edittop = openfile->edittop->next;
	}
3054
#endif
3055
    }
3056
#ifdef DEBUG
3057
    fprintf(stderr, "adjust_viewport(): setting edittop to lineno %ld\n", (long)openfile->edittop->lineno);
3058
#endif
3059
    compute_maxrows();
Chris Allegretta's avatar
Chris Allegretta committed
3060
3061
}

3062
/* Unconditionally redraw the entire screen. */
3063
void total_redraw(void)
3064
{
3065
3066
3067
3068
3069
3070
#ifdef USE_SLANG
    /* Slang curses emulation brain damage, part 4: Slang doesn't define
     * curscr. */
    SLsmg_touch_screen();
    SLsmg_refresh();
#else
3071
    wrefresh(curscr);
3072
#endif
3073
3074
}

3075
3076
/* Unconditionally redraw the entire screen, and then refresh it using
 * the current file. */
3077
3078
void total_refresh(void)
{
3079
    total_redraw();
3080
    titlebar(NULL);
3081
    edit_refresh();
3082
    bottombars(currmenu);
3083
3084
}

3085
3086
/* Display the main shortcut list on the last two rows of the bottom
 * portion of the window. */
3087
3088
void display_main_list(void)
{
3089
#ifndef DISABLE_COLOR
3090
3091
    if (openfile->syntax &&
		(openfile->syntax->formatter || openfile->syntax->linter))
3092
	set_lint_or_format_shortcuts();
3093
3094
3095
3096
    else
	set_spell_shortcuts();
#endif

3097
    bottombars(MMAIN);
3098
3099
}

3100
/* If constant is TRUE, we display the current cursor position only if
3101
3102
 * suppress_cursorpos is FALSE.  If constant is FALSE, we display the
 * position always.  In any case we reset suppress_cursorpos to FALSE. */
3103
void do_cursorpos(bool constant)
Chris Allegretta's avatar
Chris Allegretta committed
3104
{
3105
    filestruct *f;
3106
    char c;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3107
    size_t i, cur_xpt = xplustabs() + 1;
3108
    size_t cur_lenpt = strlenpt(openfile->current->data) + 1;
3109
    int linepct, colpct, charpct;
Chris Allegretta's avatar
Chris Allegretta committed
3110

3111
    assert(openfile->fileage != NULL && openfile->current != NULL);
3112

3113
    /* Determine the size of the file up to the cursor. */
3114
    f = openfile->current->next;
3115
    c = openfile->current->data[openfile->current_x];
3116
3117

    openfile->current->next = NULL;
3118
    openfile->current->data[openfile->current_x] = '\0';
3119
3120
3121

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

3122
    openfile->current->data[openfile->current_x] = c;
3123
    openfile->current->next = f;
3124

3125
    /* If the position needs to be suppressed, don't suppress it next time. */
3126
    if (suppress_cursorpos && constant) {
3127
	suppress_cursorpos = FALSE;
3128
	return;
3129
    }
Chris Allegretta's avatar
Chris Allegretta committed
3130

3131
    /* Display the current cursor position on the statusbar. */
3132
    linepct = 100 * openfile->current->lineno / openfile->filebot->lineno;
3133
    colpct = 100 * cur_xpt / cur_lenpt;
3134
    charpct = (openfile->totsize == 0) ? 0 : 100 * i / openfile->totsize;
3135

3136
    statusline(HUSH,
3137
	_("line %ld/%ld (%d%%), col %lu/%lu (%d%%), char %lu/%lu (%d%%)"),
3138
	(long)openfile->current->lineno,
3139
	(long)openfile->filebot->lineno, linepct,
3140
	(unsigned long)cur_xpt, (unsigned long)cur_lenpt, colpct,
3141
	(unsigned long)i, (unsigned long)openfile->totsize, charpct);
3142
3143
3144

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

3147
/* Unconditionally display the current cursor position. */
3148
void do_cursorpos_void(void)
3149
{
3150
    do_cursorpos(FALSE);
3151
3152
}

3153
3154
void enable_nodelay(void)
{
3155
3156
    nodelay_mode = TRUE;
    nodelay(edit, TRUE);
3157
3158
3159
3160
}

void disable_nodelay(void)
{
3161
3162
    nodelay_mode = FALSE;
    nodelay(edit, FALSE);
3163
3164
}

3165
3166
/* Highlight the current word being replaced or spell checked.  We
 * expect word to have tabs and control characters expanded. */
3167
void spotlight(bool active, const char *word)
Chris Allegretta's avatar
Chris Allegretta committed
3168
{
3169
    size_t word_len = strlenpt(word), room;
Chris Allegretta's avatar
Chris Allegretta committed
3170

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

3174
    assert(room > 0);
3175

3176
3177
    if (word_len > room)
	room--;
3178

Chris Allegretta's avatar
Chris Allegretta committed
3179
    reset_cursor();
3180
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
3181

3182
    if (active)
3183
	wattron(edit, hilite_attribute);
Chris Allegretta's avatar
Chris Allegretta committed
3184

3185
    /* This is so we can show zero-length matches. */
3186
    if (word_len == 0)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3187
	waddch(edit, ' ');
3188
    else
3189
	waddnstr(edit, word, actual_x(word, room));
3190

3191
    if (word_len > room)
3192
	waddch(edit, '$');
Chris Allegretta's avatar
Chris Allegretta committed
3193

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

3198
#ifndef DISABLE_EXTRA
3199
3200
#define CREDIT_LEN 54
#define XLCREDIT_LEN 9
3201

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

3266
    const char *xlcredits[XLCREDIT_LEN] = {
3267
3268
3269
3270
3271
	N_("The nano text editor"),
	N_("version"),
	N_("Brought to you by:"),
	N_("Special thanks to:"),
	N_("The Free Software Foundation"),
3272
	N_("the many translators and the TP"),
3273
3274
3275
	N_("For ncurses:"),
	N_("and anyone else we forgot..."),
	N_("Thank you for using nano!")
3276
    };
3277

3278
3279
3280
3281
3282
3283
    if (!old_more_space || !old_no_help) {
	SET(MORE_SPACE);
	SET(NO_HELP);
	window_init();
    }

3284
3285
    curs_set(0);
    nodelay(edit, TRUE);
3286

3287
    blank_titlebar();
Chris Allegretta's avatar
Chris Allegretta committed
3288
    blank_edit();
3289
    blank_statusbar();
3290

3291
    wrefresh(topwin);
Chris Allegretta's avatar
Chris Allegretta committed
3292
    wrefresh(edit);
3293
    wrefresh(bottomwin);
3294
    napms(700);
3295

3296
    for (crpos = 0; crpos < CREDIT_LEN + editwinrows / 2; crpos++) {
3297
	if ((kbinput = wgetch(edit)) != ERR)
3298
	    break;
3299

3300
	if (crpos < CREDIT_LEN) {
3301
	    const char *what;
3302
3303
	    size_t start_x;

3304
	    if (credits[crpos] == NULL) {
3305
		assert(0 <= xlpos && xlpos < XLCREDIT_LEN);
3306

3307
		what = _(xlcredits[xlpos]);
3308
		xlpos++;
3309
	    } else
3310
		what = credits[crpos];
3311

3312
	    start_x = COLS / 2 - strlenpt(what) / 2 - 1;
3313
3314
	    mvwaddstr(edit, editwinrows - 1 - (editwinrows % 2),
		start_x, what);
3315
	}
3316

3317
3318
3319
3320
	wrefresh(edit);

	if ((kbinput = wgetch(edit)) != ERR)
	    break;
3321
	napms(700);
3322

3323
	scrollok(edit, TRUE);
3324
	wscrl(edit, 1);
3325
	scrollok(edit, FALSE);
3326
	wrefresh(edit);
3327

3328
	if ((kbinput = wgetch(edit)) != ERR)
3329
	    break;
3330
	napms(700);
3331

3332
	scrollok(edit, TRUE);
3333
	wscrl(edit, 1);
3334
	scrollok(edit, FALSE);
3335
	wrefresh(edit);
3336
3337
    }

3338
3339
3340
    if (kbinput != ERR)
	ungetch(kbinput);

3341
    if (!old_more_space)
3342
	UNSET(MORE_SPACE);
3343
    if (!old_no_help)
3344
	UNSET(NO_HELP);
3345
    window_init();
3346

3347
    nodelay(edit, FALSE);
3348

3349
    total_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
3350
}
3351
#endif /* !DISABLE_EXTRA */