winio.c 100 KB
Newer Older
Chris Allegretta's avatar
Chris Allegretta committed
1
/**************************************************************************
2
 *   winio.c  --  This file is part of GNU nano.                          *
Chris Allegretta's avatar
Chris Allegretta committed
3
 *                                                                        *
4
 *   Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007,  *
5
 *   2008, 2009, 2010, 2011, 2013, 2014 Free Software Foundation, Inc.    *
6
7
 *   Copyright (C) 2014, 2015, 2016 Benno Schulenberg                     *
 *                                                                        *
8
9
10
11
 *   GNU nano is free software: you can redistribute it and/or modify     *
 *   it under the terms of the GNU General Public License as published    *
 *   by the Free Software Foundation, either version 3 of the License,    *
 *   or (at your option) any later version.                               *
Chris Allegretta's avatar
Chris Allegretta committed
12
 *                                                                        *
13
14
15
16
 *   GNU nano is distributed in the hope that it will be useful,          *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty          *
 *   of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.              *
 *   See the GNU General Public License for more details.                 *
Chris Allegretta's avatar
Chris Allegretta committed
17
18
 *                                                                        *
 *   You should have received a copy of the GNU General Public License    *
19
 *   along with this program.  If not, see http://www.gnu.org/licenses/.  *
Chris Allegretta's avatar
Chris Allegretta committed
20
21
22
 *                                                                        *
 **************************************************************************/

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

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

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

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

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

58
59
/* Control character compatibility:
 *
60
61
62
63
64
 * - Ctrl-H is Backspace under ASCII, ANSI, VT100, and VT220.
 * - Ctrl-I is Tab under ASCII, ANSI, VT100, VT220, and VT320.
 * - Ctrl-M is Enter under ASCII, ANSI, VT100, VT220, and VT320.
 * - Ctrl-Q is XON under ASCII, ANSI, VT100, VT220, and VT320.
 * - Ctrl-S is XOFF under ASCII, ANSI, VT100, VT220, and VT320.
65
66
 * - Ctrl-8 (Ctrl-?) is Delete under ASCII, ANSI, VT100, and VT220,
 *          but is Backspace under VT320.
67
 *
68
 * Note: VT220 and VT320 also generate Esc [ 3 ~ for Delete.  By
69
70
 * default, xterm assumes it's running on a VT320 and generates Ctrl-8
 * (Ctrl-?) for Backspace and Esc [ 3 ~ for Delete.  This causes
71
 * problems for VT100-derived terminals such as the FreeBSD console,
72
 * which expect Ctrl-H for Backspace and Ctrl-8 (Ctrl-?) for Delete, and
73
74
75
76
77
78
79
80
81
 * on which the VT320 sequences are translated by the keypad to KEY_DC
 * and [nothing].  We work around this conflict via the REBIND_DELETE
 * flag: if it's not set, we assume VT320 compatibility, and if it is,
 * we assume VT100 compatibility.  Thanks to Lee Nelson and Wouter van
 * Hemel for helping work this conflict out.
 *
 * Escape sequence compatibility:
 *
 * We support escape sequences for ANSI, VT100, VT220, VT320, the Linux
82
 * console, the FreeBSD console, the Mach console, xterm, rxvt, Eterm,
83
84
 * and Terminal, and some for iTerm2.  Among these, there are several
 * conflicts and omissions, outlined as follows:
85
86
87
88
89
90
 *
 * - Tab on ANSI == PageUp on FreeBSD console; the former is omitted.
 *   (Ctrl-I is also Tab on ANSI, which we already support.)
 * - PageDown on FreeBSD console == Center (5) on numeric keypad with
 *   NumLock off on Linux console; the latter is omitted.  (The editing
 *   keypad key is more important to have working than the numeric
91
 *   keypad key, because the latter has no value when NumLock is off.)
92
93
94
95
 * - F1 on FreeBSD console == the mouse key on xterm/rxvt/Eterm; the
 *   latter is omitted.  (Mouse input will only work properly if the
 *   extended keypad value KEY_MOUSE is generated on mouse events
 *   instead of the escape sequence.)
96
 * - F9 on FreeBSD console == PageDown on Mach console; the former is
97
98
99
 *   omitted.  (The editing keypad is more important to have working
 *   than the function keys, because the functions of the former are not
 *   arbitrary and the functions of the latter are.)
100
 * - F10 on FreeBSD console == PageUp on Mach console; the former is
101
 *   omitted.  (Same as above.)
102
 * - F13 on FreeBSD console == End on Mach console; the former is
103
 *   omitted.  (Same as above.)
104
105
106
107
108
109
 * - F15 on FreeBSD console == Shift-Up on rxvt/Eterm; the former is
 *   omitted.  (The arrow keys, with or without modifiers, are more
 *   important to have working than the function keys, because the
 *   functions of the former are not arbitrary and the functions of the
 *   latter are.)
 * - F16 on FreeBSD console == Shift-Down on rxvt/Eterm; the former is
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
110
 *   omitted.  (Same as above.) */
111

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

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

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

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

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

139
140
141
142
143
144
145
146
147
148
149
    if (input == ERR && nodelay_mode)
	return;

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

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

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

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

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

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

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

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

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

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

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

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

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
225
226
227
    /* Add the length of input to the length of the keystroke buffer,
     * and reallocate the keystroke buffer so that it has enough room
     * for input. */
228
229
230
    key_buffer_len += input_len;
    key_buffer = (int *)nrealloc(key_buffer, key_buffer_len *
	sizeof(int));
231

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

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

242
243
244
/* Put the character given in kbinput back into the input stream.  If it
 * is a Meta key, also insert an Escape character in front of it. */
void unget_kbinput(int kbinput, bool metakey)
245
{
246
    unget_input(&kbinput, 1);
247

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

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

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

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

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

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

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

    return input;
290
291
}

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

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

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

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

310
311
312
    return kbinput;
}

313
314
/* Extract a single keystroke from the input stream.  Translate escape
 * sequences and extended keypad codes into their corresponding values.
315
 * Set meta_key to TRUE when appropriate.  Supported extended keypad values
316
317
318
 * are: [arrow key], Ctrl-[arrow key], Shift-[arrow key], Enter, Backspace,
 * the editing keypad (Insert, Delete, Home, End, PageUp, and PageDown),
 * the function keys (F1-F16), and the numeric keypad with NumLock off. */
