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

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

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

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

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

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

139
140
141
142
143
144
145
146
147
148
149
    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);

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

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

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

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

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

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

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

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

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

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

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

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
225
226
227
    /* 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. */
228
229
230
    key_buffer_len += input_len;
    key_buffer = (int *)nrealloc(key_buffer, key_buffer_len *
	sizeof(int));
231

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

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

242
243
244
/* 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)
245
{
246
    unget_input(&kbinput, 1);
247

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

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

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

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

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

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

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

    return input;
290
291
}

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

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

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

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

310
311
312
    return kbinput;
}

313
314
/* Extract a single keystroke from the input stream.  Translate escape
 * sequences and extended keypad codes into their corresponding values.
315
 * Set meta_key to TRUE when appropriate.  Supported extended keypad values
316
317
318
 * 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. */
319
int parse_kbinput(WINDOW *win)
320
{
321
    static int escapes = 0, byte_digits = 0;
322
    static bool double_esc = FALSE;
323
    int *kbinput, keycode, retval = ERR;
324

325
    meta_key = FALSE;
326
    shift_held = FALSE;
327

328
    /* Read in a character. */
329
330
331
332
333
334
    kbinput = get_input(win, 1);

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

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

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

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

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

348
    if (keycode == ESC_CODE) {
349
350
351
352
353
354
355
	/* 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;
356
357
    }

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

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

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

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

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

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

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

			byte_digits = 0;
			escapes = 0;
450
		    }
451
452
453
454
455
456
457
458
459
460
461
462
463
		} 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;
464
			retval = keycode;
465
		    }
466
		    escapes = 0;
467
468
		}
	    } else if (keycode == '[' && key_buffer_len > 0 &&
469
470
471
			(('A' <= *key_buffer && *key_buffer <= 'D') ||
			('a' <= *key_buffer && *key_buffer <= 'd'))) {
		/* An iTerm2/Eterm/rxvt sequence: ^[ ^[ [ X. */
472
473
474
475
476
		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);
477
478
		meta_key = TRUE;
		escapes = 0;
479
	    }
480
481
	    break;
	case 3:
482
	    if (key_buffer_len == 0)
483
484
485
486
487
488
489
490
491
492
		/* 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));
493
	    escapes = 0;
494
495
	    break;
    }
496

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

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

536
#ifdef __linux__
537
    /* When not running under X, check for the bare arrow keys whether
538
539
540
541
     * Shift/Ctrl/Alt are being held together with them. */
    unsigned char modifiers = 6;

    if (console && ioctl(0, TIOCLINUX, &modifiers) >= 0) {
542
543
544
545
546
#ifndef NANO_TINY
	/* Is Shift being held? */
	if (modifiers & 0x01)
	    shift_held = TRUE;
#endif
547
548
	/* Is Ctrl being held? */
	if (modifiers & 0x04) {
549
	    if (retval == KEY_UP)
550
		return CONTROL_UP;
551
	    else if (retval == KEY_DOWN)
552
		return CONTROL_DOWN;
553
	    else if (retval == KEY_LEFT)
554
		return CONTROL_LEFT;
555
	    else if (retval == KEY_RIGHT)
556
		return CONTROL_RIGHT;
557
	}
558
#ifndef NANO_TINY
559
560
561
	/* Are both Shift and Alt being held? */
	if ((modifiers & 0x09) == 0x09) {
	    if (retval == KEY_UP)
562
		return KEY_PPAGE;
563
	    else if (retval == KEY_DOWN)
564
		return KEY_NPAGE;
565
	    else if (retval == KEY_LEFT)
566
		return KEY_HOME;
567
	    else if (retval == KEY_RIGHT)
568
		return KEY_END;
569
	}
570
#endif
571
    }
572
#endif /* __linux__ */
573

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

697
698
699
    return retval;
}

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

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

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

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

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

1153
    return ERR;
1154
1155
}

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

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

    free(seq);

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

