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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return input;
290
291
}

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

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

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

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

310
311
312
    return kbinput;
}

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

325
    meta_key = FALSE;
326
    shift_held = FALSE;
327

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

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

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

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

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

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

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

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

		    byte_digits++;
		    byte = get_byte_kbinput(keycode);

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

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

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

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

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

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

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

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

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

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

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

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

697
698
699
    return retval;
}

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

713
714
	switch (seq[3]) {
	    case '2':
715
716
717
718
719
		switch (seq[4]) {
		    case 'A': /* Esc O 1 ; 2 A == Shift-Up on Terminal. */
		    case 'B': /* Esc O 1 ; 2 B == Shift-Down on Terminal. */
		    case 'C': /* Esc O 1 ; 2 C == Shift-Right on Terminal. */
		    case 'D': /* Esc O 1 ; 2 D == Shift-Left on Terminal. */
720
			shift_held = TRUE;
721
722
723
724
725
726
727
728
729
730
			return arrow_from_abcd(seq[4]);
		    case 'P': /* Esc O 1 ; 2 P == F13 on Terminal. */
			return KEY_F(13);
		    case 'Q': /* Esc O 1 ; 2 Q == F14 on Terminal. */
			return KEY_F(14);
		    case 'R': /* Esc O 1 ; 2 R == F15 on Terminal. */
			return KEY_F(15);
		    case 'S': /* Esc O 1 ; 2 S == F16 on Terminal. */
			return KEY_F(16);
		}
731
732
		break;
	    case '5':
733
734
735
736
737
738
739
740
741
742
		switch (seq[4]) {
		    case 'A': /* Esc O 1 ; 5 A == Ctrl-Up on Terminal. */
			return CONTROL_UP;
		    case 'B': /* Esc O 1 ; 5 B == Ctrl-Down on Terminal. */
			return CONTROL_DOWN;
		    case 'C': /* Esc O 1 ; 5 C == Ctrl-Right on Terminal. */
			return CONTROL_RIGHT;
		    case 'D': /* Esc O 1 ; 5 D == Ctrl-Left on Terminal. */
			return CONTROL_LEFT;
		}
743
744
		break;
	}
745

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

919
	switch (seq[3]) {
920
	    case '2':
921
922
923
924
925
		switch (seq[4]) {
		    case 'A': /* Esc [ 1 ; 2 A == Shift-Up on xterm. */
		    case 'B': /* Esc [ 1 ; 2 B == Shift-Down on xterm. */
		    case 'C': /* Esc [ 1 ; 2 C == Shift-Right on xterm. */
		    case 'D': /* Esc [ 1 ; 2 D == Shift-Left on xterm. */
926
			shift_held = TRUE;
927
928
			return arrow_from_abcd(seq[4]);
		}
929
		break;
930
931
932
933
934
935
936
937
938
939
#ifndef NANO_TINY
	    case '4':
		/* When the arrow keys are held together with Shift+Meta,
		 * act as if they are Home/End/PgUp/PgDown with Shift. */
		switch (seq[4]) {
		    case 'A': /* Esc [ 1 ; 4 A == Shift-Alt-Up on xterm. */
			return SHIFT_PAGEUP;
		    case 'B': /* Esc [ 1 ; 4 B == Shift-Alt-Down on xterm. */
			return SHIFT_PAGEDOWN;
		    case 'C': /* Esc [ 1 ; 4 C == Shift-Alt-Right on xterm. */
940
			return SHIFT_END;
941
		    case 'D': /* Esc [ 1 ; 4 D == Shift-Alt-Left on xterm. */
942
			return SHIFT_HOME;
943
944
945
		}
		break;
#endif
946
	    case '5':
947
948
949
950
951
952
953
954
955
956
		switch (seq[4]) {
		    case 'A': /* Esc [ 1 ; 5 A == Ctrl-Up on xterm. */
			return CONTROL_UP;
		    case 'B': /* Esc [ 1 ; 5 B == Ctrl-Down on xterm. */
			return CONTROL_DOWN;
		    case 'C': /* Esc [ 1 ; 5 C == Ctrl-Right on xterm. */
			return CONTROL_RIGHT;
		    case 'D': /* Esc [ 1 ; 5 D == Ctrl-Left on xterm. */
			return CONTROL_LEFT;
		}
957
		break;
958
959
960
961
962
963
964
965
966
967
968
969
970
971
#ifndef NANO_TINY
	    case '6':
		switch (seq[4]) {
		    case 'A': /* Esc [ 1 ; 6 A == Shift-Ctrl-Up on xterm. */
			return shiftcontrolup;
		    case 'B': /* Esc [ 1 ; 6 B == Shift-Ctrl-Down on xterm. */
			return shiftcontroldown;
		    case 'C': /* Esc [ 1 ; 6 C == Shift-Ctrl-Right on xterm. */
			return shiftcontrolright;
		    case 'D': /* Esc [ 1 ; 6 D == Shift-Ctrl-Left on xterm. */
			return shiftcontrolleft;
		}
		break;
#endif
972
	}
973
974
975
976

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

1153
    return ERR;
1154
1155
}

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

1174
/* Interpret the escape sequence in the keystroke buffer, the first
1175
1176
 * character of which is kbinput.  Assume that the keystroke buffer
 * isn't empty, and that the initial escape has already been read in. */
1177
int parse_escape_sequence(WINDOW *win, int kbinput)
1178
1179
1180
1181
1182
1183
1184
1185
{
    int retval, *seq;
    size_t seq_len;

    /* Put back the non-escape character, get the complete escape
     * sequence, translate the sequence into its corresponding key
     * value, and save that as the result. */
    unget_input(&kbinput, 1);
1186
    seq_len = key_buffer_len;
1187
    seq = get_input(NULL, seq_len);
1188
    retval = convert_sequence(seq, seq_len);
1189
1190
1191

    free(seq);

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

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

    return retval;
}

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

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

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

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

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

    return retval;
}