319
int parse_kbinput(WINDOW *win)
320
{
321
    static int escapes = 0, byte_digits = 0;
322
    static bool double_esc = FALSE;
323
    int *kbinput, keycode, retval = ERR;
324

325
    meta_key = FALSE;
326
    shift_held = FALSE;
327

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

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

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

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

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

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

348
    if (keycode == ESC_CODE) {
349
350
351
352
353
354
355
	/* Increment the escape counter, but trim an overabundance. */
	escapes++;
	if (escapes > 3)
	    escapes = 1;
	/* Take note when an Esc arrived by itself. */
	solitary = (escapes == 1 && key_buffer_len == 0);
	return ERR;
356
357
    }

358
359
360
361
362
363
    switch (escapes) {
	case 0:
	    /* One non-escape: normal input mode. */
	    retval = keycode;
	    break;
	case 1:
364
365
366
	    if (keycode >= 0x80)
		retval = keycode;
	    else if ((keycode != 'O' && keycode != 'o' && keycode != '[') ||
367
			key_buffer_len == 0 || *key_buffer == ESC_CODE) {
368
369
370
371
372
373
374
375
376
		/* One escape followed by a single non-escape:
		 * meta key sequence mode. */
		if (!solitary || (keycode >= 0x20 && keycode < 0x7F))
		    meta_key = TRUE;
		retval = tolower(keycode);
	    } else
		/* One escape followed by a non-escape, and there
		 * are more codes waiting: escape sequence mode. */
		retval = parse_escape_sequence(win, keycode);
377
	    escapes = 0;
378
379
380
	    break;
	case 2:
	    if (double_esc) {
381
382
		/* An "ESC ESC [ X" sequence from Option+arrow, or
		 * an "ESC ESC [ x" sequence from Shift+Alt+arrow. */
383
384
385
386
387
388
389
390
		switch (keycode) {
		    case 'A':
			retval = KEY_HOME;
			break;
		    case 'B':
			retval = KEY_END;
			break;
		    case 'C':
391
			retval = CONTROL_RIGHT;
392
393
			break;
		    case 'D':
394
			retval = CONTROL_LEFT;
395
			break;
396
#ifndef NANO_TINY
397
398
399
400
401
402
403
404
405
406
407
408
		    case 'a':
			retval = shiftaltup;
			break;
		    case 'b':
			retval = shiftaltdown;
			break;
		    case 'c':
			retval = shiftaltright;
			break;
		    case 'd':
			retval = shiftaltleft;
			break;
409
#endif
410
411
412
		}
		double_esc = FALSE;
		escapes = 0;
413
	    } else if (key_buffer_len == 0) {
414
415
416
417
418
419
420
421
422
423
424
425
426
427
		if (('0' <= keycode && keycode <= '2' &&
			byte_digits == 0) || ('0' <= keycode &&
			keycode <= '9' && byte_digits > 0)) {
		    /* Two escapes followed by one or more decimal
		     * digits, and there aren't any other codes
		     * waiting: byte sequence mode.  If the range
		     * of the byte sequence is limited to 2XX (the
		     * first digit is between '0' and '2' and the
		     * others between '0' and '9', interpret it. */
		    int byte;

		    byte_digits++;
		    byte = get_byte_kbinput(keycode);

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

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

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

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

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

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

			byte_digits = 0;
			escapes = 0;
450
		    }
451
452
453
454
455
456
457
458
459
460
461
462
463
		} else {
		    if (byte_digits == 0)
			/* Two escapes followed by a non-decimal
			 * digit (or a decimal digit that would
			 * create a byte sequence greater than 2XX)
			 * and there aren't any other codes waiting:
			 * control character sequence mode. */
			retval = get_control_kbinput(keycode);
		    else {
			/* An invalid digit in the middle of a byte
			 * sequence: reset the byte sequence counter
			 * and save the code we got as the result. */
			byte_digits = 0;
464
			retval = keycode;
465
		    }
466
		    escapes = 0;
467
468
		}
	    } else if (keycode == '[' && key_buffer_len > 0 &&
469
470
471
			(('A' <= *key_buffer && *key_buffer <= 'D') ||
			('a' <= *key_buffer && *key_buffer <= 'd'))) {
		/* An iTerm2/Eterm/rxvt sequence: ^[ ^[ [ X. */
472
473
474
475
476
		double_esc = TRUE;
	    } else {
		/* Two escapes followed by a non-escape, and there are more
		 * codes waiting: combined meta and escape sequence mode. */
		retval = parse_escape_sequence(win, keycode);
477
478
		meta_key = TRUE;
		escapes = 0;
479
	    }
480
481
	    break;
	case 3:
482
	    if (key_buffer_len == 0)
483
484
485
486
487
488
489
490
491
492
		/* Three escapes followed by a non-escape, and no
		 * other codes are waiting: normal input mode. */
		retval = keycode;
	    else
		/* Three escapes followed by a non-escape, and more
		 * codes are waiting: combined control character and
		 * escape sequence mode.  First interpret the escape
		 * sequence, then the result as a control sequence. */
		retval = get_control_kbinput(
			parse_escape_sequence(win, keycode));
493
	    escapes = 0;
494
495
	    break;
    }
496

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

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

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

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

574
    switch (retval) {
575
#ifdef KEY_SLEFT
576
577
	/* Slang doesn't support KEY_SLEFT. */
	case KEY_SLEFT:
578
	    shift_held = TRUE;
579
	    return KEY_LEFT;
580
#endif
581
#ifdef KEY_SRIGHT
582
583
	/* Slang doesn't support KEY_SRIGHT. */
	case KEY_SRIGHT:
584
	    shift_held = TRUE;
585
	    return KEY_RIGHT;
586
#endif
587
#ifdef KEY_SR
588
#ifdef KEY_SUP
589
590
	/* ncurses and Slang don't support KEY_SUP. */
	case KEY_SUP:
591
#endif
592
593
	case KEY_SR:	/* Scroll backward, on Xfce4-terminal. */
	    shift_held = TRUE;
594
	    return KEY_UP;
595
596
#endif
#ifdef KEY_SF
597
#ifdef KEY_SDOWN
598
599
	/* ncurses and Slang don't support KEY_SDOWN. */
	case KEY_SDOWN:
600
#endif
601
602
	case KEY_SF:	/* Scroll forward, on Xfce4-terminal. */
	    shift_held = TRUE;
603
	    return KEY_DOWN;
604
#endif
605
#ifdef KEY_SHOME
606
607
	/* HP-UX 10-11 and Slang don't support KEY_SHOME. */
	case KEY_SHOME:
608
#endif
609
610
	case SHIFT_HOME:
	    shift_held = TRUE;
611
	case KEY_A1:	/* Home (7) on keypad with NumLock off. */
612
	    return KEY_HOME;
613
#ifdef KEY_SEND
614
615
	/* HP-UX 10-11 and Slang don't support KEY_SEND. */
	case KEY_SEND:
616
#endif
617
618
	case SHIFT_END:
	    shift_held = TRUE;
619
	case KEY_C1:	/* End (1) on keypad with NumLock off. */
620
	    return KEY_END;
621
#ifndef NANO_TINY
622
623
624
625
#ifdef KEY_SPREVIOUS
	case KEY_SPREVIOUS:
#endif
	case SHIFT_PAGEUP:	/* Fake key, from Shift+Alt+Up. */
626
627
	    shift_held = TRUE;
#endif
628
	case KEY_A3:	/* PageUp (9) on keypad with NumLock off. */
629
	    return KEY_PPAGE;
630
#ifndef NANO_TINY
631
632
633
#ifdef KEY_SNEXT
	case KEY_SNEXT:
#endif
634
635
636
	case SHIFT_PAGEDOWN:	/* Fake key, from Shift+Alt+Down. */
	    shift_held = TRUE;
#endif
637
	case KEY_C3:	/* PageDown (3) on keypad with NumLock off. */
638
	    return KEY_NPAGE;
639
#ifdef KEY_SDC
640
641
	/* Slang doesn't support KEY_SDC. */
	case KEY_SDC:
642
#endif
643
	case DEL_CODE:
644
	    if (ISSET(REBIND_DELETE))
645
		return 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
#ifdef ENABLE_UTF8
1490
    if (using_utf8()) {
1491
	/* Check whether the first code is a valid starter digit: 0 or 1. */
1492
	long uni = get_unicode_kbinput(win, *kbinput);
1493

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

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

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

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

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

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

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

1529
1530
    free(kbinput);

1531
    *count = 1;
1532

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

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

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

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

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

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

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

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

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

		return 0;
	    }
1605

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return NULL;
}

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

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

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

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

