winio.c 99.1 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
 *   Copyright (C) 2014, 2015, 2016, 2017 Benno Schulenberg               *
7
 *                                                                        *
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 the_code_for(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 the_code_for(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 the_code_for(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 the_code_for(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
    /* Read in one keycode, or one or two escapes. */
1446
    retval = parse_verbatim_kbinput(win, kbinput_len);
1447

1448
1449
1450
1451
1452
1453
    /* If the code is invalid in the current mode, discard it. */
    if ((*retval == '\n' && as_an_at) || (*retval == '\0' && !as_an_at)) {
	*kbinput_len = 0;
	beep();
    }

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

1465
    return retval;
1466
1467
}

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

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

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

1489
1490
    *count = 1;

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

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

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

1512
	    /* Convert the Unicode value to a multibyte sequence. */
1513
	    multibyte = make_mbchar(unicode, (int *)count);
1514

1515
	    /* Insert the multibyte sequence into the input buffer. */
1516
	    for (i = *count; i > 0 ; i--) {
1517
		onebyte = (unsigned char)multibyte[i - 1];
1518
1519
		unget_input(&onebyte, 1);
	    }
1520

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

1528
1529
    free(kbinput);

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

    return get_input(NULL, *count);
1536
1537
}

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

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

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

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

1570
1571
    in_bottomwin = wenclose(bottomwin, *mouse_y, *mouse_x);

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

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

		return 0;
	    }
1602

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

1606
1607
	    if (number > MAIN_VISIBLE)
		number = MAIN_VISIBLE;
1608

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

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

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

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

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

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

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

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

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

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

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

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

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

    return NULL;
}

1724
1725
/* Move to (x, y) in win, and display a line of n spaces with the
 * current attributes. */
1726
void blank_row(WINDOW *win, int y, int x, int n)
1727
1728
{
    wmove(win, y, x);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1729

1730
1731
1732
1733
    for (; n > 0; n--)
	waddch(win, ' ');
}

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

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

1746
1747
    for (row = 0; row < editwinrows; row++)
	blank_row(edit, row, 0, COLS);
Chris Allegretta's avatar
Chris Allegretta committed
1748
1749
}

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

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

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

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

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

1813
1814
    start_index = actual_x(buf, start_col);
    column = strnlenpt(buf, start_index);
1815

1816
    assert(column <= start_col);
1817

1818
    index = 0;
1819
#ifdef USING_OLD_NCURSES
1820
    seen_wide = FALSE;
1821
#endif
1822
    buf += start_index;
1823

1824
1825
1826
    /* Allocate enough space for converting the relevant part of the line. */
    converted = charalloc(strlen(buf) * (mb_cur_max() + tabsize) + 1);

1827
1828
1829
1830
    /* If the first character starts before the left edge, or would be
     * overwritten by a "$" token, then show spaces instead. */
    if (*buf != '\0' && *buf != '\t' && (column < start_col ||
				(column > 0 && isdata && !ISSET(SOFTWRAP)))) {
1831
	if (is_cntrl_mbchar(buf)) {
1832
	    if (column < start_col) {
1833
		converted[index++] = control_mbrep(buf, isdata);
1834
		start_col++;
1835
		buf += parse_mbchar(buf, NULL, NULL);
1836
	    }
1837
	}
1838
#ifdef ENABLE_UTF8
1839
	else if (using_utf8() && mbwidth(buf) == 2) {
1840
1841
1842
1843
1844
	    if (column >= start_col) {
		converted[index++] = ' ';
		start_col++;
	    }

1845
	    converted[index++] = ' ';
1846
	    start_col++;
1847

1848
	    buf += parse_mbchar(buf, NULL, NULL);
1849
	}
1850
#endif
1851
1852
    }

1853
    while (*buf != '\0' && start_col < beyond) {
1854
	int charlength, charwidth = 1;
1855

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

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

1891
	charlength = length_of_char(buf, &charwidth);
1892

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

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

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

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

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

1931
1932
1933
1934
    /* If there is more text than can be shown, make room for the $. */
    if (*buf != '\0' && isdata && !ISSET(SOFTWRAP))
	span--;

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

1939
    return converted;
1940
1941
}

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

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

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

1969
    wattron(topwin, interface_color_pair[TITLE_BAR]);
1970

1971
    blank_titlebar();
1972
    as_an_at = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
1973

1974
1975
1976
    /* 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
1977

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

1992
1993
1994
1995
	if (openfile->modified)
	    state = _("Modified");
	else if (ISSET(VIEW_MODE))
	    state = _("View");
1996

1997
1998
	pluglen = strlenpt(_("Modified")) + 1;
    }
1999

2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
    /* 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;
2010
2011
    }

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

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

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

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

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

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

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

2072
2073
2074
2075
2076
2077
2078
2079
2080
/* 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);
}

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

    UNSET(WHITESPACE_DISPLAY);
2095
#endif
2096
2097
2098
2099
2100

    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(). */
2101
    if (isendwin()) {
2102
2103
2104
2105
2106
	vfprintf(stderr, msg, ap);
	va_end(ap);
	return;
    }

2107
2108
2109
2110
2111
    /* If there already was an alert message, ignore lesser ones. */
    if ((lastmessage == ALERT && importance != ALERT) ||
		(lastmessage == MILD && importance == HUSH))
	return;

2112
2113
2114
2115
2116
2117
2118
    /* If the ALERT status has been reset, reset the counter. */
    if (lastmessage == HUSH)
	alerts = 0;

    /* Shortly pause after each of the first three alert messages,
     * to give the user time to read them. */
    if (lastmessage == ALERT && alerts < 4)