1286
#ifdef ENABLE_UTF8
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1287
/* If the character in kbinput is a valid hexadecimal digit, multiply it
1288
 * by factor and add the result to uni, and return ERR to signify okay. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1289
1290
1291
1292
1293
1294
1295
long add_unicode_digit(int kbinput, long factor, long *uni)
{
    if ('0' <= kbinput && kbinput <= '9')
	*uni += (kbinput - '0') * factor;
    else if ('a' <= tolower(kbinput) && tolower(kbinput) <= 'f')
	*uni += (tolower(kbinput) - 'a' + 10) * factor;
    else
1296
1297
	/* The character isn't hexadecimal; give it as the result. */
	return (long)kbinput;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1298

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

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

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

1314
    switch (uni_digits) {
1315
	case 1:
1316
1317
1318
1319
	    /* The first digit must be zero or one.  Put it in the
	     * 0x100000's position of the Unicode sequence holder.
	     * Otherwise, return the character itself as the result. */
	    if (kbinput == '0' || kbinput == '1')
1320
		uni = (kbinput - '0') * 0x100000;
1321
1322
1323
1324
	    else
		retval = kbinput;
	    break;
	case 2:
1325
1326
1327
	    /* The second digit must be zero if the first was one, but
	     * may be any hexadecimal value if the first was zero. */
	    if (kbinput == '0' || uni == 0)
1328
		retval = add_unicode_digit(kbinput, 0x10000, &uni);
1329
1330
1331
1332
	    else
		retval = kbinput;
	    break;
	case 3:
1333
	    /* Later digits may be any hexadecimal value. */
1334
	    retval = add_unicode_digit(kbinput, 0x1000, &uni);
1335
	    break;
1336
	case 4:
1337
	    retval = add_unicode_digit(kbinput, 0x100, &uni);
1338
	    break;
1339
	case 5:
1340
	    retval = add_unicode_digit(kbinput, 0x10, &uni);
1341
	    break;
1342
	case 6:
1343
	    retval = add_unicode_digit(kbinput, 0x1, &uni);
1344
1345
	    /* If also the sixth digit was a valid hexadecimal value, then
	     * the Unicode sequence is complete, so return it. */
1346
	    if (retval == ERR)
1347
		retval = uni;
1348
1349
	    break;
    }
1350

1351
    /* Show feedback only when editing, not when at a prompt. */
1352
    if (retval == ERR && win == edit) {
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
	char partial[7] = "......";

	/* Construct the partial result, right-padding it with dots. */
	snprintf(partial, uni_digits + 1, "%06lX", uni);
	partial[uni_digits] = '.';

	/* TRANSLATORS: This is shown while a six-digit hexadecimal
	 * Unicode character code (%s) is being typed in. */
	statusline(HUSH, _("Unicode Input: %s"), partial);
    }
1363

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

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

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

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

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

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

1408
1409
    return retval;
}
1410

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

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

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

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

1426
    unget_input(input, output_len);
1427

1428
    free(input);
1429
1430
}

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

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

1445
    /* Read in one keycode, or one or two escapes. */
1446
    retval = parse_verbatim_kbinput(win, kbinput_len);
1447

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

1454
    /* Turn flow control characters back on if necessary and turn the
1455
     * keypad back on if necessary now that we're done. */
1456
1457
    if (ISSET(PRESERVE))
	enable_flow_control();
1458
1459
1460
1461
1462
1463
    /* Use the global window pointers, because a resize may have freed
     * the data that the win parameter points to. */
    if (!ISSET(REBIND_KEYPAD)) {
	keypad(edit, TRUE);
	keypad(bottomwin, TRUE);
    }
1464

1465
    return retval;
1466
1467
}

1468
1469
1470
1471
/* Read in one control character (or an iTerm/Eterm/rxvt double Escape),
 * or convert a series of six digits into a Unicode codepoint.  Return
 * in count either 1 (for a control character or the first byte of a
 * multibyte sequence), or 2 (for an iTerm/Eterm/rxvt double Escape). */