1749
1750
    for (row = 0; row < editwinrows; row++)
	blank_row(edit, row, 0, COLS);
Chris Allegretta's avatar
Chris Allegretta committed
1751
1752
}

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

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

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

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

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

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

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

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

1824
    assert(column <= start_col);
1825

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

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

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

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

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

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

1864
	if (*buf == ' ') {
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
	    /* Show a space as a visible character, or as a space. */
#ifndef NANO_TINY
	    if (ISSET(WHITESPACE_DISPLAY)) {
		int i = whitespace_len[0];

		while (i < whitespace_len[0] + whitespace_len[1])
		    converted[index++] = whitespace[i++];
	    } else
#endif
		converted[index++] = ' ';
	    start_col++;
1876
1877
	    buf++;
	    continue;
1878
	} else if (*buf == '\t') {
1879
	    /* Show a tab as a visible character, or as as a space. */
1880
#ifndef NANO_TINY
1881
	    if (ISSET(WHITESPACE_DISPLAY)) {
1882
		int i = 0;
1883

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

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

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

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

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

1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
	/* Represent an invalid sequence with the Replacement Character. */
	converted[index++] = '\xEF';
	converted[index++] = '\xBF';
	converted[index++] = '\xBD';

	start_col += 1;
	buf++;

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

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

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

1943
    return converted;
1944
1945
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1946
1947
1948
1949
1950
1951
/* If path is NULL, we're in normal editing mode, so display the current
 * version of nano, the current filename, and whether the current file
 * has been modified on the titlebar.  If path isn't NULL, we're in the
 * file browser, and path contains the directory to start the file
 * browser in, so display the current version of nano and the contents
 * of path on the titlebar. */
1952
void titlebar(const char *path)
Chris Allegretta's avatar
Chris Allegretta committed
1953
{
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
    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 "". */
1964
1965
    char *caption;
	/* The presentable form of the pathname. */
1966

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

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

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

1975
    blank_titlebar();
1976
    as_an_at = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
1977

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    UNSET(WHITESPACE_DISPLAY);
2098
#endif
2099
2100
2101
2102
2103

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

2110
2111
2112
2113
2114
2115
2116
    /* If there already was an alert message, ignore lesser ones. */
    if ((lastmessage == ALERT && importance != ALERT) ||
		(lastmessage == MILD && importance == HUSH))
	return;

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

2119
    if (importance == ALERT)
2120
	beep();
2121
2122

    lastmessage = importance;
2123

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

2127
2128
    blank_statusbar();

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

2136
2137
    start_col = (COLS - strlenpt(message)) / 2;
    bracketed = (start_col > 1);
2138

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

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

2153
    suppress_cursorpos = TRUE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2154

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

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

2176
2177
2178
    /* Set the global variable to the given menu. */
    currmenu = menu;

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

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

2185
2186
    if (number > MAIN_VISIBLE)
	number = MAIN_VISIBLE;
2187

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

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

2195
    blank_bottombars();
2196

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

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

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

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

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

2232
    reset_cursor();
2233
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
2234
2235
}

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

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

2249
    length -= strlenpt(keystroke) + 1;
2250

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

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

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

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

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

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

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

2310
    assert(openfile != NULL && fileptr != NULL && converted != NULL);
2311
2312
2313
    assert(strlenpt(converted) <= editwincols);

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

2328
    /* First simply write the line -- afterward we'll add colors and the
2329
     * marking highlight on just the pieces that need it. */
2330
    mvwaddstr(edit, line, margin, converted);
2331

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

2340
#ifndef DISABLE_COLOR
2341
    /* If color syntaxes are available and turned on, apply them. */
2342
    if (openfile->colorstrings != NULL && !ISSET(NO_COLOR_SYNTAX)) {
2343
	const colortype *varnish = openfile->colorstrings;
2344

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

2349
	/* Iterate through all the coloring regexes. */
2350
	for (; varnish != NULL; varnish = varnish->next) {
2351
2352
	    size_t index = 0;
		/* Where in the line we currently begin looking for a match. */
2353
	    int start_col;
2354
		/* The starting column of a piece to paint.  Zero-based. */
2355
	    int paintlen = 0;
2356
2357
2358
		/* The number of characters to paint. */
	    const char *thetext;
		/* The place in converted from where painting starts. */
2359
	    regmatch_t startmatch, endmatch;
2360
		/* Match positions of the start and end regexes. */
2361

2362
2363
2364
	    /* 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. */
2365

2366
2367
	    wattron(edit, varnish->attributes);

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

2385
2386
2387
2388
2389
2390
		    /* Skip over a zero-length regex match. */
		    if (startmatch.rm_so == startmatch.rm_eo) {
			index += startmatch.rm_eo + 1;
			continue;
		    }

2391
		    /* Translate the match to the beginning of the line. */
2392
2393
2394
		    startmatch.rm_so += index;
		    startmatch.rm_eo += index;
		    index = startmatch.rm_eo;
2395

2396
2397
2398
2399
2400
		    /* If the matching piece is not visible, skip it. */
		    if (startmatch.rm_so >= till_x ||
					startmatch.rm_eo <= from_x)
			continue;

2401
		    start_col = (startmatch.rm_so <= from_x) ?
2402
				0 : strnlenpt(fileptr->data,
2403
				startmatch.rm_so) - from_col;
2404

2405
		    thetext = converted + actual_x(converted, start_col);
2406

2407
		    paintlen = actual_x(thetext, strnlenpt(fileptr->data,
2408
				startmatch.rm_eo) - from_col - start_col);
2409

2410
		    mvwaddnstr(edit, line, margin + start_col,
2411
						thetext, paintlen);
Chris Allegretta's avatar
Chris Allegretta committed
2412
		}
2413
2414
2415
2416
		goto tail_of_loop;
	    }

	    /* Second case: varnish is a multiline expression. */
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
	    const filestruct *start_line = fileptr->prev;
		/* The first line before fileptr that matches 'start'. */
	    const filestruct *end_line = fileptr;
		/* The line that matches 'end'. */

	    /* First see if the multidata was maybe already calculated. */
	    if (fileptr->multidata[varnish->id] == CNONE)
		goto tail_of_loop;
	    else if (fileptr->multidata[varnish->id] == CWHOLELINE) {
		mvwaddnstr(edit, line, margin, converted, -1);
		goto tail_of_loop;
	    } else if (fileptr->multidata[varnish->id] == CBEGINBEFORE) {
		regexec(varnish->end, fileptr->data, 1, &endmatch, 0);
		/* If the coloured part is scrolled off, skip it. */
		if (endmatch.rm_eo <= from_x)
2432
		    goto tail_of_loop;
2433
		paintlen = actual_x(converted, strnlenpt(fileptr->data,
2434
						endmatch.rm_eo) - from_col);
2435
2436
2437
		mvwaddnstr(edit, line, margin, converted, paintlen);
		goto tail_of_loop;
	    }
2438

2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
	    /* There is no precalculated multidata, or it is CENDAFTER or
	     * CSTARTENDHERE.  In all cases, find out what to paint. */

	    /* When the multidata is unitialized, assume CNONE until one
	     * of the steps below concludes otherwise. */
	    if (fileptr->multidata[varnish->id] == -1)
		fileptr->multidata[varnish->id] = CNONE;

	    /* First check if the beginning of the line is colored by a
	     * start on an earlier line, and an end on this line or later.
	     *
	     * So: find the first line before fileptr matching the start.
	     * If every match on that line is followed by an end, then go
	     * to step two.  Otherwise, find a line after start_line that
	     * matches the end.  If that line is not before fileptr, then
	     * paint the beginning of this line. */

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

2465
2466
2467
2468
2469
2470
2471
	    /* 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 &&
2472
2473
			(start_line->multidata[varnish->id] == CBEGINBEFORE ||
			start_line->multidata[varnish->id] == CSTARTENDHERE))
2474
		goto step_two;
2475

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

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

2500
2501
	    /* We've already checked that there is no end between the start
	     * and the current line.  But is there an end after the start
2502
2503
2504
2505
2506
	     * 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;

2507
	    /* If there is no end, there is nothing to paint. */
2508
	    if (end_line == NULL)
2509
2510
		goto tail_of_loop;
	    /* If the end is scrolled off to the left, next step. */
2511
2512
2513
2514
	    if (end_line == fileptr && endmatch.rm_eo <= from_x) {
		fileptr->multidata[varnish->id] = CBEGINBEFORE;
		goto step_two;
	    }
2515

2516
2517
2518
2519
2520
	    /* Now paint the start of the line.  However, if the end match
	     * is on a different line, paint the whole line, and go on. */
	    if (end_line != fileptr) {
		mvwaddnstr(edit, line, margin, converted, -1);
		fileptr->multidata[varnish->id] = CWHOLELINE;
2521
#ifdef DEBUG
2522
    fprintf(stderr, "  Marking for id %i  line %i as CWHOLELINE\n", varnish->id, line);
2523
#endif
2524
2525
2526
2527
		/* Don't bother looking for any more starts. */
		goto tail_of_loop;
	    } else {
		paintlen = actual_x(converted, strnlenpt(fileptr->data,
2528
						endmatch.rm_eo) - from_col);
2529
2530
		mvwaddnstr(edit, line, margin, converted, paintlen);
		fileptr->multidata[varnish->id] = CBEGINBEFORE;
2531
#ifdef DEBUG
2532
    fprintf(stderr, "  Marking for id %i  line %i as CBEGINBEFORE\n", varnish->id, line);
2533
#endif
2534
	    }
2535
  step_two:
2536
2537
2538
	    /* 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;
2539

2540
	    while (regexec(varnish->start, fileptr->data + index,
2541
				1, &startmatch, (index == 0) ?
2542
				0 : REG_NOTBOL) == 0) {
2543
2544
2545
2546
		/* Translate the match to be relative to the
		 * beginning of the line. */
		startmatch.rm_so += index;
		startmatch.rm_eo += index;
2547

2548
		start_col = (startmatch.rm_so <= from_x) ?
2549
				0 : strnlenpt(fileptr->data,
2550
				startmatch.rm_so) - from_col;
2551

2552
		thetext = converted + actual_x(converted, start_col);
2553

2554
2555
		if (regexec(varnish->end, fileptr->data + startmatch.rm_eo,
				1, &endmatch, (startmatch.rm_eo == 0) ?
2556
				0 : REG_NOTBOL) == 0) {
2557
2558
2559
2560
		    /* 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;
2561
2562
		    /* Only paint the match if it is visible on screen and
		     * it is more than zero characters long. */
2563
		    if (endmatch.rm_eo > from_x &&
2564
					endmatch.rm_eo > startmatch.rm_so) {
2565
			paintlen = actual_x(thetext, strnlenpt(fileptr->data,
2566
					endmatch.rm_eo) - from_col - start_col);
2567

2568
2569
			mvwaddnstr(edit, line, margin + start_col,
						thetext, paintlen);
2570

2571
			fileptr->multidata[varnish->id] = CSTARTENDHERE;
2572
#ifdef DEBUG
2573
    fprintf(stderr, "  Marking for id %i  line %i as CSTARTENDHERE\n", varnish->id, line);
2574
#endif
2575
		    }
2576
2577
2578
2579
2580
2581
		    index = endmatch.rm_eo;
		    /* Skip over a zero-length match. */
		    if (endmatch.rm_so == endmatch.rm_eo)
			index += 1;
		    continue;
		}
2582

2583
2584
		/* There is no end on this line.  But maybe on later lines? */
		end_line = fileptr->next;
2585

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

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

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

2610
#ifndef NANO_TINY
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
    /* 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. */
2622
	int start_col;
2623
2624
2625
2626
2627
	    /* 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". */
2628

2629
	mark_order(&top, &top_x, &bot, &bot_x, NULL);
2630

2631
2632
2633
2634
	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
2635

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

2641
	    thetext = converted + actual_x(converted, start_col);
2642

2643
2644
2645
2646
2647
2648
	    /* 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);
	    }
2649

2650
	    wattron(edit, hilite_attribute);
2651
	    mvwaddnstr(edit, line, margin + start_col, thetext, paintlen);
2652
	    wattroff(edit, hilite_attribute);
Chris Allegretta's avatar
Chris Allegretta committed
2653
	}
2654
    }
2655
#endif /* !NANO_TINY */
Chris Allegretta's avatar
Chris Allegretta committed
2656
2657
}

2658
/* Just update one line in the edit buffer.  This is basically a wrapper
2659
 * for edit_draw().  The line will be displayed starting with
2660
 * fileptr->data[index].  Likely arguments are current_x or zero.
2661
 * Returns: Number of additional lines consumed (needed for SOFTWRAP). */
2662
int update_line(filestruct *fileptr, size_t index)
Chris Allegretta's avatar
Chris Allegretta committed
2663
{
2664
    int line = 0;
2665
	/* The row in the edit window we will be updating. */
2666
    int extralinesused = 0;
2667
    char *converted;
2668
	/* The data of the line with tabs and control characters expanded. */
2669
    size_t page_start;
Chris Allegretta's avatar
Chris Allegretta committed
2670

2671
#ifndef NANO_TINY
2672
    if (ISSET(SOFTWRAP)) {
2673
2674
	filestruct *tmp;

2675
	for (tmp = openfile->edittop; tmp && tmp != fileptr; tmp = tmp->next)
2676
	    line += (strlenpt(tmp->data) / editwincols) + 1;
2677
    } else
2678
#endif
2679
2680
	line = fileptr->lineno - openfile->edittop->lineno;

2681
    if (line < 0 || line >= editwinrows)
Chris Allegretta's avatar
Chris Allegretta committed
2682
	return 1;
2683

2684
    /* First, blank out the line. */
2685
    blank_row(edit, line, 0, COLS);
2686

2687
2688
    /* Next, convert variables that index the line to their equivalent
     * positions in the expanded line. */
2689
#ifndef NANO_TINY
2690
2691
2692
    if (ISSET(SOFTWRAP))
	index = 0;
    else
2693
#endif
2694
	index = strnlenpt(fileptr->data, index);
2695
    page_start = get_page_start(index);
2696

2697
2698
    /* Expand the line, replacing tabs with spaces, and control
     * characters with their displayed forms. */
2699
    converted = display_string(fileptr->data, page_start, editwincols, TRUE);
2700
#ifdef DEBUG
2701
    if (ISSET(SOFTWRAP) && strlen(converted) >= editwincols - 2)
2702
	fprintf(stderr, "update_line(): converted(1) line = %s\n", converted);
2703
2704
#endif

2705
    /* Paint the line. */
2706
    edit_draw(fileptr, converted, line, page_start);
2707
    free(converted);
Chris Allegretta's avatar
Chris Allegretta committed
2708

2709
#ifndef NANO_TINY
2710
    if (!ISSET(SOFTWRAP)) {
2711
#endif
2712
	if (page_start > 0)
2713
	    mvwaddch(edit, line, margin, '$');
2714
	if (strlenpt(fileptr->data) > page_start + editwincols)
2715
	    mvwaddch(edit, line, COLS - 1, '$');
2716
#ifndef NANO_TINY
2717
    } else {
2718
	size_t full_length = strlenpt(fileptr->data);
2719
	for (index += editwincols; index <= full_length && line < editwinrows - 1; index += editwincols) {
2720
2721
	    line++;
#ifdef DEBUG
2722
	    fprintf(stderr, "update_line(): softwrap code, moving to %d index %lu\n", line, (unsigned long)index);
2723
#endif
2724
	    blank_row(edit, line, 0, COLS);
2725
2726

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

	    /* Paint the line. */
	    edit_draw(fileptr, converted, line, index);
2736
	    free(converted);
2737
2738
2739
	    extralinesused++;
	}
    }
2740
#endif /* !NANO_TINY */
2741
    return extralinesused;
Chris Allegretta's avatar
Chris Allegretta committed
2742
2743
}

2744
2745
2746
/* 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)
2747
{
2748
#ifndef NANO_TINY
2749
2750
2751
    if (openfile->mark_set)
	return TRUE;
    else
2752
#endif
2753
	return (get_page_start(old_column) != get_page_start(new_column));
2754
2755
}

2756
2757
/* When edittop changes, try and figure out how many lines we really
 * have to work with, accounting for softwrap mode. */
2758
void compute_maxlines(void)
2759
{
2760
2761
#ifndef NANO_TINY
    if (ISSET(SOFTWRAP)) {
2762
2763
	int screenrow;
	filestruct *line = openfile->edittop;
2764

2765
	maxlines = 0;
2766
2767
2768
2769

	for (screenrow = 0; screenrow < editwinrows && line != NULL; screenrow++) {
	    screenrow += strlenpt(line->data) / editwincols;
	    line = line->next;
2770
	    maxlines++;
2771
	}
2772

2773
	if (screenrow < editwinrows)
2774
	    maxlines += editwinrows - screenrow;
2775

2776
#ifdef DEBUG
2777
	fprintf(stderr, "recomputed: maxlines = %d\n", maxlines);
2778
#endif
2779
2780
    } else
#endif /* !NANO_TINY */
2781
	maxlines = editwinrows;
2782
2783
}

2784
2785
/* Scroll the edit window in the given direction and the given number
 * of lines, and draw new lines on the blank lines left after the
2786
2787
 * scrolling.  direction is the direction to scroll, either UPWARD or
 * DOWNWARD, and nlines is the number of lines to scroll.  We change
2788
2789
 * edittop, and assume that current and current_x are up to date.  We
 * also assume that scrollok(edit) is FALSE. */
2790
void edit_scroll(scroll_dir direction, ssize_t nlines)
2791
{
2792
    ssize_t i;
2793
    filestruct *foo;
2794

2795
2796
2797
    /* Part 1: nlines is the number of lines we're going to scroll the
     * text of the edit window. */

2798
    /* Move the top line of the edit window up or down (depending on the
2799
2800
     * value of direction) nlines lines, or as many lines as we can if
     * there are fewer than nlines lines available. */
2801
    for (i = nlines; i > 0; i--) {
2802
	if (direction == UPWARD) {
2803
	    if (openfile->edittop == openfile->fileage)
2804
		break;
2805
	    openfile->edittop = openfile->edittop->prev;
2806
	} else {
2807
	    if (openfile->edittop == openfile->filebot)
2808
		break;
2809
	    openfile->edittop = openfile->edittop->next;
2810
	}
2811
2812

#ifndef NANO_TINY
2813
	/* Don't over-scroll on long lines. */
2814
	if (ISSET(SOFTWRAP) && direction == UPWARD) {
2815
	    ssize_t len = strlenpt(openfile->edittop->data) / editwincols;
2816
	    i -= len;
2817
	    if (len > 0)
2818
		refresh_needed = TRUE;
2819
	}
2820
#endif
2821
2822
    }

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

2826
2827
2828
2829
    /* Don't bother scrolling zero lines, nor more than the window can hold. */
    if (nlines == 0)
	return;
    if (nlines >= editwinrows)
2830
	refresh_needed = TRUE;
2831

2832
    if (refresh_needed == TRUE)
2833
	return;
2834
2835
2836

    /* Scroll the text of the edit window up or down nlines lines,
     * depending on the value of direction. */
2837
    scrollok(edit, TRUE);
2838
    wscrl(edit, (direction == UPWARD) ? -nlines : nlines);
2839
2840
    scrollok(edit, FALSE);

2841
2842
2843
    /* Part 2: nlines is the number of lines in the scrolled region of
     * the edit window that we need to draw. */

2844
2845
2846
2847
2848
2849
    /* If the scrolled region contains only one line, and the line
     * before it is visible in the edit window, we need to draw it too.
     * If the scrolled region contains more than one line, and the lines
     * before and after the scrolled region are visible in the edit
     * window, we need to draw them too. */
    nlines += (nlines == 1) ? 1 : 2;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2850

2851
2852
    if (nlines > editwinrows)
	nlines = editwinrows;
2853

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

2857
    /* If we scrolled down, move down to the line before the scrolled region. */
2858
    if (direction == DOWNWARD) {
2859
	for (i = editwinrows - nlines; i > 0 && foo != NULL; i--)
2860
2861
2862
	    foo = foo->next;
    }

2863
2864
2865
2866
2867
2868
    /* Draw new lines on any blank lines before or inside the scrolled
     * region.  If we scrolled down and we're on the top line, or if we
     * scrolled up and we're on the bottom line, the line won't be
     * blank, so we don't need to draw it unless the mark is on or we're
     * not on the first page. */
    for (i = nlines; i > 0 && foo != NULL; i--) {
2869
2870
	if ((i == nlines && direction == DOWNWARD) ||
			(i == 1 && direction == UPWARD)) {
2871
	    if (need_horizontal_scroll(openfile->placewewant, 0))
2872
2873
2874
2875
		update_line(foo, (foo == openfile->current) ?
			openfile->current_x : 0);
	} else
	    update_line(foo, (foo == openfile->current) ?
2876
		openfile->current_x : 0);
2877
	foo = foo->next;
2878
    }
2879

2880
    compute_maxlines();
2881
2882
2883
}

/* Update any lines between old_current and current that need to be
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2884
 * updated.  Use this if we've moved without changing any text. */
2885
void edit_redraw(filestruct *old_current)
2886
{
2887
2888
2889
2890
    size_t was_pww = openfile->placewewant;

    openfile->placewewant = xplustabs();

2891
    /* If the current line is offscreen, scroll until it's onscreen. */
2892
    if (openfile->current->lineno >= openfile->edittop->lineno + maxlines ||
2893
#ifndef NANO_TINY
2894
		(openfile->current->lineno == openfile->edittop->lineno + maxlines - 1 &&
2895
		ISSET(SOFTWRAP) && strlenpt(openfile->current->data) >= editwincols) ||
2896
#endif
2897
		openfile->current->lineno < openfile->edittop->lineno) {
2898
	adjust_viewport((focusing || !ISSET(SMOOTH_SCROLL)) ? CENTERING : FLOWING);
2899
	refresh_needed = TRUE;
2900
	return;
2901
    }
2902

2903
#ifndef NANO_TINY
2904
2905
2906
    /* If the mark is on, update all lines between old_current and current. */
    if (openfile->mark_set) {
	filestruct *foo = old_current;
2907

2908
	while (foo != openfile->current) {
2909
	    update_line(foo, 0);
2910

2911
2912
2913
	    foo = (foo->lineno > openfile->current->lineno) ?
			foo->prev : foo->next;
	}
2914
2915
2916
2917
2918
2919
    } 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);
2920
2921
2922

    /* Update current if we've changed page, or if it differs from
     * old_current and needs to be horizontally scrolled. */
2923
    if (need_horizontal_scroll(was_pww, openfile->placewewant) ||
2924
			(old_current != openfile->current &&
2925
			get_page_start(openfile->placewewant) > 0))
2926
	update_line(openfile->current, openfile->current_x);
2927
2928
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2929
2930
/* 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
2931
2932
void edit_refresh(void)
{
2933
2934
    filestruct *line;
    int row = 0;
2935

2936
2937
    /* Figure out what maxlines should really be. */
    compute_maxlines();
2938

2939
    /* If the current line is out of view, get it back on screen. */
2940
    if (openfile->current->lineno < openfile->edittop->lineno ||
2941
		openfile->current->lineno >= openfile->edittop->lineno + maxlines) {
2942
#ifdef DEBUG
2943
2944
	fprintf(stderr, "edit-refresh: line = %ld, edittop = %ld and maxlines = %d\n",
		(long)openfile->current->lineno, (long)openfile->edittop->lineno, maxlines);
2945
#endif
2946
	adjust_viewport((focusing || !ISSET(SMOOTH_SCROLL)) ? CENTERING : STATIONARY);
2947
    }
Chris Allegretta's avatar
Chris Allegretta committed
2948

2949
#ifdef DEBUG
2950
    fprintf(stderr, "edit-refresh: now edittop = %ld\n", (long)openfile->edittop->lineno);
2951
#endif
2952

2953
2954
2955
2956
2957
2958
2959
2960
    line = openfile->edittop;

    while (row < editwinrows && line != NULL) {
	if (line == openfile->current)
	    row += update_line(line, openfile->current_x) + 1;
	else
	    row += update_line(line, 0) + 1;
	line = line->next;
2961
2962
    }

2963
    while (row < editwinrows)
2964
	blank_row(edit, row++, 0, COLS);
2965
2966

    reset_cursor();
2967
    wnoutrefresh(edit);
2968
2969

    refresh_needed = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
2970
2971
}