2119
2120
	napms(1200);

2121
2122
    if (importance == ALERT) {
	if (++alerts > 3)
2123
	    msg = _("Further warnings were suppressed");
2124
	beep();
2125
    }
2126
2127

    lastmessage = importance;
2128

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

2132
2133
    blank_statusbar();

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2200
    blank_bottombars();
2201

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

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

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

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

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

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

2241
2242
/* Write a shortcut key to the help area at the bottom of the window.
 * keystroke is e.g. "^G" and desc is e.g. "Get Help".  We are careful
2243
 * to write at most length characters, even if length is very small and
2244
2245
 * keystroke and desc are long.  Note that waddnstr(,,(size_t)-1) adds
 * the whole string!  We do not bother padding the entry with blanks. */
2246
void onekey(const char *keystroke, const char *desc, int length)
Chris Allegretta's avatar
Chris Allegretta committed
2247
{
2248
2249
    assert(keystroke != NULL && desc != NULL);

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

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

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

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

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

2275
	/* Calculate how many rows the lines from edittop to current use. */
2276
	while (line != NULL && line != openfile->current) {
2277
	    row += strlenpt(line->data) / editwincols + 1;
2278
2279
2280
	    line = line->next;
	}

2281
2282
2283
	/* Add the number of wraps in the current line before the cursor. */
	row += xpt / editwincols;
	col = xpt % editwincols;
2284
2285
2286
    } else
#endif
    {
2287
2288
	row = openfile->current->lineno - openfile->edittop->lineno;
	col = xpt - get_page_start(xpt);
2289
    }
2290
2291
2292
2293
2294

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

    openfile->current_y = row;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2295
}
Chris Allegretta's avatar
Chris Allegretta committed
2296

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

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

#ifdef ENABLE_LINENUMBERS
2322
    /* If line numbering is switched on, put a line number in front of
2323
2324
     * the text -- but only for the parts that are not softwrapped. */
    if (margin > 0) {
2325
	wattron(edit, interface_color_pair[LINE_NUMBER]);
2326
#ifndef NANO_TINY
2327
	if (ISSET(SOFTWRAP) && from_x != 0)
2328
	    mvwprintw(edit, row, 0, "%*s", margin - 1, " ");
2329
2330
	else
#endif
2331
	    mvwprintw(edit, row, 0, "%*ld", margin - 1, (long)fileptr->lineno);
2332
	wattroff(edit, interface_color_pair[LINE_NUMBER]);
2333
2334
    }
#endif
2335

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

2340
#ifdef USING_OLD_NCURSES
2341
2342
2343
    /* 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. */
2344
    if (seen_wide)
2345
	wredrawln(edit, row, 1);
2346
#endif
2347

2348
#ifndef DISABLE_COLOR
2349
    /* If color syntaxes are available and turned on, apply them. */
2350
    if (openfile->colorstrings != NULL && !ISSET(NO_COLOR_SYNTAX)) {
2351
	const colortype *varnish = openfile->colorstrings;
2352

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

2357
	/* Iterate through all the coloring regexes. */
2358
	for (; varnish != NULL; varnish = varnish->next) {
2359
2360
	    size_t index = 0;
		/* Where in the line we currently begin looking for a match. */
2361
	    int start_col;
2362
		/* The starting column of a piece to paint.  Zero-based. */
2363
	    int paintlen = 0;
2364
2365
2366
		/* The number of characters to paint. */
	    const char *thetext;
		/* The place in converted from where painting starts. */
2367
2368
	    regmatch_t match;
		/* The match positions of a single-line regex. */
2369

2370
2371
2372
	    /* 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. */
2373

2374
2375
	    wattron(edit, varnish->attributes);

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

2392
		    /* If the match is of length zero, skip it. */
2393
		    if (match.rm_so == match.rm_eo) {
2394
			index = move_mbright(fileptr->data,
2395
						index + match.rm_eo);
2396
2397
2398
			continue;
		    }

2399
		    /* Translate the match to the beginning of the line. */
2400
2401
2402
		    match.rm_so += index;
		    match.rm_eo += index;
		    index = match.rm_eo;
2403

2404
2405
		    /* If the matching part is not visible, skip it. */
		    if (match.rm_eo <= from_x || match.rm_so >= till_x)
2406
2407
			continue;

2408
2409
2410
		    start_col = (match.rm_so <= from_x) ?
					0 : strnlenpt(fileptr->data,
					match.rm_so) - from_col;
2411

2412
		    thetext = converted + actual_x(converted, start_col);
2413

2414
		    paintlen = actual_x(thetext, strnlenpt(fileptr->data,
2415
					match.rm_eo) - from_col - start_col);
2416

2417
		    mvwaddnstr(edit, row, margin + start_col,
2418
						thetext, paintlen);
Chris Allegretta's avatar
Chris Allegretta committed
2419
		}
2420
2421
2422
2423
		goto tail_of_loop;
	    }

	    /* Second case: varnish is a multiline expression. */
2424
2425
2426
2427
	    const filestruct *start_line = fileptr->prev;
		/* The first line before fileptr that matches 'start'. */
	    const filestruct *end_line = fileptr;
		/* The line that matches 'end'. */
2428
2429
	    regmatch_t startmatch, endmatch;
		/* The match positions of the start and end regexes. */
2430

2431
2432
2433
	    /* Assume nothing gets painted until proven otherwise below. */
	    fileptr->multidata[varnish->id] = CNONE;