1207
#ifdef DEBUG
1208
1209
    fprintf(stderr, "parse_escape_sequence(): kbinput = %d, seq_len = %lu, retval = %d\n",
		kbinput, (unsigned long)seq_len, retval);
1210
1211
1212
1213
1214
#endif

    return retval;
}

1215
1216
/* Translate a byte sequence: turn a three-digit decimal number (from
 * 000 to 255) into its corresponding byte value. */
1217
int get_byte_kbinput(int kbinput)
1218
{
1219
    static int byte_digits = 0, byte = 0;
1220
    int retval = ERR;
1221

1222
1223
    /* Increment the byte digit counter. */
    byte_digits++;
1224

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

    /* If we have a result, reset the byte digit counter and the byte
     * sequence holder. */
    if (retval != ERR) {
	byte_digits = 0;
1276
	byte = 0;
1277
1278
1279
    }

#ifdef DEBUG
1280
    fprintf(stderr, "get_byte_kbinput(): kbinput = %d, byte_digits = %d, byte = %d, retval = %d\n", kbinput, byte_digits, byte, retval);
1281
1282
1283
1284
1285
#endif

    return retval;
}

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

1299
    return ERR;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1300
1301
}

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

1311
    /* Increment the Unicode digit counter. */
1312
    uni_digits++;
1313

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

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

1364
#ifdef DEBUG
1365
1366
    fprintf(stderr, "get_unicode_kbinput(): kbinput = %d, uni_digits = %d, uni = %ld, retval = %ld\n",
						kbinput, uni_digits, uni, retval);
1367
1368
#endif

1369
1370
1371
1372
    /* If we have an end result, reset the Unicode digit counter. */
    if (retval != ERR)
	uni_digits = 0;

1373
1374
    return retval;
}
1375
#endif /* ENABLE_UTF8 */
1376

1377
1378
1379
1380
1381
1382
/* Translate a control character sequence: turn an ASCII non-control
 * character into its corresponding control character. */
int get_control_kbinput(int kbinput)
{
    int retval;

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

1404
#ifdef DEBUG
1405
    fprintf(stderr, "get_control_kbinput(): kbinput = %d, retval = %d\n", kbinput, retval);
1406
1407
#endif

1408
1409
    return retval;
}
1410

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

1418
1419
1420
1421
    if (output_len == 0)
	return;

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

1423
1424
    for (i = 0; i < output_len; i++)
	input[i] = (int)output[i];
1425

1426
    unget_input(input, output_len);
1427

1428
    free(input);
1429
1430
}

1431
/* Read in a stream of characters verbatim, and return the length of the
1432
1433
1434
1435
 * string in kbinput_len.  Assume nodelay(win) is FALSE. */
int *get_verbatim_kbinput(WINDOW *win, size_t *kbinput_len)
{
    int *retval;
1436

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

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

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

1459
    return retval;
1460
1461
}

1462
1463
1464
1465
/* 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). */
1466
int *parse_verbatim_kbinput(WINDOW *win, size_t *count)
1467
{
1468
    int *kbinput;
1469

1470
    /* Read in the first code. */
1471
1472
    while ((kbinput = get_input(win, 1)) == NULL)
	;
1473

1474
#ifndef NANO_TINY
1475
1476
1477
    /* When the window was resized, abort and return nothing. */
    if (*kbinput == KEY_WINCH) {
	free(kbinput);
1478
	*count = 0;
1479
1480
	return NULL;
    }
1481
#endif
1482

1483
#ifdef ENABLE_UTF8
1484
    if (using_utf8()) {
1485
	/* Check whether the first code is a valid starter digit: 0 or 1. */
1486
	long uni = get_unicode_kbinput(win, *kbinput);
1487

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

	    while (uni == ERR) {
1498
		free(kbinput);
1499
1500
		while ((kbinput = get_input(win, 1)) == NULL)
		    ;
1501
		uni = get_unicode_kbinput(win, *kbinput);
1502
	    }
1503

1504
	    /* Convert the Unicode value to a multibyte sequence. */
1505
	    uni_mb = make_mbchar(uni, &uni_mb_len);
1506

1507
	    seq = (int *)nmalloc(uni_mb_len * sizeof(int));
1508

1509
1510
	    for (i = 0; i < uni_mb_len; i++)
		seq[i] = (unsigned char)uni_mb[i];
1511

1512
	    /* Insert the multibyte sequence into the input buffer. */
1513
	    unget_input(seq, uni_mb_len);
1514

1515
1516
	    free(seq);
	    free(uni_mb);
1517
	}
1518
    } else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1519