2972
2973
2974
2975
2976
/* 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. */
2977
void adjust_viewport(update_type manner)
Chris Allegretta's avatar
Chris Allegretta committed
2978
{
2979
    int goal = 0;
2980

2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
    /* If manner is CENTERING, move edittop half the number of window
     * lines back from current.  If manner is STATIONARY, move edittop
     * back current_y lines if current_y is in range of the screen,
     * 0 lines if current_y is below zero, or (editwinrows - 1) lines
     * if current_y is too big.  This puts current at the same place
     * on the screen as before, or at the top or bottom if current_y is
     * beyond either.  If manner is FLOWING, move edittop back 0 lines
     * or (editwinrows - 1) lines, depending or where current has moved.
     * This puts the cursor on the first or the last line. */
    if (manner == CENTERING)
2991
	goal = editwinrows / 2;
2992
    else if (manner == FLOWING) {
2993
	if (openfile->current->lineno >= openfile->edittop->lineno) {
2994
	    goal = editwinrows - 1;
2995
2996
#ifndef NANO_TINY
	    if (ISSET(SOFTWRAP))
2997
		goal -= strlenpt(openfile->current->data) / editwincols;
2998
2999
#endif
	}
3000
    } else {
3001
	goal = openfile->current_y;
3002

3003
	/* Limit goal to (editwinrows - 1) lines maximum. */
3004
3005
	if (goal > editwinrows - 1)
	    goal = editwinrows - 1;
Chris Allegretta's avatar
Chris Allegretta committed
3006
    }
3007

3008
3009
3010
3011
    openfile->edittop = openfile->current;

    while (goal > 0 && openfile->edittop->prev != NULL) {
	openfile->edittop = openfile->edittop->prev;
3012
	goal--;
3013
#ifndef NANO_TINY
3014
	if (ISSET(SOFTWRAP)) {
3015
	    goal -= strlenpt(openfile->edittop->data) / editwincols;
3016
3017
3018
	    if (goal < 0)
		openfile->edittop = openfile->edittop->next;
	}
3019
#endif
3020
    }
3021
#ifdef DEBUG
3022
    fprintf(stderr, "adjust_viewport(): setting edittop to lineno %ld\n", (long)openfile->edittop->lineno);
3023
#endif
3024
    compute_maxlines();
Chris Allegretta's avatar
Chris Allegretta committed
3025
3026
}