2434
2435
2436
2437
	    /* First check the multidata of the preceding line -- it tells
	     * us about the situation so far, and thus what to do here. */
	    if (start_line != NULL && start_line->multidata != NULL) {
		if (start_line->multidata[varnish->id] == CWHOLELINE ||
2438
2439
			start_line->multidata[varnish->id] == CENDAFTER ||
			start_line->multidata[varnish->id] == CWOULDBE)
2440
2441
2442
		    goto seek_an_end;
		if (start_line->multidata[varnish->id] == CNONE ||
			start_line->multidata[varnish->id] == CBEGINBEFORE ||
2443
			start_line->multidata[varnish->id] == CSTARTENDHERE)
2444
2445
2446
		    goto step_two;
	    }

2447
2448
	    /* The preceding line has no precalculated multidata.  So, do
	     * some backtracking to find out what to paint. */
2449

2450
2451
	    /* First step: see if there is a line before current that
	     * matches 'start' and is not complemented by an 'end'. */
2452
2453
	    while (start_line != NULL && regexec(varnish->start,
		    start_line->data, 1, &startmatch, 0) == REG_NOMATCH) {
2454
		/* There is no start on this line; but if there is an end,
2455
2456
		 * there is no need to look for starts on earlier lines. */
		if (regexec(varnish->end, start_line->data, 0, NULL, 0) == 0)
2457
		    goto step_two;
2458
2459
		start_line = start_line->prev;
	    }
2460

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

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

2472
	    /* Is there an uncomplemented start on the found line? */
2473
	    while (TRUE) {
2474
		/* Begin searching for an end after the start match. */
2475
		index += startmatch.rm_eo;
2476
		/* If there is no end after this last start, good. */
2477
2478
		if (regexec(varnish->end, start_line->data + index, 1, &endmatch,
				(index == 0) ? 0 : REG_NOTBOL) == REG_NOMATCH)
2479
		    break;
2480
2481
		/* Begin searching for a new start after the end match. */
		index += endmatch.rm_eo;
2482
2483
2484
		/* If both start and end match are mere anchors, advance. */
		if (startmatch.rm_so == startmatch.rm_eo &&
				endmatch.rm_so == endmatch.rm_eo) {
2485
		    if (start_line->data[index] == '\0')
2486
			break;
2487
		    index = move_mbright(start_line->data, index);
2488
		}
2489
		/* If there is no later start on this line, next step. */
2490
		if (regexec(varnish->start, start_line->data + index,
2491
				1, &startmatch, REG_NOTBOL) == REG_NOMATCH)
2492
		    goto step_two;
2493
2494
2495
	    }
	    /* Indeed, there is a start without an end on that line. */

2496
  seek_an_end:
2497
2498
	    /* We've already checked that there is no end between the start
	     * and the current line.  But is there an end after the start
2499
2500
2501
2502
2503
	     * at all?  We don't paint unterminated starts. */
	    while (end_line != NULL && regexec(varnish->end, end_line->data,
				 1, &endmatch, 0) == REG_NOMATCH)
		end_line = end_line->next;

2504
	    /* If there is no end, there is nothing to paint. */
2505
2506
	    if (end_line == NULL) {
		fileptr->multidata[varnish->id] = CWOULDBE;
2507
		goto tail_of_loop;
2508
	    }
2509

2510
	    /* If the end is on a later line, paint whole line, and be done. */
2511
	    if (end_line != fileptr) {
2512
		mvwaddnstr(edit, row, margin, converted, -1);
2513
2514
		fileptr->multidata[varnish->id] = CWHOLELINE;
		goto tail_of_loop;
2515
2516
2517
2518
	    }

	    /* Only if it is visible, paint the part to be coloured. */
	    if (endmatch.rm_eo > from_x) {
2519
		paintlen = actual_x(converted, strnlenpt(fileptr->data,
2520
						endmatch.rm_eo) - from_col);
2521
		mvwaddnstr(edit, row, margin, converted, paintlen);
2522
2523
	    }
	    fileptr->multidata[varnish->id] = CBEGINBEFORE;
2524

2525
  step_two:
2526
2527
2528
	    /* Second step: look for starts on this line, but begin
	     * looking only after an end match, if there is one. */
	    index = (paintlen == 0) ? 0 : endmatch.rm_eo;
2529