#endif /* ENABLE_UTF8 */
1520
	/* Put back the first code. */
1521
	unget_input(kbinput, 1);
1522

1523
1524
    free(kbinput);

1525
    *count = 1;
1526

1527
    /* If this is an iTerm/Eterm/rxvt double escape, take both Escapes. */
1528
1529
1530
1531
1532
    if (key_buffer_len > 3 && *key_buffer == ESC_CODE &&
		key_buffer[1] == ESC_CODE && key_buffer[2] == '[')
	*count = 2;

    return get_input(NULL, *count);
1533
1534
}

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

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

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

1563
    /* Save the screen coordinates where the mouse event took place. */
1564
    *mouse_x = mevent.x - margin;
1565
    *mouse_y = mevent.y;
1566

1567
1568
    in_bottomwin = wenclose(bottomwin, *mouse_y, *mouse_x);

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

1585
1586
1587
1588
1589
1590
1591
1592
1593
	    /* 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. */
1594
		*mouse_x = mevent.x - margin;
1595
1596
1597
1598
		*mouse_y = mevent.y;

		return 0;
	    }
1599

1600
	    /* Determine how many shortcuts are being shown. */
1601
	    number = length_of_list(currmenu);
1602

1603
1604
	    if (number > MAIN_VISIBLE)
		number = MAIN_VISIBLE;
1605

1606
1607
1608
	    /* 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. */
1609
	    if (number < 2)
1610
1611
		i = COLS / (MAIN_VISIBLE / 2);
	    else
1612
		i = COLS / ((number / 2) + (number % 2));
1613

1614
1615
	    /* Calculate the one-based index in the shortcut list. */
	    j = (*mouse_x / i) * 2 + *mouse_y;
1616

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

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

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

1662
1663
1664
1665
	if (in_bottomwin)
	    /* Translate the mouse event coordinates so that they're
	     * relative to bottomwin. */
	    wmouse_trafo(bottomwin, mouse_y, mouse_x, FALSE);
1666

1667
	if (in_edit || (in_bottomwin && *mouse_y == 0)) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1668
1669
	    int i;

1670
1671
1672
1673
1674
	    /* 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) ?
1675
				KEY_PPAGE : KEY_NPAGE, FALSE);
1676
1677
1678
1679
1680
1681
1682

	    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;
1683
1684
    }
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1685
1686
1687

    /* Ignore all other mouse events. */
    return 2;
1688
}
1689
1690
#endif /* !DISABLE_MOUSE */

1691
1692
1693
1694
/* 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. */
1695
const sc *get_shortcut(int *kbinput)
1696
{
1697
    sc *s;
1698

1699
#ifdef DEBUG
1700
1701
    fprintf(stderr, "get_shortcut(): kbinput = %d, meta_key = %s -- ",
				*kbinput, meta_key ? "TRUE" : "FALSE");
1702
1703
#endif

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

    return NULL;
}

1721
1722
1723
1724
1725
/* 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
1726

1727
1728
1729
1730
    for (; n > 0; n--)
	waddch(win, ' ');
}

1731
/* Blank the first line of the top portion of the window. */
1732
void blank_titlebar(void)
Chris Allegretta's avatar
Chris Allegretta committed
1733
{
1734
    blank_line(topwin, 0, 0, COLS);
1735
1736
}

1737
/* Blank all the lines of the middle portion of the window, i.e. the
1738
 * edit window. */