3027
/* Unconditionally redraw the entire screen. */
3028
void total_redraw(void)
3029
{
3030
3031
3032
3033
3034
3035
#ifdef USE_SLANG
    /* Slang curses emulation brain damage, part 4: Slang doesn't define
     * curscr. */
    SLsmg_touch_screen();
    SLsmg_refresh();
#else
3036
    wrefresh(curscr);
3037
#endif
3038
3039
}

3040
3041
/* Unconditionally redraw the entire screen, and then refresh it using
 * the current file. */
3042
3043
void total_refresh(void)
{
3044
    total_redraw();
3045
    titlebar(NULL);
3046
    edit_refresh();
3047
    bottombars(currmenu);
3048
3049
}

3050
3051
/* Display the main shortcut list on the last two rows of the bottom
 * portion of the window. */
3052
3053
void display_main_list(void)
{
3054
#ifndef DISABLE_COLOR
3055
3056
    if (openfile->syntax &&
		(openfile->syntax->formatter || openfile->syntax->linter))
3057
	set_lint_or_format_shortcuts();
3058
3059
3060
3061
    else
	set_spell_shortcuts();
#endif

3062
    bottombars(MMAIN);
3063
3064
}

3065
/* If constant is TRUE, we display the current cursor position only if
3066
3067
 * suppress_cursorpos is FALSE.  If constant is FALSE, we display the
 * position always.  In any case we reset suppress_cursorpos to FALSE. */