1472
int *parse_verbatim_kbinput(WINDOW *win, size_t *count)
1473
{
1474
    int *kbinput;
1475

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

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

1489
1490
    *count = 1;

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

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

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

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

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

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

1528
1529
    free(kbinput);

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

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

1538
#ifndef DISABLE_MOUSE
1539
/* Handle any mouse event that may have occurred.  We currently handle
1540
1541
1542
1543
1544
1545
1546
 * releases/clicks of the first mouse button.  If allow_shortcuts is
 * TRUE, releasing/clicking on a visible shortcut will put back the
 * keystroke associated with that shortcut.  If NCURSES_MOUSE_VERSION is
 * at least 2, we also currently handle presses of the fourth mouse
 * button (upward rolls of the mouse wheel) by putting back the
 * keystrokes to move up, and presses of the fifth mouse button
 * (downward rolls of the mouse wheel) by putting back the keystrokes to
1547
1548
1549
1550
1551
1552
 * move down.  We also store the coordinates of a mouse event that needs
 * to be handled in mouse_x and mouse_y, relative to the entire screen.
 * Return -1 on error, 0 if the mouse event needs to be handled, 1 if
 * it's been handled by putting back keystrokes that need to be handled.
 * or 2 if it's been ignored.  Assume that KEY_MOUSE has already been
 * read in. */
1553
int get_mouseinput(int *mouse_x, int *mouse_y, bool allow_shortcuts)
1554
1555
{
    MEVENT mevent;
1556
    bool in_bottomwin;
1557
    subnfunc *f;
1558
1559
1560
1561
1562
1563

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

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

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

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

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

1588
1589
1590
1591
1592
1593
1594
1595
1596
	    /* Translate the mouse event coordinates so that they're
	     * relative to bottomwin. */
	    wmouse_trafo(bottomwin, mouse_y, mouse_x, FALSE);

	    /* Handle releases/clicks of the first mouse button on the
	     * statusbar elsewhere. */
	    if (*mouse_y == 0) {
		/* Restore the untranslated mouse event coordinates, so
		 * that they're relative to the entire screen again. */
1597
		*mouse_x = mevent.x - margin;
1598
1599
1600
1601
		*mouse_y = mevent.y;

		return 0;
	    }
1602

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

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

1609
1610
1611
	    /* Calculate the width of all of the shortcuts in the list
	     * except for the last two, which are longer by (COLS % i)
	     * columns so as to not waste space. */
1612
	    if (number < 2)
1613
1614
		i = COLS / (MAIN_VISIBLE / 2);
	    else
1615
		i = COLS / ((number / 2) + (number % 2));
1616

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

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

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

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

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

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

1673
1674
1675
1676
1677
	    /* One upward roll of the mouse wheel is equivalent to
	     * moving up three lines, and one downward roll of the mouse
	     * wheel is equivalent to moving down three lines. */
	    for (i = 0; i < 3; i++)
		unget_kbinput((mevent.bstate & BUTTON4_PRESSED) ?
1678
				KEY_PPAGE : KEY_NPAGE, FALSE);
1679
1680
1681
1682
1683
1684
1685

	    return 1;
	} else
	    /* Ignore presses of the fourth mouse button and presses of
	     * the fifth mouse buttons that aren't on the edit window or
	     * the statusbar. */
	    return 2;
1686
1687
    }
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1688
1689
1690

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

1694
1695
1696
1697
/* Return the shortcut that corresponds to the values of kbinput (the
 * key itself) and meta_key (whether the key is a meta sequence).  The
 * returned shortcut will be the first in the list that corresponds to
 * the given sequence. */
1698
const sc *get_shortcut(int *kbinput)
1699
{
1700
    sc *s;
1701

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

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

    return NULL;
}

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

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

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

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

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

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

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

1766
1767
/* Check if the number of keystrokes needed to blank the statusbar has
 * been pressed.  If so, blank the statusbar, unless constant cursor
1768
 * position display is on and we are in the editing screen. */