2530
	    while (regexec(varnish->start, fileptr->data + index,
2531
				1, &startmatch, (index == 0) ?
2532
				0 : REG_NOTBOL) == 0) {
2533
2534
2535
2536
		/* Translate the match to be relative to the
		 * beginning of the line. */
		startmatch.rm_so += index;
		startmatch.rm_eo += index;
2537

2538
		start_col = (startmatch.rm_so <= from_x) ?
2539
				0 : strnlenpt(fileptr->data,
2540
				startmatch.rm_so) - from_col;
2541

2542
		thetext = converted + actual_x(converted, start_col);
2543

2544
2545
		if (regexec(varnish->end, fileptr->data + startmatch.rm_eo,
				1, &endmatch, (startmatch.rm_eo == 0) ?
2546
				0 : REG_NOTBOL) == 0) {
2547
2548
2549
2550
		    /* 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;
2551
2552
		    /* Only paint the match if it is visible on screen and
		     * it is more than zero characters long. */
2553
		    if (endmatch.rm_eo > from_x &&
2554
					endmatch.rm_eo > startmatch.rm_so) {
2555
			paintlen = actual_x(thetext, strnlenpt(fileptr->data,
2556
					endmatch.rm_eo) - from_col - start_col);
2557

2558
			mvwaddnstr(edit, row, margin + start_col,
2559
						thetext, paintlen);
2560

2561
			fileptr->multidata[varnish->id] = CSTARTENDHERE;
2562
		    }
2563
		    index = endmatch.rm_eo;
2564
2565
2566
		    /* If both start and end match are anchors, advance. */
		    if (startmatch.rm_so == startmatch.rm_eo &&
				endmatch.rm_so == endmatch.rm_eo) {
2567
			if (fileptr->data[index] == '\0')
2568
			    break;
2569
			index = move_mbright(fileptr->data, index);
2570
2571
		    }
		    continue;
2572
		}
2573

2574
2575
		/* There is no end on this line.  But maybe on later lines? */
		end_line = fileptr->next;
2576

2577
2578
2579
		while (end_line != NULL && regexec(varnish->end, end_line->data,
					0, NULL, 0) == REG_NOMATCH)
		    end_line = end_line->next;
2580

2581
		/* If there is no end, we're done with this regex. */
2582
2583
		if (end_line == NULL) {
		    fileptr->multidata[varnish->id] = CWOULDBE;
2584
		    break;
2585
		}
2586

2587
		/* Paint the rest of the line, and we're done. */
2588
		mvwaddnstr(edit, row, margin + start_col, thetext, -1);
2589
2590
		fileptr->multidata[varnish->id] = CENDAFTER;
		break;
2591
	    }
2592
  tail_of_loop:
2593
	    wattroff(edit, varnish->attributes);
2594
	}
2595
    }
2596
#endif /* !DISABLE_COLOR */
2597

2598
#ifndef NANO_TINY
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
    /* 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. */
2610
	int start_col;
2611
2612
2613
2614
2615
	    /* The column where painting starts.  Zero-based. */
	const char *thetext;
	    /* The place in converted from where painting starts. */
	int paintlen = -1;
	    /* The number of characters to paint.  Negative means "all". */
2616

2617
	mark_order(&top, &top_x, &bot, &bot_x, NULL);
2618

2619
2620
2621
2622
	if (top->lineno < fileptr->lineno || top_x < from_x)
	    top_x = from_x;
	if (bot->lineno > fileptr->lineno || bot_x > till_x)
	    bot_x = till_x;
Chris Allegretta's avatar
Chris Allegretta committed
2623

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

2629
	    thetext = converted + actual_x(converted, start_col);
2630

2631
2632
2633
2634
2635
2636
	    /* If the end of the mark is onscreen, compute how many
	     * characters to paint.  Otherwise, just paint all. */
	    if (bot_x < till_x) {
		size_t end_col = strnlenpt(fileptr->data, bot_x) - from_col;
		paintlen = actual_x(thetext, end_col - start_col);
	    }
2637

2638
	    wattron(edit, hilite_attribute);
2639
	    mvwaddnstr(edit, row, margin + start_col, thetext, paintlen);
2640
	    wattroff(edit, hilite_attribute);
Chris Allegretta's avatar
Chris Allegretta committed
2641
	}
2642
    }
2643
#endif /* !NANO_TINY */
Chris Allegretta's avatar
Chris Allegretta committed
2644
2645
}

2646
2647
2648
2649
2650
/* Redraw the line at fileptr.  The line will be displayed so that the
 * character with the given index is visible -- if necessary, the line
 * will be horizontally scrolled.  In softwrap mode, however, the entire
 * line will be displayed.  Likely values of index are current_x or zero.
 * Return the number of additional rows consumed (when softwrapping). */
2651
int update_line(filestruct *fileptr, size_t index)
Chris Allegretta's avatar
Chris Allegretta committed
2652
{
2653
    int row = 0;
2654
	/* The row in the edit window we will be updating. */
2655
    char *converted;
2656
	/* The data of the line with tabs and control characters expanded. */
2657
2658
    size_t from_col = 0;
	/* From which column a horizontally scrolled line is displayed. */
Chris Allegretta's avatar
Chris Allegretta committed
2659

2660
#ifndef NANO_TINY
2661
    if (ISSET(SOFTWRAP)) {
2662
	filestruct *line = openfile->edittop;
2663

2664
	/* Find out on which screen row the target line should be shown. */
2665
2666
2667
2668
	while (line != fileptr && line != NULL) {
	    row += (strlenpt(line->data) / editwincols) + 1;
	    line = line->next;
	}
2669
    } else
2670
#endif
2671
	row = fileptr->lineno - openfile->edittop->lineno;
2672

2673
    /* If the line is offscreen, don't even try to display it. */
2674
    if (row < 0 || row >= editwinrows)
2675
	return 0;
2676

2677
#ifndef NANO_TINY
2678
    if (ISSET(SOFTWRAP)) {
2679
	size_t full_length = strlenpt(fileptr->data);
2680
	int starting_row = row;
2681

2682
2683
	for (from_col = 0; from_col <= full_length &&
			row < editwinrows; from_col += editwincols) {
2684
	    /* First, blank out the row. */
2685
	    blank_row(edit, row, 0, COLS);
2686
2687

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

2691
	    /* Draw the line. */
2692
	    edit_draw(fileptr, converted, row++, from_col);
2693
	    free(converted);
2694
	}
2695

2696
	return (row - starting_row);
2697
    }
2698
#endif
2699

2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
    /* First, blank out the row. */
    blank_row(edit, row, 0, COLS);

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

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

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

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

2719
    return 1;
Chris Allegretta's avatar
Chris Allegretta committed
2720
2721
}