3068
void do_cursorpos(bool constant)
Chris Allegretta's avatar
Chris Allegretta committed
3069
{
3070
3071
    char saved_byte;
    size_t sum, cur_xpt = xplustabs() + 1;
3072
    size_t cur_lenpt = strlenpt(openfile->current->data) + 1;
3073
    int linepct, colpct, charpct;
Chris Allegretta's avatar
Chris Allegretta committed
3074

3075
    assert(openfile->fileage != NULL && openfile->current != NULL);
3076

3077
    /* Determine the size of the file up to the cursor. */
3078
    saved_byte = openfile->current->data[openfile->current_x];
3079
    openfile->current->data[openfile->current_x] = '\0';
3080

3081
    sum = get_totsize(openfile->fileage, openfile->current);
3082

3083
    openfile->current->data[openfile->current_x] = saved_byte;
3084
3085
3086

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

3089
    /* If the position needs to be suppressed, don't suppress it next time. */
3090
    if (suppress_cursorpos && constant) {
3091
	suppress_cursorpos = FALSE;
3092
	return;
3093
    }
Chris Allegretta's avatar
Chris Allegretta committed
3094

3095
    /* Display the current cursor position on the statusbar. */
3096
    linepct = 100 * openfile->current->lineno / openfile->filebot->lineno;
3097
    colpct = 100 * cur_xpt / cur_lenpt;
3098
    charpct = (openfile->totsize == 0) ? 0 : 100 * sum / openfile->totsize;
3099

3100
    statusline(HUSH,
3101
	_("line %ld/%ld (%d%%), col %lu/%lu (%d%%), char %lu/%lu (%d%%)"),
3102
	(long)openfile->current->lineno,
3103
	(long)openfile->filebot->lineno, linepct,
3104
	(unsigned long)cur_xpt, (unsigned long)cur_lenpt, colpct,
3105
	(unsigned long)sum, (unsigned long)openfile->totsize, charpct);
3106
3107
3108

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

3111
/* Unconditionally display the current cursor position. */
3112
void do_cursorpos_void(void)
3113
{
3114
    do_cursorpos(FALSE);
3115
3116
}

3117
3118
void enable_nodelay(void)
{
3119
3120
    nodelay_mode = TRUE;
    nodelay(edit, TRUE);
3121
3122
3123
3124
}

void disable_nodelay(void)
{
3125
3126
    nodelay_mode = FALSE;
    nodelay(edit, FALSE);
3127
3128
}

3129
3130
/* Highlight the current word being replaced or spell checked.  We
 * expect word to have tabs and control characters expanded. */
3131
void spotlight(bool active, const char *word)
Chris Allegretta's avatar
Chris Allegretta committed
3132
{
3133
    size_t word_len = strlenpt(word), room;
Chris Allegretta's avatar
Chris Allegretta committed
3134

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

3138
    assert(room > 0);
3139

3140
3141
    if (word_len > room)
	room--;
3142

Chris Allegretta's avatar
Chris Allegretta committed
3143
    reset_cursor();
Chris Allegretta's avatar
Chris Allegretta committed
3144

3145
    if (active)
3146
	wattron(edit, hilite_attribute);
Chris Allegretta's avatar
Chris Allegretta committed
3147

3148
    /* This is so we can show zero-length matches. */
3149
    if (word_len == 0)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3150
	waddch(edit, ' ');
3151
    else
3152
	waddnstr(edit, word, actual_x(word, room));
3153

3154
    if (word_len > room)
3155
	waddch(edit, '$');
Chris Allegretta's avatar
Chris Allegretta committed
3156

3157
    if (active)
3158
	wattroff(edit, hilite_attribute);
3159
3160

    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
3161
3162
}

3163
#ifndef DISABLE_EXTRA
3164
3165
#define CREDIT_LEN 54
#define XLCREDIT_LEN 9
3166

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3167
3168
/* Easter egg: Display credits.  Assume nodelay(edit) and scrollok(edit)
 * are FALSE. */
3169
3170
void do_credits(void)
{
3171
3172
    bool old_more_space = ISSET(MORE_SPACE);
    bool old_no_help = ISSET(NO_HELP);
3173
    int kbinput = ERR, crpos = 0, xlpos = 0;
3174
3175
3176
    const char *credits[CREDIT_LEN] = {
	NULL,				/* "The nano text editor" */
	NULL,				/* "version" */
Chris Allegretta's avatar
Chris Allegretta committed
3177
3178
	VERSION,
	"",
3179
	NULL,				/* "Brought to you by:" */
Chris Allegretta's avatar
Chris Allegretta committed
3180
3181
3182
3183
3184
	"Chris Allegretta",
	"Jordi Mallach",
	"Adam Rogoyski",
	"Rob Siemborski",
	"Rocco Corsi",
3185
	"David Lawrence Ramsey",
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3186
	"David Benbennick",
Benno Schulenberg's avatar
Benno Schulenberg committed
3187
	"Mark Majeres",
3188
	"Mike Frysinger",
3189
	"Benno Schulenberg",
Chris Allegretta's avatar
Chris Allegretta committed
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
	"Ken Tyler",
	"Sven Guckes",
	"Bill Soudan",
	"Christian Weisgerber",
	"Erik Andersen",
	"Big Gaute",
	"Joshua Jensen",
	"Ryan Krebs",
	"Albert Chin",
	"",
3200
	NULL,				/* "Special thanks to:" */
3201
	"Monique, Brielle & Joseph",
Chris Allegretta's avatar
Chris Allegretta committed
3202
3203
3204
3205
3206
3207
	"Plattsburgh State University",
	"Benet Laboratories",
	"Amy Allegretta",
	"Linda Young",
	"Jeremy Robichaud",
	"Richard Kolb II",
3208
	NULL,				/* "The Free Software Foundation" */
Chris Allegretta's avatar
Chris Allegretta committed
3209
	"Linus Torvalds",
3210
	NULL,				/* "the many translators and the TP" */
3211
	NULL,				/* "For ncurses:" */
3212
3213
3214
3215
	"Thomas Dickey",
	"Pavel Curtis",
	"Zeyd Ben-Halim",
	"Eric S. Raymond",
3216
3217
3218
3219
3220
3221
	NULL,				/* "and anyone else we forgot..." */
	NULL,				/* "Thank you for using nano!" */
	"",
	"",
	"",
	"",
3222
	"(C) 1999 - 2016",
3223
	"Free Software Foundation, Inc.",
3224
3225
3226
3227
	"",
	"",
	"",
	"",
3228
	"https://nano-editor.org/"
3229
3230
    };

3231
    const char *xlcredits[XLCREDIT_LEN] = {
3232
3233
3234
3235
3236
	N_("The nano text editor"),
	N_("version"),
	N_("Brought to you by:"),
	N_("Special thanks to:"),
	N_("The Free Software Foundation"),
3237
	N_("the many translators and the TP"),
3238
3239
3240
	N_("For ncurses:"),
	N_("and anyone else we forgot..."),
	N_("Thank you for using nano!")
3241
    };
3242

3243
3244
3245
3246
3247
3248
    if (!old_more_space || !old_no_help) {
	SET(MORE_SPACE);
	SET(NO_HELP);
	window_init();
    }

3249
3250
    curs_set(0);
    nodelay(edit, TRUE);
3251

3252
    blank_titlebar();
Chris Allegretta's avatar
Chris Allegretta committed
3253
    blank_edit();
3254
    blank_statusbar();
3255

3256
    wrefresh(topwin);
Chris Allegretta's avatar
Chris Allegretta committed
3257
    wrefresh(edit);
3258
    wrefresh(bottomwin);
3259
    napms(700);
3260

3261
    for (crpos = 0; crpos < CREDIT_LEN + editwinrows / 2; crpos++) {
3262
	if ((kbinput = wgetch(edit)) != ERR)
3263
	    break;
3264

3265
	if (crpos < CREDIT_LEN) {
3266
	    const char *what;
3267
	    size_t start_col;
3268

3269
	    if (credits[crpos] == NULL) {
3270
		assert(0 <= xlpos && xlpos < XLCREDIT_LEN);
3271

3272
		what = _(xlcredits[xlpos]);
3273
		xlpos++;
3274
	    } else
3275
		what = credits[crpos];
3276

3277
	    start_col = COLS / 2 - strlenpt(what) / 2 - 1;
3278
	    mvwaddstr(edit, editwinrows - 1 - (editwinrows % 2),
3279
						start_col, what);
3280
	}
3281

3282
3283
3284
3285
	wrefresh(edit);

	if ((kbinput = wgetch(edit)) != ERR)
	    break;
3286
	napms(700);
3287

3288
	scrollok(edit, TRUE);
3289
	wscrl(edit, 1);
3290
	scrollok(edit, FALSE);
3291
	wrefresh(edit);
3292

3293
	if ((kbinput = wgetch(edit)) != ERR)
3294
	    break;
3295
	napms(700);
3296

3297
	scrollok(edit, TRUE);
3298
	wscrl(edit, 1);
3299
	scrollok(edit, FALSE);
3300
	wrefresh(edit);
3301
3302
    }

3303
3304
3305
    if (kbinput != ERR)
	ungetch(kbinput);

3306
    if (!old_more_space)
3307
	UNSET(MORE_SPACE);
3308
    if (!old_no_help)
3309
	UNSET(NO_HELP);
3310
    window_init();
3311

3312
    nodelay(edit, FALSE);
3313

3314
    total_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
3315
}
3316
#endif /* !DISABLE_EXTRA */