1769
void check_statusblank(void)
Chris Allegretta's avatar
Chris Allegretta committed
1770
{
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
    if (statusblank == 0)
	return;

    statusblank--;

    /* When editing and 'constantshow' is active, skip the blanking. */
    if (currmenu == MMAIN && ISSET(CONST_UPDATE))
	return;

    if (statusblank == 0) {
	blank_statusbar();
	wnoutrefresh(bottomwin);
	reset_cursor();
	wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
1785
    }
1786
1787
1788
1789

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

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

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

1816
    assert(column <= start_col);
1817

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

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

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

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

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

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

1856
	if (*buf == ' ') {
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
	    /* Show a space as a visible character, or as a space. */
#ifndef NANO_TINY
	    if (ISSET(WHITESPACE_DISPLAY)) {
		int i = whitespace_len[0];

		while (i < whitespace_len[0] + whitespace_len[1])
		    converted[index++] = whitespace[i++];
	    } else
#endif
		converted[index++] = ' ';
	    start_col++;
1868
1869
	    buf++;
	    continue;
1870
	} else if (*buf == '\t') {
1871
	    /* Show a tab as a visible character, or as as a space. */
1872
#ifndef NANO_TINY
1873
	    if (ISSET(WHITESPACE_DISPLAY)) {
1874
		int i = 0;
1875

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

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

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

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

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

1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
	/* Represent an invalid sequence with the Replacement Character. */
	converted[index++] = '\xEF';
	converted[index++] = '\xBF';
	converted[index++] = '\xBD';

	start_col += 1;
	buf++;

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

1928
1929
    /* If there is more text than can be shown, make room for the $. */
    if (*buf != '\0' && isdata && !ISSET(SOFTWRAP))
1930
	index = move_mbleft(converted, index);
1931

1932
1933
    /* Null-terminate the converted string. */
    converted[index] = '\0';
1934

1935
    return converted;
1936
1937
}

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

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

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

1965
    wattron(topwin, interface_color_pair[TITLE_BAR]);
1966

1967
    blank_titlebar();
1968
    as_an_at = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
1969

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

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

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

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

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

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

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

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

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

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

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

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

2068
2069
2070
2071
2072
2073
2074
2075
2076
/* 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);
}

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

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

    va_start(ap, msg);

    /* Curses mode is turned off.  If we use wmove() now, it will muck
     * up the terminal settings.  So we just use vfprintf(). */
2097
    if (isendwin()) {
2098
2099
2100
2101
2102
	vfprintf(stderr, msg, ap);
	va_end(ap);
	return;
    }

2103
2104
2105
2106
2107
    /* If there already was an alert message, ignore lesser ones. */
    if ((lastmessage == ALERT && importance != ALERT) ||
		(lastmessage == MILD && importance == HUSH))
	return;

2108
2109
2110
2111
2112
2113
    /* If the ALERT status has been reset, reset the counter. */
    if (lastmessage == HUSH)
	alerts = 0;

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

2117
    if (importance == ALERT) {
2118
	if (++alerts > 3 && !ISSET(NO_PAUSES))
2119
	    msg = _("Further warnings were suppressed");
2120
	beep();
2121
    }
2122
2123

    lastmessage = importance;
2124

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

2128
2129
    blank_statusbar();

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

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

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

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

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

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

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

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

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

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

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

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

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

2196
    blank_bottombars();
2197

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

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

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

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

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

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

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

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

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

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

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

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

2271
2272
	row -= (openfile->firstcolumn / editwincols);

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

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

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

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

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

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

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

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

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

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

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

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

2368
2369
2370
	    /* 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. */
2371

2372
2373
	    wattron(edit, varnish->attributes);

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

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

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

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

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

2410
		    thetext = converted + actual_x(converted, start_col);
2411

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

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

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

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

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

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

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

2459
2460
2461
2462
2463
2464
2465
	    /* 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 &&
2466
2467
			(start_line->multidata[varnish->id] == CBEGINBEFORE ||
			start_line->multidata[varnish->id] == CSTARTENDHERE))
2468
		goto step_two;
2469

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

2494
  seek_an_end:
2495
2496
	    /* We've already checked that there is no end between the start
	     * and the current line.  But is there an end after the start
2497
2498
2499
2500
2501
	     * 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;

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

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

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

2523
  step_two:
2524
2525
2526
	    /* 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;
2527

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

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

2540
		thetext = converted + actual_x(converted, start_col);
2541

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

2556
			mvwaddnstr(edit, row, margin + start_col,
2557
						thetext, paintlen);
2558

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

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

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

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

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

2596
#ifndef NANO_TINY
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
    /* 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. */
2608
	int start_col;
2609
2610
2611
2612
2613
	    /* 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". */
2614

2615
	mark_order(&top, &top_x, &bot, &bot_x, NULL);
2616

2617
2618
2619
2620
	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
2621

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

2627
2628
2629
	    if (start_col < 0)
		start_col = 0;

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

2632
2633
2634
2635
2636
2637
	    /* 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);
	    }
2638

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

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

2662
#ifndef NANO_TINY
2663
2664
    if (ISSET(SOFTWRAP))
	return update_softwrapped_line(fileptr);
2665
#endif
2666
2667

    row = fileptr->lineno - openfile->edittop->lineno;
2668

2669
    /* If the line is offscreen, don't even try to display it. */
2670
    if (row < 0 || row >= editwinrows) {
2671
	statusline(ALERT, "Badness: tried to display a line on row %i"
2672
				" -- please report a bug", row);
2673
	return 0;
2674
    }
2675

2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
    /* First, blank out the row. */
    blank_row(edit, row, 0, COLS);

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

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

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

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

2695
    return 1;
Chris Allegretta's avatar
Chris Allegretta committed
2696
2697
}

2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
#ifndef NANO_TINY
/* Redraw all the chunks of the given line (as far as they fit onscreen),
 * unless it's edittop, which will be displayed from column firstcolumn.
 * Return the number of additional rows consumed. */
int update_softwrapped_line(filestruct *fileptr)
{
    int row = 0;
	/* The row in the edit window we will write to. */
    filestruct *line = openfile->edittop;
	/* An iterator needed to find the relevant row. */
    int starting_row;
	/* The first row in the edit window that gets updated. */
    size_t from_col = 0;
	/* The starting column of the current chunk. */
    char *converted;
	/* The data of the chunk with tabs and control characters expanded. */
    size_t full_length;
	/* The length of the expanded line. */

    if (fileptr == openfile->edittop)
	from_col = openfile->firstcolumn;
    else
	row -= (openfile->firstcolumn / editwincols);

    /* Find out on which screen row the target line should be shown. */
    while (line != fileptr && line != NULL) {
	row += (strlenpt(line->data) / editwincols) + 1;
	line = line->next;
    }

2728
2729
2730
2731
    /* If the first chunk is offscreen, don't even try to display it. */
    if (row < 0 || row >= editwinrows) {
	statusline(ALERT, "Badness: tried to display a chunk on row %i"
				" -- please report a bug", row);
2732
	return 0;
2733
    }
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752

    full_length = strlenpt(fileptr->data);
    starting_row = row;

    while (from_col <= full_length && row < editwinrows) {
	blank_row(edit, row, 0, COLS);

	/* Convert the chunk to its displayable form and draw it. */
	converted = display_string(fileptr->data, from_col, editwincols, TRUE);
	edit_draw(fileptr, converted, row++, from_col);
	free(converted);

	from_col += editwincols;
    }

    return (row - starting_row);
}
#endif

2753
2754
2755
2756
/* Check whether the mark is on, or whether old_column and new_column are on
 * different "pages" (in softwrap mode, only the former applies), which means
 * that the relevant line needs to be redrawn. */
bool line_needs_update(const size_t old_column, const size_t new_column)
2757
{
2758
#ifndef NANO_TINY
2759
2760
2761
    if (openfile->mark_set)
	return TRUE;
    else
2762
#endif
2763
	return (get_page_start(old_column) != get_page_start(new_column));
2764
2765
}

2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
/* Try to move up nrows softwrapped chunks from the given line and the
 * given column (leftedge).  After moving, leftedge will be set to the
 * starting column of the current chunk.  Return the number of chunks we
 * couldn't move up, which will be zero if we completely succeeded. */
int go_back_chunks(int nrows, filestruct **line, size_t *leftedge)
{
    int i;

    /* Don't move more chunks than the window can hold. */
    if (nrows > editwinrows - 1)
	nrows = (editwinrows < 2) ? 1 : editwinrows - 1;

#ifndef NANO_TINY
    if (ISSET(SOFTWRAP)) {
	size_t current_chunk = (*leftedge) / editwincols;

2782
	/* Recede through the requested number of chunks. */
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
	for (i = nrows; i > 0; i--) {
	    if (current_chunk > 0) {
		current_chunk--;
		continue;
	    }

	    if (*line == openfile->fileage)
		break;

	    *line = (*line)->prev;
	    current_chunk = strlenpt((*line)->data) / editwincols;
	}

	/* Only change leftedge when we actually could move. */
	if (i < nrows)
	    *leftedge = current_chunk * editwincols;
    } else
#endif
	for (i = nrows; i > 0 && (*line)->prev != NULL; i--)
	    *line = (*line)->prev;

    return i;
}

/* Try to move down nrows softwrapped chunks from the given line and the
 * given column (leftedge).  After moving, leftedge will be set to the
 * starting column of the current chunk.  Return the number of chunks we
 * couldn't move down, which will be zero if we completely succeeded. */
int go_forward_chunks(int nrows, filestruct **line, size_t *leftedge)
{
    int i;

    /* Don't move more chunks than the window can hold. */
    if (nrows > editwinrows - 1)
	nrows = (editwinrows < 2) ? 1 : editwinrows - 1;

#ifndef NANO_TINY
    if (ISSET(SOFTWRAP)) {
	size_t current_chunk = (*leftedge) / editwincols;
	size_t last_chunk = strlenpt((*line)->data) / editwincols;

2824
	/* Advance through the requested number of chunks. */
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
	for (i = nrows; i > 0; i--) {
	    if (current_chunk < last_chunk) {
		current_chunk++;
		continue;
	    }

	    if (*line == openfile->filebot)
		break;

	    *line = (*line)->next;
	    current_chunk = 0;
	    last_chunk = strlenpt((*line)->data) / editwincols;
	}

	/* Only change leftedge when we actually could move. */
	if (i < nrows)
	    *leftedge = current_chunk * editwincols;
    } else
#endif
	for (i = nrows; i > 0 && (*line)->next != NULL; i--)
	    *line = (*line)->next;

    return i;
}

2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
/* Return TRUE if there are fewer than a screen's worth of lines between
 * the line at line number was_lineno (and column was_leftedge, if we're
 * in softwrap mode) and the line at current[current_x]. */
bool less_than_a_screenful(size_t was_lineno, size_t was_leftedge)
{
#ifndef NANO_TINY
    if (ISSET(SOFTWRAP)) {
	filestruct *line = openfile->current;
	size_t leftedge = (xplustabs() / editwincols) * editwincols;
	int rows_left = go_back_chunks(editwinrows - 1, &line, &leftedge);

	return (rows_left > 0 || line->lineno < was_lineno ||
		(line->lineno == was_lineno && leftedge <= was_leftedge));
    } else
#endif
	return (openfile->current->lineno - was_lineno < editwinrows);
}

2868
/* Scroll the edit window in the given direction and the given number of rows,
2869
 * and draw new lines on the blank lines left after the scrolling. */
2870
void edit_scroll(scroll_dir direction, int nrows)
2871
{
2872
    filestruct *line;
2873
2874
    size_t leftedge;

2875
2876
    /* Part 1: nrows is the number of rows we're going to scroll the text of
     * the edit window. */
2877

2878
2879
    /* Move the top line of the edit window the requested number of rows up or
     * down, and reduce the number of rows with the amount we couldn't move. */
2880
    if (direction == UPWARD)
2881
	nrows -= go_back_chunks(nrows, &openfile->edittop, &openfile->firstcolumn);
2882
    else
2883
	nrows -= go_forward_chunks(nrows, &openfile->edittop, &openfile->firstcolumn);
2884

2885
2886
    /* Don't bother scrolling zero rows, nor more than the window can hold. */
    if (nrows == 0)
2887
	return;
2888
    if (nrows >= editwinrows) {
2889
	refresh_needed = TRUE;
2890
	return;
2891
    }
2892

2893
    /* Scroll the text of the edit window a number of rows up or down. */
2894
    scrollok(edit, TRUE);
2895
    wscrl(edit, (direction == UPWARD) ? -nrows : nrows);
2896
2897
    scrollok(edit, FALSE);

2898
2899
    /* Part 2: nrows is now the number of rows in the scrolled region of the
     * edit window that we need to draw. */
2900

2901
2902
2903
2904
    /* If we're not on the first "page" (when not softwrapping), or the mark
     * is on, the row next to the scrolled region needs to be redrawn too. */
    if (line_needs_update(openfile->placewewant, 0) && nrows < editwinrows)
	nrows++;
2905

2906
    /* If we scrolled backward, start on the first line of the blank region. */
2907
    line = openfile->edittop;
2908
    leftedge = openfile->firstcolumn;
2909

2910
    /* If we scrolled forward, move down to the start of the blank region. */
2911
2912
    if (direction == DOWNWARD)
	go_forward_chunks(editwinrows - nrows, &line, &leftedge);
2913

2914
#ifndef NANO_TINY
2915
2916
2917
    /* Compensate for the earlier onscreen chunks of a softwrapped line
     * when the first blank row happens to be in the middle of that line. */
    if (ISSET(SOFTWRAP) && line != openfile->edittop)
2918
	nrows += leftedge / editwincols;
2919
#endif
2920
2921
2922

    /* Draw new content on the blank rows inside the scrolled region
     * (and on the bordering row too when it was deemed necessary). */
2923
2924
2925
    while (nrows > 0 && line != NULL) {
	nrows -= update_line(line, (line == openfile->current) ?
					openfile->current_x : 0);
2926
	line = line->next;
2927
2928
2929
    }
}

2930
/* Ensure that firstcolumn is at the starting column of the softwrapped chunk
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
 * it's on.  We need to do this when the number of columns of the edit window
 * has changed, because then the width of softwrapped chunks has changed. */
void ensure_firstcolumn_is_aligned(void)
{
#ifndef NANO_TINY
    if (openfile->firstcolumn % editwincols != 0)
	openfile->firstcolumn -= (openfile->firstcolumn % editwincols);
#endif
}

2941
2942
2943
2944
/* Return TRUE if current[current_x] is above the top of the screen, and FALSE
 * otherwise. */
bool current_is_above_screen(void)
{
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
#ifndef NANO_TINY
    if (ISSET(SOFTWRAP))
	/* The cursor is above screen when current[current_x] is before edittop
	 * at column firstcolumn. */
	return (openfile->current->lineno < openfile->edittop->lineno ||
		(openfile->current->lineno == openfile->edittop->lineno &&
		xplustabs() < openfile->firstcolumn));
    else
#endif
	return (openfile->current->lineno < openfile->edittop->lineno);
2955
2956
2957
2958
2959
2960
2961
2962
2963
}

/* Return TRUE if current[current_x] is below the bottom of the screen, and
 * FALSE otherwise. */
bool current_is_below_screen(void)
{
#ifndef NANO_TINY
    if (ISSET(SOFTWRAP)) {
	filestruct *line = openfile->edittop;
2964
	size_t leftedge = openfile->firstcolumn;
2965
2966

	/* If current[current_x] is more than a screen's worth of lines after
2967
	 * edittop at column firstcolumn, it's below the screen. */
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
	return (go_forward_chunks(editwinrows - 1, &line, &leftedge) == 0 &&
			(line->lineno < openfile->current->lineno ||
			(line->lineno == openfile->current->lineno &&
			leftedge < (xplustabs() / editwincols) * editwincols)));
    } else
#endif
	return (openfile->current->lineno >=
			openfile->edittop->lineno + editwinrows);
}

/* Return TRUE if current[current_x] is offscreen relative to edittop, and
 * FALSE otherwise. */
bool current_is_offscreen(void)
{
    return (current_is_above_screen() || current_is_below_screen());
}

2985
/* Update any lines between old_current and current that need to be
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2986
 * updated.  Use this if we've moved without changing any text. */
2987
void edit_redraw(filestruct *old_current)
2988
{
2989
2990
2991
2992
    size_t was_pww = openfile->placewewant;

    openfile->placewewant = xplustabs();

2993
    /* If the current line is offscreen, scroll until it's onscreen. */
2994
    if (current_is_offscreen()) {
2995
	adjust_viewport((focusing || !ISSET(SMOOTH_SCROLL)) ? CENTERING : FLOWING);
2996
	refresh_needed = TRUE;
2997
	return;
2998
    }
2999

3000
#ifndef NANO_TINY
3001
3002
    /* If the mark is on, update all lines between old_current and current. */
    if (openfile->mark_set) {
3003
	filestruct *line = old_current;
3004

3005
3006
	while (line != openfile->current) {
	    update_line(line, 0);
3007

3008
3009
	    line = (line->lineno > openfile->current->lineno) ?
			line->prev : line->next;
3010
	}
3011
3012
3013
3014
3015
3016
    } 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);
3017

3018
3019
    /* Update current if the mark is on or it has changed "page", or if it
     * differs from old_current and needs to be horizontally scrolled. */
3020
    if (line_needs_update(was_pww, openfile->placewewant) ||
3021
			(old_current != openfile->current &&
3022
			get_page_start(openfile->placewewant) > 0))
3023
	update_line(openfile->current, openfile->current_x);
3024
3025
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3026
3027
/* 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
3028
3029
void edit_refresh(void)
{
3030
3031
    filestruct *line;
    int row = 0;
3032

3033
    /* If the current line is out of view, get it back on screen. */
3034
    if (current_is_offscreen()) {
3035
#ifdef DEBUG
3036
3037
	fprintf(stderr, "edit-refresh: line = %ld, edittop = %ld and editwinrows = %d\n",
		(long)openfile->current->lineno, (long)openfile->edittop->lineno, editwinrows);
3038
#endif
3039
	adjust_viewport((focusing || !ISSET(SMOOTH_SCROLL)) ? CENTERING : STATIONARY);
3040
    }
Chris Allegretta's avatar
Chris Allegretta committed
3041

3042
#ifdef DEBUG
3043
    fprintf(stderr, "edit-refresh: now edittop = %ld\n", (long)openfile->edittop->lineno);
3044
#endif
3045

3046
3047
3048
3049
    line = openfile->edittop;

    while (row < editwinrows && line != NULL) {
	if (line == openfile->current)
3050
	    row += update_line(line, openfile->current_x);
3051
	else
3052
	    row += update_line(line, 0);
3053
	line = line->next;
3054
3055
    }

3056
    while (row < editwinrows)
3057
	blank_row(edit, row++, 0, COLS);
3058
3059

    reset_cursor();
3060
    wnoutrefresh(edit);
3061
3062

    refresh_needed = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
3063
3064
}