Chris Allegretta's avatar
Chris Allegretta committed
1739
1740
void blank_edit(void)
{
1741
    int i;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1742

1743
    for (i = 0; i < editwinrows; i++)
1744
	blank_line(edit, i, 0, COLS);
Chris Allegretta's avatar
Chris Allegretta committed
1745
1746
}

1747
/* Blank the first line of the bottom portion of the window. */
Chris Allegretta's avatar
Chris Allegretta committed
1748
1749
void blank_statusbar(void)
{
1750
    blank_line(bottomwin, 0, 0, COLS);
Chris Allegretta's avatar
Chris Allegretta committed
1751
1752
}

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

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

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

1789
1790
/* Convert buf into a string that can be displayed on screen.  The
 * caller wants to display buf starting with column start_col, and
1791
1792
 * extending for at most span columns.  start_col is zero-based.  span
 * is one-based, so span == 0 means you get "" returned.  The returned
1793
 * string is dynamically allocated, and should be freed.  If isdata is
1794
1795
 * TRUE, the caller might put "$" at the beginning or end of the line if
 * it's too long. */
1796
char *display_string(const char *buf, size_t start_col, size_t span,
1797
	bool isdata)
1798
1799
{
    size_t start_index;
1800
	/* Index in buf of the first character shown. */
1801
    size_t column;
1802
	/* Screen column that start_index corresponds to. */
1803
1804
1805
1806
    char *converted;
	/* The string we return. */
    size_t index;
	/* Current position in converted. */
1807

1808
1809
    /* If this is data, make room for the "$" at the end of the line. */
    if (isdata && !ISSET(SOFTWRAP) && strlenpt(buf) > start_col + span)
1810
	span--;
1811

1812
    if (span == 0)
1813
1814
1815
1816
	return mallocstrcpy(NULL, "");

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

1818
    assert(column <= start_col);
1819

1820
1821
    /* Allocate enough space to hold the entire converted buffer. */
    converted = charalloc(strlen(buf) * (mb_cur_max() + tabsize) + 1);
1822

1823
    index = 0;
1824
#ifdef USING_OLD_NCURSES
1825
    seen_wide = FALSE;
1826
#endif
1827
    buf += start_index;
1828

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

1847
	    converted[index++] = ' ';
1848
	    start_col++;
1849

1850
	    buf += parse_mbchar(buf, NULL, NULL);
1851
	}
1852
#endif
1853
1854
    }