2722
2723
2724
/* 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)
2725
{
2726
#ifndef NANO_TINY
2727
2728
2729
    if (openfile->mark_set)
	return TRUE;
    else
2730
#endif
2731
	return (get_page_start(old_column) != get_page_start(new_column));
2732
2733
}

2734
/* Determine how many file lines we can display, accounting for softwraps. */
2735
void compute_maxlines(void)
2736
{
2737
2738
#ifndef NANO_TINY
    if (ISSET(SOFTWRAP)) {
2739
	filestruct *line = openfile->edittop;
2740
	int row = 0;
2741

2742
	maxlines = 0;
2743

2744
2745
	while (row < editwinrows && line != NULL) {
	    row += (strlenpt(line->data) / editwincols) + 1;
2746
	    line = line->next;
2747
	    maxlines++;
2748
	}
2749

2750
2751
	if (row < editwinrows)
	    maxlines += (editwinrows - row);
2752
#ifdef DEBUG
2753
	fprintf(stderr, "recomputed: maxlines = %d\n", maxlines);
2754
#endif
2755
2756
    } else
#endif /* !NANO_TINY */
2757
	maxlines = editwinrows;
2758
2759
}

2760
2761
2762
2763
/* Scroll the edit window in the given direction and the given number of rows,
 * and draw new lines on the blank lines left after the scrolling.  We change
 * edittop, and assume that current and current_x are up to date. */
void edit_scroll(scroll_dir direction, int nrows)
2764
{
2765
    int i;
2766
    filestruct *line;
2767

2768
2769
    /* Part 1: nrows is the number of rows we're going to scroll the text of
     * the edit window. */
2770

2771
2772
2773
2774
    /* Move the top line of the edit window up or down (depending on the value
     * of direction) nrows rows, or as many rows as we can if there are fewer
     * than nrows rows available. */
    for (i = nrows; i > 0; i--) {
2775
	if (direction == UPWARD) {
2776
	    if (openfile->edittop == openfile->fileage)
2777
		break;
2778
	    openfile->edittop = openfile->edittop->prev;
2779
	} else {
2780
	    if (openfile->edittop == openfile->filebot)
2781
		break;
2782
	    openfile->edittop = openfile->edittop->next;
2783
	}
2784
2785

#ifndef NANO_TINY
2786
	/* Don't over-scroll on long lines. */
2787
	if (ISSET(SOFTWRAP) && direction == UPWARD) {
2788
	    ssize_t len = strlenpt(openfile->edittop->data) / editwincols;
2789
	    i -= len;
2790
	    if (len > 0)
2791
		refresh_needed = TRUE;
2792
	}
2793
#endif
2794
2795
    }

2796
2797
    /* Limit nrows to the number of rows we could scroll. */
    nrows -= i;
2798

2799
2800
    /* Don't bother scrolling zero rows, nor more than the window can hold. */
    if (nrows == 0)
2801
	return;
2802
    if (nrows >= editwinrows)
2803
	refresh_needed = TRUE;
2804

2805
    if (refresh_needed == TRUE)
2806
	return;
2807

2808
    /* Scroll the text of the edit window a number of rows up or down. */
2809
    scrollok(edit, TRUE);
2810
    wscrl(edit, (direction == UPWARD) ? -nrows : nrows);
2811
2812
    scrollok(edit, FALSE);

2813
2814
    /* Part 2: nrows is now the number of rows in the scrolled region of the
     * edit window that we need to draw. */
2815

2816
2817
2818
2819
2820
    /* If the scrolled region contains only one row, and the row before it is
     * visible in the edit window, we need to draw it too.  If the scrolled
     * region is more than one row, and the rows before and after it are
     * visible in the edit window, we need to draw them too. */
    nrows += (nrows == 1) ? 1 : 2;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2821

2822
2823
    if (nrows > editwinrows)
	nrows = editwinrows;
2824

2825
    /* If we scrolled up, we're on the line before the scrolled region. */
2826
    line = openfile->edittop;
2827

2828
    /* If we scrolled down, move down to the line before the scrolled region. */
2829
    if (direction == DOWNWARD) {
2830
	for (i = editwinrows - nrows; i > 0 && line != NULL; i--)
2831
	    line = line->next;
2832
2833
    }

2834
2835
2836
2837
2838
2839
    /* Draw new lines on any blank rows before or inside the scrolled region.
     * If we scrolled down and we're on the top row, or if we scrolled up and
     * we're on the bottom row, the row 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 = nrows; i > 0 && line != NULL; i--) {
	if ((i == nrows && direction == DOWNWARD) ||
2840
			(i == 1 && direction == UPWARD)) {
2841
	    if (need_horizontal_scroll(openfile->placewewant, 0))
2842
		update_line(line, (line == openfile->current) ?
2843
2844
			openfile->current_x : 0);
	} else
2845
	    update_line(line, (line == openfile->current) ?
2846
		openfile->current_x : 0);
2847
	line = line->next;
2848
    }
2849

2850
    compute_maxlines();
2851
2852
2853
}

/* Update any lines between old_current and current that need to be
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2854
 * updated.  Use this if we've moved without changing any text. */
2855
void edit_redraw(filestruct *old_current)
2856
{
2857
2858
2859
2860
    size_t was_pww = openfile->placewewant;

    openfile->placewewant = xplustabs();

2861
    /* If the current line is offscreen, scroll until it's onscreen. */
2862
    if (openfile->current->lineno >= openfile->edittop->lineno + maxlines ||
2863
#ifndef NANO_TINY
2864
		(openfile->current->lineno == openfile->edittop->lineno + maxlines - 1 &&
2865
		ISSET(SOFTWRAP) && strlenpt(openfile->current->data) >= editwincols) ||
2866
#endif
2867
		openfile->current->lineno < openfile->edittop->lineno) {
2868
	adjust_viewport((focusing || !ISSET(SMOOTH_SCROLL)) ? CENTERING : FLOWING);
2869
	refresh_needed = TRUE;
2870
	return;
2871
    }
2872

2873
#ifndef NANO_TINY
2874
2875
    /* If the mark is on, update all lines between old_current and current. */
    if (openfile->mark_set) {
2876
	filestruct *line = old_current;
2877

2878
2879
	while (line != openfile->current) {
	    update_line(line, 0);
2880

2881
2882
	    line = (line->lineno > openfile->current->lineno) ?
			line->prev : line->next;
2883
	}
2884
2885
2886
2887
2888
2889
    } 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);
2890

2891
2892
    /* Update current if the mark is on or it has changed "page", or if it
     * differs from old_current and needs to be horizontally scrolled. */
2893
    if (need_horizontal_scroll(was_pww, openfile->placewewant) ||
2894
			(old_current != openfile->current &&
2895
			get_page_start(openfile->placewewant) > 0))
2896
	update_line(openfile->current, openfile->current_x);
2897
2898
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2899
2900
/* 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
2901
2902
void edit_refresh(void)
{
2903
2904
    filestruct *line;
    int row = 0;
2905

2906
2907
    /* Figure out what maxlines should really be. */
    compute_maxlines();
2908

2909
    /* If the current line is out of view, get it back on screen. */
2910
    if (openfile->current->lineno < openfile->edittop->lineno ||
2911
		openfile->current->lineno >= openfile->edittop->lineno + maxlines) {
2912
#ifdef DEBUG
2913
2914
	fprintf(stderr, "edit-refresh: line = %ld, edittop = %ld and maxlines = %d\n",
		(long)openfile->current->lineno, (long)openfile->edittop->lineno, maxlines);
2915
#endif
2916
	adjust_viewport((focusing || !ISSET(SMOOTH_SCROLL)) ? CENTERING : STATIONARY);
2917
    }
Chris Allegretta's avatar
Chris Allegretta committed
2918

2919
#ifdef DEBUG
2920
    fprintf(stderr, "edit-refresh: now edittop = %ld\n", (long)openfile->edittop->lineno);
2921
#endif
2922

2923
2924
2925
2926
    line = openfile->edittop;

    while (row < editwinrows && line != NULL) {
	if (line == openfile->current)
2927
	    row += update_line(line, openfile->current_x);
2928
	else
2929
	    row += update_line(line, 0);
2930
	line = line->next;
2931
2932
    }

2933
    while (row < editwinrows)
2934
	blank_row(edit, row++, 0, COLS);
2935
2936

    reset_cursor();
2937
    wnoutrefresh(edit);
2938
2939

    refresh_needed = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
2940
2941
}