3065
3066
3067
3068
3069
/* 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. */
3070
void adjust_viewport(update_type manner)
Chris Allegretta's avatar
Chris Allegretta committed
3071
{
3072
    int goal = 0;
3073

3074
3075
3076
3077
3078
3079
3080
3081
    /* If manner is CENTERING, move edittop half the number of window rows
     * back from current.  If manner is FLOWING, move edittop back 0 rows
     * or (editwinrows - 1) rows, depending on where current has moved.
     * This puts the cursor on the first or the last row.  If manner is
     * STATIONARY, move edittop back current_y rows if current_y is in range
     * of the screen, 0 rows if current_y is below zero, or (editwinrows - 1)
     * rows if current_y is too big.  This puts current at the same place on
     * the screen as before, or... at some undefined place. */
3082
    if (manner == CENTERING)
3083
	goal = editwinrows / 2;
3084
    else if (manner == FLOWING) {
3085
	if (!current_is_above_screen())
3086
3087
	    goal = editwinrows - 1;
    } else {
3088
	goal = openfile->current_y;
3089

3090
	/* Limit goal to (editwinrows - 1) rows maximum. */
3091
3092
	if (goal > editwinrows - 1)
	    goal = editwinrows - 1;
Chris Allegretta's avatar
Chris Allegretta committed
3093
    }
3094

3095
3096
    openfile->edittop = openfile->current;

3097
#ifndef NANO_TINY
3098
    if (ISSET(SOFTWRAP))
3099
	openfile->firstcolumn = (xplustabs() / editwincols) * editwincols;
3100
#endif
3101
3102

    /* Move edittop back goal rows, starting at current[current_x]. */
3103
    go_back_chunks(goal, &openfile->edittop, &openfile->firstcolumn);
3104

3105
#ifdef DEBUG
3106
    fprintf(stderr, "adjust_viewport(): setting edittop to lineno %ld\n", (long)openfile->edittop->lineno);
3107
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3108
3109
}