1855
    while (*buf != '\0') {
1856
	int charlength, charwidth = 1;
1857

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

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

1893
	charlength = length_of_char(buf, &charwidth);
1894

1895
	/* If buf contains a control character, represent it. */
1896
	if (is_cntrl_mbchar(buf)) {
1897
	    converted[index++] = '^';
1898
	    converted[index++] = control_mbrep(buf, isdata);
1899
	    start_col += 2;
1900
1901
1902
	    buf += charlength;
	    continue;
	}
1903

1904
1905
1906
1907
	/* If buf contains a valid non-control character, simply copy it. */
	if (charlength > 0) {
	    for (; charlength > 0; charlength--)
		converted[index++] = *(buf++);
1908

1909
	    start_col += charwidth;
1910
#ifdef USING_OLD_NCURSES
1911
	    if (charwidth > 1)
1912
		seen_wide = TRUE;
1913
#endif
1914
	    continue;
1915
1916
	}

1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
	/* 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;
1928
1929
    }

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1930
    /* Null-terminate converted. */
1931
    converted[index] = '\0';
1932

1933
1934
    /* Make sure converted takes up no more than span columns. */
    index = actual_x(converted, span);
1935
    null_at(&converted, index);
1936

1937
    return converted;
1938
1939
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1940
1941
1942
1943
1944
1945
/* 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. */
1946
void titlebar(const char *path)
Chris Allegretta's avatar
Chris Allegretta committed
1947
{
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
    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 "". */
1958
1959
    char *caption;
	/* The presentable form of the pathname. */
1960

1961
1962
1963
1964
    /* If the screen is too small, there is no titlebar. */
    if (topwin == NULL)
	return;

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

1967
    wattron(topwin, interface_color_pair[TITLE_BAR]);
1968

1969
    blank_titlebar();
Chris Allegretta's avatar
Chris Allegretta committed
1970

1971
1972
1973
    /* 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
1974

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

1989
1990
1991
1992
	if (openfile->modified)
	    state = _("Modified");
	else if (ISSET(VIEW_MODE))
	    state = _("View");
1993

1994
1995
	pluglen = strlenpt(_("Modified")) + 1;
    }
1996

1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
    /* 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;
2007
2008
    }

2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
    /* 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;
2021
2022
2023
	}
    }

2024
2025
2026
2027
    /* If we have side spaces left, center the path name. */
    if (verlen > 0)
	offset = verlen + (COLS - (verlen + pluglen + statelen) -
					(prefixlen + pathlen)) / 2;
2028

2029
2030
2031
2032
2033
2034
2035
2036
2037
    /* 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. */
2038
2039
2040
2041
2042
    if (pathlen + pluglen + statelen <= COLS) {
	caption = display_string(path, 0, pathlen, FALSE);
	waddstr(topwin, caption);
	free(caption);
    } else if (5 + statelen <= COLS) {
2043
	waddstr(topwin, "...");
2044
	caption = display_string(path, 3 + pathlen - COLS + statelen,
2045
					COLS - statelen, FALSE);
2046
2047
	waddstr(topwin, caption);
	free(caption);
2048
    }
2049

2050
2051
2052
2053
2054
2055
    /* 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));

2056
    wattroff(topwin, interface_color_pair[TITLE_BAR]);
2057

2058
    wnoutrefresh(topwin);
Chris Allegretta's avatar
Chris Allegretta committed
2059
    reset_cursor();
2060
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
2061
2062
}

2063
2064
2065
2066
2067
2068
/* Display a normal message on the statusbar, quietly. */
void statusbar(const char *msg)
{
    statusline(HUSH, msg);
}

2069
2070
2071
2072
2073
2074
2075
2076
2077
/* Warn the user on the statusbar and pause for a moment, so that the
 * message can be noticed and read. */
void warn_and_shortly_pause(const char *msg)
{
    statusbar(msg);
    beep();
    napms(1800);
}

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

    UNSET(WHITESPACE_DISPLAY);
2091
#endif
2092
2093
2094
2095
2096

    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(). */
2097
    if (isendwin()) {
2098
2099
2100
2101
2102
	vfprintf(stderr, msg, ap);
	va_end(ap);
	return;
    }

2103
2104
2105
2106
2107
2108
2109
    /* 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)
2110
2111
	napms(1200);

2112
    if (importance == ALERT)
2113
	beep();
2114
2115

    lastmessage = importance;
2116

2117
2118
2119
    /* Turn the cursor off while fiddling in the statusbar. */
    curs_set(0);

2120
2121
    blank_statusbar();

2122
2123
2124
    /* Construct the message out of all the arguments. */
    compound = charalloc(mb_cur_max() * (COLS + 1));
    vsnprintf(compound, mb_cur_max() * (COLS + 1), msg, ap);
2125
    va_end(ap);
2126
2127
    message = display_string(compound, 0, COLS, FALSE);
    free(compound);
2128

2129
    start_x = (COLS - strlenpt(message)) / 2;
2130
    bracketed = (start_x > 1);
2131

2132
    wmove(bottomwin, 0, (bracketed ? start_x - 2 : start_x));
2133
    wattron(bottomwin, interface_color_pair[STATUS_BAR]);
2134
2135
    if (bracketed)
	waddstr(bottomwin, "[ ");
2136
2137
    waddstr(bottomwin, message);
    free(message);
2138
2139
    if (bracketed)
	waddstr(bottomwin, " ]");
2140
    wattroff(bottomwin, interface_color_pair[STATUS_BAR]);
2141

2142
    /* Push the message to the screen straightaway. */
2143
    wnoutrefresh(bottomwin);
2144
    doupdate();
2145

2146
    suppress_cursorpos = TRUE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2147

2148
#ifndef NANO_TINY
2149
2150
2151
2152
2153
    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. */
2154
2155
2156
    if (ISSET(QUICK_BLANK))
	statusblank = 1;
    else
2157
#endif
2158
	statusblank = 26;
2159
2160
}

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

2169
2170
2171
    /* Set the global variable to the given menu. */
    currmenu = menu;

2172
    if (ISSET(NO_HELP) || LINES < 5)
Chris Allegretta's avatar
Chris Allegretta committed
2173
2174
	return;

2175
    /* Determine how many shortcuts there are to show. */
2176
    number = length_of_list(menu);
2177

2178
2179
    if (number > MAIN_VISIBLE)
	number = MAIN_VISIBLE;
2180

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

2184
    /* If there is no room, don't print anything. */
2185
    if (itemwidth == 0)
2186
2187
	return;

2188
    blank_bottombars();
2189

2190
#ifdef DEBUG
2191
    fprintf(stderr, "In bottombars, number of items == \"%d\"\n", (int) number);
2192
#endif
2193

2194
    for (f = allfuncs, i = 0; i < number && f != NULL; f = f->next) {
2195
#ifdef DEBUG
2196
	fprintf(stderr, "Checking menu items....");
2197
#endif
2198
	if ((f->menus & menu) == 0)
2199
	    continue;
2200

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

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

2220
2221
    /* Defeat a VTE bug by moving the cursor and forcing a screen update. */
    wmove(bottomwin, 0, 0);
2222
    wnoutrefresh(bottomwin);
2223
2224
    doupdate();

2225
    reset_cursor();
2226
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
2227
2228
}

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

2238
    wattron(bottomwin, interface_color_pair[KEY_COMBO]);
2239
    waddnstr(bottomwin, keystroke, actual_x(keystroke, length));
2240
    wattroff(bottomwin, interface_color_pair[KEY_COMBO]);
2241

2242
    length -= strlenpt(keystroke) + 1;
2243

2244
    if (length > 0) {
2245
	waddch(bottomwin, ' ');
2246
	wattron(bottomwin, interface_color_pair[FUNCTION_TAG]);
2247
	waddnstr(bottomwin, desc, actual_x(desc, length));
2248
	wattroff(bottomwin, interface_color_pair[FUNCTION_TAG]);
Chris Allegretta's avatar
Chris Allegretta committed
2249
2250
2251
    }
}