2942
2943
2944
2945
2946
/* 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. */
2947
void adjust_viewport(update_type manner)
Chris Allegretta's avatar
Chris Allegretta committed
2948
{
2949
    int goal = 0;
2950

2951
2952
2953
2954
2955
2956
2957
2958
    /* If manner is CENTERING, move edittop half the number of window rows
     * back from current.  If manner is FLOWING, move edittop back 0 rows
     * or (editwinrows - 1) rows, depending on where current has moved.
     * This puts the cursor on the first or the last row.  If manner is
     * STATIONARY, move edittop back current_y rows if current_y is in range
     * of the screen, 0 rows if current_y is below zero, or (editwinrows - 1)
     * rows if current_y is too big.  This puts current at the same place on
     * the screen as before, or... at some undefined place. */
2959
    if (manner == CENTERING)
2960
	goal = editwinrows / 2;
2961
    else if (manner == FLOWING) {
2962
	if (openfile->current->lineno >= openfile->edittop->lineno) {
2963
	    goal = editwinrows - 1;
2964
2965
#ifndef NANO_TINY
	    if (ISSET(SOFTWRAP))
2966
		goal -= strlenpt(openfile->current->data) / editwincols;
2967
2968
#endif
	}
2969
    } else {
2970
	goal = openfile->current_y;
2971

2972
	/* Limit goal to (editwinrows - 1) rows maximum. */
2973
2974
	if (goal > editwinrows - 1)
	    goal = editwinrows - 1;
Chris Allegretta's avatar
Chris Allegretta committed
2975
    }
2976

2977
2978
2979
2980
    openfile->edittop = openfile->current;

    while (goal > 0 && openfile->edittop->prev != NULL) {
	openfile->edittop = openfile->edittop->prev;
2981
	goal--;
2982
#ifndef NANO_TINY
2983
	if (ISSET(SOFTWRAP)) {
2984
	    goal -= strlenpt(openfile->edittop->data) / editwincols;
2985
2986
2987
	    if (goal < 0)
		openfile->edittop = openfile->edittop->next;
	}
2988
#endif
2989
    }
2990
#ifdef DEBUG
2991
    fprintf(stderr, "adjust_viewport(): setting edittop to lineno %ld\n", (long)openfile->edittop->lineno);
2992
#endif
2993
    compute_maxlines();
Chris Allegretta's avatar
Chris Allegretta committed
2994
2995
}

2996
/* Unconditionally redraw the entire screen. */
2997
void total_redraw(void)
2998
{
2999
3000
3001
3002
3003
3004
#ifdef USE_SLANG
    /* Slang curses emulation brain damage, part 4: Slang doesn't define
     * curscr. */
    SLsmg_touch_screen();
    SLsmg_refresh();
#else
3005
    wrefresh(curscr);
3006
#endif
3007
3008
}