3110
/* Unconditionally redraw the entire screen. */
3111
void total_redraw(void)
3112
{
3113
3114
3115
3116
3117
3118
#ifdef USE_SLANG
    /* Slang curses emulation brain damage, part 4: Slang doesn't define
     * curscr. */
    SLsmg_touch_screen();
    SLsmg_refresh();
#else
3119
    wrefresh(curscr);
3120
#endif
3121
3122
}

3123
3124
/* Unconditionally redraw the entire screen, and then refresh it using
 * the current file. */
3125
3126
void total_refresh(void)
{
3127
    total_redraw();
3128
    titlebar(NULL);
3129
    edit_refresh();
3130
    bottombars(currmenu);
3131
3132
}

3133
3134
/* Display the main shortcut list on the last two rows of the bottom
 * portion of the window. */
3135
3136
void display_main_list(void)
{
3137
#ifndef DISABLE_COLOR
3138
3139
    if (openfile->syntax &&
		(openfile->syntax->formatter || openfile->syntax->linter))
3140
	set_lint_or_format_shortcuts();
3141
3142
3143
3144
    else
	set_spell_shortcuts();
#endif

3145
    bottombars(MMAIN);
3146
3147
}

3148
3149
3150
3151
/* Show info about the current cursor position on the statusbar.
 * Do this unconditionally when force is TRUE; otherwise, only if
 * suppress_cursorpos is FALSE.  In any case, reset the latter. */