2252
2253
/* 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
2254
2255
void reset_cursor(void)
{
2256
    size_t xpt = xplustabs();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2257

2258
#ifndef NANO_TINY
2259
    if (ISSET(SOFTWRAP)) {
2260
	filestruct *line = openfile->edittop;
2261
2262
	openfile->current_y = 0;

2263
	while (line != NULL && line != openfile->current) {
2264
	    openfile->current_y += strlenpt(line->data) / editwincols + 1;
2265
2266
	    line = line->next;
	}
2267
	openfile->current_y += xpt / editwincols;
2268

2269
	if (openfile->current_y < editwinrows)
2270
	    wmove(edit, openfile->current_y, xpt % editwincols + margin);
2271
2272
2273
    } else
#endif
    {
2274
	openfile->current_y = openfile->current->lineno -
2275
				openfile->edittop->lineno;
2276
2277

	if (openfile->current_y < editwinrows)
2278
	    wmove(edit, openfile->current_y, xpt - get_page_start(xpt) + margin);
2279
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2280
}
Chris Allegretta's avatar
Chris Allegretta committed
2281

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

2305
    assert(openfile != NULL && fileptr != NULL && converted != NULL);
2306
2307
2308
    assert(strlenpt(converted) <= editwincols);

#ifdef ENABLE_LINENUMBERS
2309
2310
2311
    /* 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) {
2312
	wattron(edit, interface_color_pair[LINE_NUMBER]);
2313
#ifndef NANO_TINY
2314
	if (ISSET(SOFTWRAP) && startpos >= editwincols)
2315
	    mvwprintw(edit, line, 0, "%*s", margin - 1, " ");
2316
2317
2318
	else
#endif
	    mvwprintw(edit, line, 0, "%*ld", margin - 1, (long)fileptr->lineno);
2319
	wattroff(edit, interface_color_pair[LINE_NUMBER]);
2320
2321
    }
#endif
2322

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

2327
#ifdef USING_OLD_NCURSES
2328
2329
2330
    /* 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. */