3009
3010
/* Unconditionally redraw the entire screen, and then refresh it using
 * the current file. */
3011
3012
void total_refresh(void)
{
3013
    total_redraw();
3014
    titlebar(NULL);
3015
    edit_refresh();
3016
    bottombars(currmenu);
3017
3018
}

3019
3020
/* Display the main shortcut list on the last two rows of the bottom
 * portion of the window. */
3021
3022
void display_main_list(void)
{
3023
#ifndef DISABLE_COLOR
3024
3025
    if (openfile->syntax &&
		(openfile->syntax->formatter || openfile->syntax->linter))
3026
	set_lint_or_format_shortcuts();
3027
3028
3029
3030
    else
	set_spell_shortcuts();
#endif

3031
    bottombars(MMAIN);
3032
3033
}

3034
3035
3036
3037
/* Show info about the current cursor position on the statusbar.
 * Do this unconditionally when force is TRUE; otherwise, only if
 * suppress_cursorpos is FALSE.  In any case, reset the latter. */
void do_cursorpos(bool force)
Chris Allegretta's avatar
Chris Allegretta committed
3038
{
3039
3040
    char saved_byte;
    size_t sum, cur_xpt = xplustabs() + 1;
3041
    size_t cur_lenpt = strlenpt(openfile->current->data) + 1;
3042
    int linepct, colpct, charpct;
Chris Allegretta's avatar
Chris Allegretta committed
3043

3044
    assert(openfile->fileage != NULL && openfile->current != NULL);
3045

3046
    /* Determine the size of the file up to the cursor. */
3047
    saved_byte = openfile->current->data[openfile->current_x];
3048
    openfile->current->data[openfile->current_x] = '\0';
3049

3050
    sum = get_totsize(openfile->fileage, openfile->current);
3051

3052
    openfile->current->data[openfile->current_x] = saved_byte;
3053
3054
3055

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

3058
3059
    /* If the showing needs to be suppressed, don't suppress it next time. */
    if (suppress_cursorpos && !force) {
3060
	suppress_cursorpos = FALSE;
3061
	return;
3062
    }
Chris Allegretta's avatar
Chris Allegretta committed
3063

3064
    /* Display the current cursor position on the statusbar. */
3065
    linepct = 100 * openfile->current->lineno / openfile->filebot->lineno;
3066
    colpct = 100 * cur_xpt / cur_lenpt;
3067
    charpct = (openfile->totsize == 0) ? 0 : 100 * sum / openfile->totsize;
3068

3069
    statusline(HUSH,
3070
	_("line %ld/%ld (%d%%), col %lu/%lu (%d%%), char %lu/%lu (%d%%)"),
3071
	(long)openfile->current->lineno,
3072
	(long)openfile->filebot->lineno, linepct,
3073
	(unsigned long)cur_xpt, (unsigned long)cur_lenpt, colpct,
3074
	(unsigned long)sum, (unsigned long)openfile->totsize, charpct);
3075
3076
3077

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

3080
/* Unconditionally display the current cursor position. */
3081
void do_cursorpos_void(void)
3082
{
3083
    do_cursorpos(TRUE);
3084
3085
}

3086
3087
void enable_nodelay(void)
{
3088
3089
    nodelay_mode = TRUE;
    nodelay(edit, TRUE);
3090
3091
3092
3093
}

void disable_nodelay(void)
{
3094
3095
    nodelay_mode = FALSE;
    nodelay(edit, FALSE);
3096
3097
}

3098
3099
/* Highlight the current word being replaced or spell checked.  We
 * expect word to have tabs and control characters expanded. */
3100
void spotlight(bool active, const char *word)
Chris Allegretta's avatar
Chris Allegretta committed
3101
{
3102
3103
    size_t word_span = strlenpt(word);
    size_t room = word_span;
Chris Allegretta's avatar
Chris Allegretta committed
3104

3105
    /* Compute the number of columns that are available for the word. */
3106
    if (!ISSET(SOFTWRAP)) {
3107
	room = editwincols + get_page_start(xplustabs()) - xplustabs();
Chris Allegretta's avatar
Chris Allegretta committed
3108

3109
3110
3111
3112
	/* If the word is partially offscreen, reserve space for the "$". */
	if (word_span > room)
	    room--;
    }
3113

Chris Allegretta's avatar
Chris Allegretta committed
3114
    reset_cursor();
Chris Allegretta's avatar
Chris Allegretta committed
3115

3116
    if (active)
3117
	wattron(edit, hilite_attribute);
Chris Allegretta's avatar
Chris Allegretta committed
3118

3119
    /* This is so we can show zero-length matches. */
3120
    if (word_span == 0)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3121
	waddch(edit, ' ');
3122
    else
3123
	waddnstr(edit, word, actual_x(word, room));
3124

3125
    if (word_span > room)
3126
	waddch(edit, '$');
Chris Allegretta's avatar
Chris Allegretta committed
3127

3128
    if (active)
3129
	wattroff(edit, hilite_attribute);
3130
3131

    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
3132
3133
}

3134
#ifndef DISABLE_EXTRA
3135
3136
#define CREDIT_LEN 54
#define XLCREDIT_LEN 9
3137

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3138
3139
/* Easter egg: Display credits.  Assume nodelay(edit) and scrollok(edit)
 * are FALSE. */
3140
3141
void do_credits(void)
{
3142
3143
    bool old_more_space = ISSET(MORE_SPACE);
    bool old_no_help = ISSET(NO_HELP);
3144
    int kbinput = ERR, crpos = 0, xlpos = 0;
3145
3146
3147
    const char *credits[CREDIT_LEN] = {
	NULL,				/* "The nano text editor" */
	NULL,				/* "version" */
Chris Allegretta's avatar
Chris Allegretta committed
3148
3149
	VERSION,
	"",
3150
	NULL,				/* "Brought to you by:" */
Chris Allegretta's avatar
Chris Allegretta committed
3151
3152
3153
3154
3155
	"Chris Allegretta",
	"Jordi Mallach",
	"Adam Rogoyski",
	"Rob Siemborski",
	"Rocco Corsi",
3156
	"David Lawrence Ramsey",
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3157
	"David Benbennick",
Benno Schulenberg's avatar
Benno Schulenberg committed
3158
	"Mark Majeres",
3159
	"Mike Frysinger",
3160
	"Benno Schulenberg",
Chris Allegretta's avatar
Chris Allegretta committed
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
	"Ken Tyler",
	"Sven Guckes",
	"Bill Soudan",
	"Christian Weisgerber",
	"Erik Andersen",
	"Big Gaute",
	"Joshua Jensen",
	"Ryan Krebs",
	"Albert Chin",
	"",
3171
	NULL,				/* "Special thanks to:" */
3172
	"Monique, Brielle & Joseph",
Chris Allegretta's avatar
Chris Allegretta committed
3173
3174
3175
3176
3177
3178
	"Plattsburgh State University",
	"Benet Laboratories",
	"Amy Allegretta",
	"Linda Young",
	"Jeremy Robichaud",
	"Richard Kolb II",
3179
	NULL,				/* "The Free Software Foundation" */
Chris Allegretta's avatar
Chris Allegretta committed
3180
	"Linus Torvalds",
3181
	NULL,				/* "the many translators and the TP" */
3182
	NULL,				/* "For ncurses:" */
3183
3184
3185
3186
	"Thomas Dickey",
	"Pavel Curtis",
	"Zeyd Ben-Halim",
	"Eric S. Raymond",
3187
3188
3189
3190
3191
3192
	NULL,				/* "and anyone else we forgot..." */
	NULL,				/* "Thank you for using nano!" */
	"",
	"",
	"",
	"",
3193
	"(C) 1999 - 2016",
3194
	"Free Software Foundation, Inc.",
3195
3196
3197
3198
	"",
	"",
	"",
	"",
3199
	"https://nano-editor.org/"
3200
3201
    };

3202
    const char *xlcredits[XLCREDIT_LEN] = {
3203
3204
3205
3206
3207
	N_("The nano text editor"),
	N_("version"),
	N_("Brought to you by:"),
	N_("Special thanks to:"),
	N_("The Free Software Foundation"),
3208
	N_("the many translators and the TP"),
3209
3210
3211
	N_("For ncurses:"),
	N_("and anyone else we forgot..."),
	N_("Thank you for using nano!")
3212
    };
3213

3214
3215
3216
3217
3218
3219
    if (!old_more_space || !old_no_help) {
	SET(MORE_SPACE);
	SET(NO_HELP);
	window_init();
    }

3220
3221
    curs_set(0);
    nodelay(edit, TRUE);
3222

3223
    blank_titlebar();
Chris Allegretta's avatar
Chris Allegretta committed
3224
    blank_edit();
3225
    blank_statusbar();
3226

3227
    wrefresh(topwin);
Chris Allegretta's avatar
Chris Allegretta committed
3228
    wrefresh(edit);
3229
    wrefresh(bottomwin);
3230
    napms(700);
3231

3232
    for (crpos = 0; crpos < CREDIT_LEN + editwinrows / 2; crpos++) {
3233
	if ((kbinput = wgetch(edit)) != ERR)
3234
	    break;
3235

3236
	if (crpos < CREDIT_LEN) {
3237
	    const char *what;
3238
	    size_t start_col;
3239

3240
	    if (credits[crpos] == NULL) {
3241
		assert(0 <= xlpos && xlpos < XLCREDIT_LEN);
3242

3243
		what = _(xlcredits[xlpos]);
3244
		xlpos++;
3245
	    } else
3246
		what = credits[crpos];
3247

3248
	    start_col = COLS / 2 - strlenpt(what) / 2 - 1;
3249
	    mvwaddstr(edit, editwinrows - 1 - (editwinrows % 2),
3250
						start_col, what);
3251
	}
3252

3253
3254
3255
3256
	wrefresh(edit);

	if ((kbinput = wgetch(edit)) != ERR)
	    break;
3257
	napms(700);
3258

3259
	scrollok(edit, TRUE);
3260
	wscrl(edit, 1);
3261
	scrollok(edit, FALSE);
3262
	wrefresh(edit);
3263

3264
	if ((kbinput = wgetch(edit)) != ERR)
3265
	    break;
3266
	napms(700);
3267

3268
	scrollok(edit, TRUE);
3269
	wscrl(edit, 1);
3270
	scrollok(edit, FALSE);
3271
	wrefresh(edit);
3272
3273
    }

3274
3275
3276
    if (kbinput != ERR)
	ungetch(kbinput);

3277
    if (!old_more_space)
3278
	UNSET(MORE_SPACE);
3279
    if (!old_no_help)
3280
	UNSET(NO_HELP);
3281
    window_init();
3282

3283
    nodelay(edit, FALSE);
3284

3285
    total_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
3286
}
3287
#endif /* !DISABLE_EXTRA */