void do_cursorpos(bool force)
Chris Allegretta's avatar
Chris Allegretta committed
3152
{
3153
3154
    char saved_byte;
    size_t sum, cur_xpt = xplustabs() + 1;
3155
    size_t cur_lenpt = strlenpt(openfile->current->data) + 1;
3156
    int linepct, colpct, charpct;
Chris Allegretta's avatar
Chris Allegretta committed
3157

3158
    assert(openfile->fileage != NULL && openfile->current != NULL);
3159

3160
    /* Determine the size of the file up to the cursor. */
3161
    saved_byte = openfile->current->data[openfile->current_x];
3162
    openfile->current->data[openfile->current_x] = '\0';
3163

3164
    sum = get_totsize(openfile->fileage, openfile->current);
3165

3166
    openfile->current->data[openfile->current_x] = saved_byte;
3167
3168
3169

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

3172
3173
    /* If the showing needs to be suppressed, don't suppress it next time. */
    if (suppress_cursorpos && !force) {
3174
	suppress_cursorpos = FALSE;
3175
	return;
3176
    }
Chris Allegretta's avatar
Chris Allegretta committed
3177

3178
    /* Display the current cursor position on the statusbar. */
3179
    linepct = 100 * openfile->current->lineno / openfile->filebot->lineno;
3180
    colpct = 100 * cur_xpt / cur_lenpt;
3181
    charpct = (openfile->totsize == 0) ? 0 : 100 * sum / openfile->totsize;
3182

3183
    statusline(HUSH,
3184
	_("line %ld/%ld (%d%%), col %lu/%lu (%d%%), char %lu/%lu (%d%%)"),
3185
	(long)openfile->current->lineno,
3186
	(long)openfile->filebot->lineno, linepct,
3187
	(unsigned long)cur_xpt, (unsigned long)cur_lenpt, colpct,
3188
	(unsigned long)sum, (unsigned long)openfile->totsize, charpct);
3189
3190
3191

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

3194
/* Unconditionally display the current cursor position. */
3195
void do_cursorpos_void(void)
3196
{
3197
    do_cursorpos(TRUE);
3198
3199
}

3200
3201
void enable_nodelay(void)
{
3202
3203
    nodelay_mode = TRUE;
    nodelay(edit, TRUE);
3204
3205
3206
3207
}

void disable_nodelay(void)
{
3208
3209
    nodelay_mode = FALSE;
    nodelay(edit, FALSE);
3210
3211
}

3212
3213
/* Highlight the current word being replaced or spell checked.  We
 * expect word to have tabs and control characters expanded. */
3214
void spotlight(bool active, const char *word)
Chris Allegretta's avatar
Chris Allegretta committed
3215
{
3216
3217
    size_t word_span = strlenpt(word);
    size_t room = word_span;
Chris Allegretta's avatar
Chris Allegretta committed
3218

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

3223
3224
3225
3226
	/* If the word is partially offscreen, reserve space for the "$". */
	if (word_span > room)
	    room--;
    }
3227

Chris Allegretta's avatar
Chris Allegretta committed
3228
    reset_cursor();
Chris Allegretta's avatar
Chris Allegretta committed
3229

3230
    if (active)
3231
	wattron(edit, hilite_attribute);
Chris Allegretta's avatar
Chris Allegretta committed
3232

3233
    /* This is so we can show zero-length matches. */
3234
    if (word_span == 0)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3235
	waddch(edit, ' ');
3236
    else
3237
	waddnstr(edit, word, actual_x(word, room));
3238

3239
    if (word_span > room)
3240
	waddch(edit, '$');
Chris Allegretta's avatar
Chris Allegretta committed
3241

3242
    if (active)
3243
	wattroff(edit, hilite_attribute);
3244
3245

    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
3246
3247
}