2331
2332
    if (seen_wide)
	wredrawln(edit, line, 1);
2333
#endif
2334

2335
#ifndef DISABLE_COLOR
2336
2337
2338
    /* If color syntaxes are available and turned on, we need to display
     * them. */
    if (openfile->colorstrings != NULL && !ISSET(NO_COLOR_SYNTAX)) {
2339
	const colortype *varnish = openfile->colorstrings;
2340

2341
2342
2343
2344
	/* If there are multiline regexes, make sure there is a cache. */
	if (openfile->syntax->nmultis > 0)
	    alloc_multidata_if_needed(fileptr);

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

2358
	    wattron(edit, varnish->attributes);
2359
2360
2361
	    /* 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. */
2362

2363
2364
	    /* First case: varnish is a single-line expression. */
	    if (varnish->end == NULL) {
2365
2366
2367
		size_t k = 0;

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

		    /* Skip over a zero-length regex match. */
		    if (startmatch.rm_so == startmatch.rm_eo)
2388
			startmatch.rm_eo++;
2389
		    else if (startmatch.rm_so < endpos &&
2390
			startmatch.rm_eo > startpos) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2391
2392
			x_start = (startmatch.rm_so <= startpos) ? 0 :
				strnlenpt(fileptr->data,
2393
				startmatch.rm_so) - start;
2394

2395
2396
2397
			index = actual_x(converted, x_start);

			paintlen = actual_x(converted + index,
2398
2399
				strnlenpt(fileptr->data,
				startmatch.rm_eo) - start - x_start);
2400
2401
2402

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

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

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

2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
		/* 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. */

2445
		while (start_line != NULL && regexec(varnish->start,
2446
2447
2448
			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. */
2449
		    if (regexec(varnish->end, start_line->data, 0, NULL, 0) == 0)
2450
2451
2452
			goto step_two;
		    start_line = start_line->prev;
		}
2453

2454
2455
2456
2457
		/* If no start was found, skip to the next step. */
		if (start_line == NULL)
		    goto step_two;

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

2465
		/* Skip over a zero-length regex match. */
2466
		if (startmatch.rm_so == startmatch.rm_eo)
2467
2468
		    goto tail_of_loop;

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

2498
2499
2500
2501
		/* 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) {
2502
		    fileptr->multidata[varnish->id] = CBEGINBEFORE;
2503
2504
		    goto step_two;
		}
2505

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

2543
2544
2545
2546
2547
2548
2549
		    /* 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,
2550
				startmatch.rm_so) - start;
2551

2552
		    index = actual_x(converted, x_start);
2553

2554
		    if (regexec(varnish->end, fileptr->data +
2555
				startmatch.rm_eo, 1, &endmatch,
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
				(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 &&
2566
				endmatch.rm_eo > startmatch.rm_so) {
2567
			    paintlen = actual_x(converted + index,
2568
					strnlenpt(fileptr->data,
2569
					endmatch.rm_eo) - start - x_start);
2570

2571
			    assert(0 <= x_start && x_start < editwincols);
2572

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

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

2596
2597
2598
			/* If there is no end, we're done on this line. */
			if (end_line == NULL)
			    break;
2599

2600
			assert(0 <= x_start && x_start < editwincols);
2601
2602

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

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

2640
	mark_order(&top, &top_x, &bot, &bot_x, NULL);
2641
2642
2643
2644
2645

	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
2646

2647
	/* Only paint if the marked bit of fileptr is on this page. */
2648
2649
	if (top_x < endpos && bot_x > startpos) {
	    assert(startpos <= top_x);
2650
2651
2652
2653

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

2655
2656
2657
2658
2659
	    /* 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. */
2660
2661
2662
	    if (bot_x >= endpos)
		paintlen = -1;
	    else
2663
		paintlen = strnlenpt(fileptr->data, bot_x) - (x_start + start);
2664
2665
2666
2667
2668
2669
2670
2671

	    /* 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;
	    }
2672
2673
2674

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

2675
	    index = actual_x(converted, x_start);
2676

2677
2678
2679
	    if (paintlen > 0)
		paintlen = actual_x(converted + index, paintlen);

2680
	    wattron(edit, hilite_attribute);
2681
	    mvwaddnstr(edit, line, x_start + margin, converted + index, paintlen);
2682
	    wattroff(edit, hilite_attribute);
Chris Allegretta's avatar
Chris Allegretta committed
2683
	}
2684
    }
2685
#endif /* !NANO_TINY */
Chris Allegretta's avatar
Chris Allegretta committed
2686
2687
}

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

2702
    assert(fileptr != NULL);
2703

2704
#ifndef NANO_TINY
2705
    if (ISSET(SOFTWRAP)) {
2706
2707
	filestruct *tmp;

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

2714
    if (line < 0 || line >= editwinrows)
Chris Allegretta's avatar
Chris Allegretta committed
2715
	return 1;
2716

2717
    /* First, blank out the line. */
2718
    blank_line(edit, line, 0, COLS);
2719

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

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

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

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

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

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

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

2789
2790
/* When edittop changes, try and figure out how many lines we really
 * have to work with, accounting for softwrap mode. */
2791
2792
void compute_maxrows(void)
{
2793
2794
#ifndef NANO_TINY
    if (ISSET(SOFTWRAP)) {
2795
2796
	int screenrow;
	filestruct *line = openfile->edittop;
2797
2798

	maxrows = 0;
2799
2800
2801
2802

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

2806
2807
	if (screenrow < editwinrows)
	    maxrows += editwinrows - screenrow;
2808

2809
#ifdef DEBUG
2810
	fprintf(stderr, "recomputed: maxrows = %d\n", maxrows);
2811
#endif
2812
2813
2814
    } else
#endif /* !NANO_TINY */
	maxrows = editwinrows;
2815
2816
}

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
	fprintf(stderr, "edit-refresh: line = %ld, edittop = %ld and maxrows = %d\n",
2980
		(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: now edittop = %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
    wnoutrefresh(edit);
3002
3003

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

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

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

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

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

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

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

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

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

3096
    bottombars(MMAIN);
3097
3098
}

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

3109
    assert(openfile->fileage != NULL && openfile->current != NULL);
3110

3111
    /* Determine the size of the file up to the cursor. */
3112
    saved_byte = openfile->current->data[openfile->current_x];
3113
    openfile->current->data[openfile->current_x] = '\0';
3114

3115
    sum = get_totsize(openfile->fileage, openfile->current);
3116

3117
    openfile->current->data[openfile->current_x] = saved_byte;
3118
3119
3120

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

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

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

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

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

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

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

void disable_nodelay(void)
{
3159
3160
    nodelay_mode = FALSE;
    nodelay(edit, FALSE);
3161
3162
}

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

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

3172
    assert(room > 0);
3173

3174
3175
    if (word_len > room)
	room--;
3176

Chris Allegretta's avatar
Chris Allegretta committed
3177
    reset_cursor();
Chris Allegretta's avatar
Chris Allegretta committed
3178

3179
    if (active)
3180
	wattron(edit, hilite_attribute);
Chris Allegretta's avatar
Chris Allegretta committed
3181

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

3188
    if (word_len > room)
3189
	waddch(edit, '$');
Chris Allegretta's avatar
Chris Allegretta committed
3190

3191
    if (active)
3192
	wattroff(edit, hilite_attribute);
3193
3194

    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
3195
3196
}

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

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

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

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

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

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

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

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

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

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

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

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

3316
3317
3318
3319
	wrefresh(edit);

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

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

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

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

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

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

3346
    nodelay(edit, FALSE);
3347

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