3248
#ifndef DISABLE_EXTRA
3249
3250
#define CREDIT_LEN 54
#define XLCREDIT_LEN 9
3251

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3252
3253
/* Easter egg: Display credits.  Assume nodelay(edit) and scrollok(edit)
 * are FALSE. */
3254
3255
void do_credits(void)
{
3256
3257
    bool old_more_space = ISSET(MORE_SPACE);
    bool old_no_help = ISSET(NO_HELP);
3258
    int kbinput = ERR, crpos = 0, xlpos = 0;
3259
3260
3261
    const char *credits[CREDIT_LEN] = {
	NULL,				/* "The nano text editor" */
	NULL,				/* "version" */
Chris Allegretta's avatar
Chris Allegretta committed
3262
3263
	VERSION,
	"",
3264
	NULL,				/* "Brought to you by:" */
Chris Allegretta's avatar
Chris Allegretta committed
3265
3266
3267
3268
3269
	"Chris Allegretta",
	"Jordi Mallach",
	"Adam Rogoyski",
	"Rob Siemborski",
	"Rocco Corsi",
3270
	"David Lawrence Ramsey",
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3271
	"David Benbennick",
Benno Schulenberg's avatar
Benno Schulenberg committed
3272
	"Mark Majeres",
3273
	"Mike Frysinger",
3274
	"Benno Schulenberg",
Chris Allegretta's avatar
Chris Allegretta committed
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
	"Ken Tyler",
	"Sven Guckes",
	"Bill Soudan",
	"Christian Weisgerber",
	"Erik Andersen",
	"Big Gaute",
	"Joshua Jensen",
	"Ryan Krebs",
	"Albert Chin",
	"",
3285
	NULL,				/* "Special thanks to:" */
3286
	"Monique, Brielle & Joseph",
Chris Allegretta's avatar
Chris Allegretta committed
3287
3288
3289
3290
3291
3292
	"Plattsburgh State University",
	"Benet Laboratories",
	"Amy Allegretta",
	"Linda Young",
	"Jeremy Robichaud",
	"Richard Kolb II",
3293
	NULL,				/* "The Free Software Foundation" */
Chris Allegretta's avatar
Chris Allegretta committed
3294
	"Linus Torvalds",
3295
	NULL,				/* "the many translators and the TP" */
3296
	NULL,				/* "For ncurses:" */
3297
3298
3299
3300
	"Thomas Dickey",
	"Pavel Curtis",
	"Zeyd Ben-Halim",
	"Eric S. Raymond",
3301
3302
3303
3304
3305
3306
	NULL,				/* "and anyone else we forgot..." */
	NULL,				/* "Thank you for using nano!" */
	"",
	"",
	"",
	"",
3307
	"(C) 1999 - 2016",
3308
	"Free Software Foundation, Inc.",
3309
3310
3311
3312
	"",
	"",
	"",
	"",
3313
	"https://nano-editor.org/"
3314
3315
    };

3316
    const char *xlcredits[XLCREDIT_LEN] = {
3317
3318
3319
3320
3321
	N_("The nano text editor"),
	N_("version"),
	N_("Brought to you by:"),
	N_("Special thanks to:"),
	N_("The Free Software Foundation"),
3322
	N_("the many translators and the TP"),
3323
3324
3325
	N_("For ncurses:"),
	N_("and anyone else we forgot..."),
	N_("Thank you for using nano!")
3326
    };
3327

3328
3329
3330
3331
3332
3333
    if (!old_more_space || !old_no_help) {
	SET(MORE_SPACE);
	SET(NO_HELP);
	window_init();
    }

3334
3335
    curs_set(0);
    nodelay(edit, TRUE);
3336

3337
    blank_titlebar();
Chris Allegretta's avatar
Chris Allegretta committed
3338
    blank_edit();
3339
    blank_statusbar();
3340

3341
    wrefresh(topwin);
Chris Allegretta's avatar
Chris Allegretta committed
3342
    wrefresh(edit);
3343
    wrefresh(bottomwin);
3344
    napms(700);
3345

3346
    for (crpos = 0; crpos < CREDIT_LEN + editwinrows / 2; crpos++) {
3347
	if ((kbinput = wgetch(edit)) != ERR)
3348
	    break;
3349

3350
	if (crpos < CREDIT_LEN) {
3351
	    const char *what;
3352
	    size_t start_col;
3353

3354
	    if (credits[crpos] == NULL) {
3355
		assert(0 <= xlpos && xlpos < XLCREDIT_LEN);
3356

3357
		what = _(xlcredits[xlpos]);
3358
		xlpos++;
3359
	    } else
3360
		what = credits[crpos];
3361

3362
	    start_col = COLS / 2 - strlenpt(what) / 2 - 1;
3363
	    mvwaddstr(edit, editwinrows - 1 - (editwinrows % 2),
3364
						start_col, what);
3365
	}
3366

3367
3368
3369
3370
	wrefresh(edit);

	if ((kbinput = wgetch(edit)) != ERR)
	    break;
3371
	napms(700);
3372

3373
	scrollok(edit, TRUE);
3374
	wscrl(edit, 1);
3375
	scrollok(edit, FALSE);
3376
	wrefresh(edit);
3377

3378
	if ((kbinput = wgetch(edit)) != ERR)
3379
	    break;
3380
	napms(700);
3381

3382
	scrollok(edit, TRUE);
3383
	wscrl(edit, 1);
3384
	scrollok(edit, FALSE);
3385
	wrefresh(edit);
3386
3387
    }

3388
3389
3390
    if (kbinput != ERR)
	ungetch(kbinput);

3391
    if (!old_more_space)
3392
	UNSET(MORE_SPACE);
3393
    if (!old_no_help)
3394
	UNSET(NO_HELP);
3395
    window_init();
3396

3397
    nodelay(edit, FALSE);
3398

3399
    total_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
3400
}
3401
#endif /* !DISABLE_EXTRA */