winio.c 101 KB
Newer Older
Chris Allegretta's avatar
Chris Allegretta committed
1
2
3
/**************************************************************************
 *   winio.c                                                              *
 *                                                                        *
4
 *   Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007,  *
5
 *   2008, 2009, 2010, 2011, 2013, 2014 Free Software Foundation, Inc.    *
Chris Allegretta's avatar
Chris Allegretta committed
6
7
 *   This program is free software; you can redistribute it and/or modify *
 *   it under the terms of the GNU General Public License as published by *
8
 *   the Free Software Foundation; either version 3, or (at your option)  *
Chris Allegretta's avatar
Chris Allegretta committed
9
10
 *   any later version.                                                   *
 *                                                                        *
11
12
13
14
 *   This program 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
15
16
17
 *                                                                        *
 *   You should have received a copy of the GNU General Public License    *
 *   along with this program; if not, write to the Free Software          *
18
19
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA            *
 *   02110-1301, USA.                                                     *
Chris Allegretta's avatar
Chris Allegretta committed
20
21
22
 *                                                                        *
 **************************************************************************/

23
#include "proto.h"
24

25
#include <stdio.h>
Chris Allegretta's avatar
Chris Allegretta committed
26
27
#include <stdarg.h>
#include <string.h>
28
#include <unistd.h>
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
29
#include <ctype.h>
Chris Allegretta's avatar
Chris Allegretta committed
30

31
static int *key_buffer = NULL;
32
33
	/* The keystroke buffer, containing all the keystrokes we
	 * haven't handled yet at a given point. */
34
static size_t key_buffer_len = 0;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
35
	/* The length of the keystroke buffer. */
36
static int statusblank = 0;
37
	/* The number of keystrokes left before we blank the statusbar. */
38
static bool suppress_cursorpos = FALSE;
39
	/* Should we skip constant position display for one keystroke? */
40
41
static bool seen_wide = FALSE;
	/* Whether we've seen a multicolumn character in the current line. */
42

43
#ifndef NANO_TINY
44
45
46
47
48
49
50
51
52
53
54
55
static sig_atomic_t last_sigwinch_counter = 0;

/* Did we receive a SIGWINCH since we were last called? */
bool the_window_resized(void)
{
    if (sigwinch_counter == last_sigwinch_counter)
	return FALSE;

    last_sigwinch_counter = sigwinch_counter;
    regenerate_screen();
    return TRUE;
}
56
#endif
57

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

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

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

129
130
131
132
    /* Just before reading in the first character, display any pending
     * screen updates. */
    doupdate();

133
    /* Read in the first character using whatever mode we're in. */
134
135
    input = wgetch(win);

136
137
138
139
140
#ifndef NANO_TINY
    if (the_window_resized())
	input = KEY_WINCH;
#endif

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

243
/* Put back the character stored in kbinput, putting it in byte range
244
245
 * beforehand.  If metakey is TRUE, put back the Escape character after
 * putting back kbinput.  If funckey is TRUE, put back the function key
246
 * (a value outside byte range) without putting it in byte range. */
247
void unget_kbinput(int kbinput, bool metakey, bool funckey)
248
{
249
    if (!funckey)
250
	kbinput = (char)kbinput;
251

252
    unget_input(&kbinput, 1);
253

254
    if (metakey) {
255
256
	kbinput = NANO_CONTROL_3;
	unget_input(&kbinput, 1);
257
258
259
    }
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
260
261
262
/* Try to read input_len characters from the keystroke buffer.  If the
 * keystroke buffer is empty and win isn't NULL, try to read in more
 * characters from win and add them to the keystroke buffer before doing
263
 * anything else.  If the keystroke buffer is (still) empty, return NULL. */
264
int *get_input(WINDOW *win, size_t input_len)
265
{
266
    int *input;
267

268
269
    if (key_buffer_len == 0 && win != NULL)
	get_key_buffer(win);
270

271
272
    if (key_buffer_len == 0)
	return NULL;
273

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
274
275
    /* If input_len is greater than the length of the keystroke buffer,
     * only read the number of characters in the keystroke buffer. */
276
277
278
    if (input_len > key_buffer_len)
	input_len = key_buffer_len;

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
279
280
281
    /* Subtract input_len from the length of the keystroke buffer, and
     * allocate input so that it has enough room for input_len
     * keystrokes. */
282
    key_buffer_len -= input_len;
283
    input = (int *)nmalloc(input_len * sizeof(int));
284

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
285
286
    /* Copy input_len keystrokes from the beginning of the keystroke
     * buffer into input. */
287
    memcpy(input, key_buffer, input_len * sizeof(int));
288

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
289
    /* If the keystroke buffer is empty, mark it as such. */
290
291
292
    if (key_buffer_len == 0) {
	free(key_buffer);
	key_buffer = NULL;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
293
294
295
    /* If the keystroke buffer isn't empty, move its beginning forward
     * far enough so that the keystrokes in input are no longer at its
     * beginning. */
296
297
    } else {
	memmove(key_buffer, key_buffer + input_len, key_buffer_len *
298
299
300
		sizeof(int));
	key_buffer = (int *)nrealloc(key_buffer, key_buffer_len *
		sizeof(int));
301
302
303
    }

    return input;
304
305
}

306
/* Read in a single keystroke, ignoring any that are invalid. */
307
int get_kbinput(WINDOW *win)
308
309
310
{
    int kbinput;

311
    /* Extract one keystroke from the input stream. */
312
    while ((kbinput = parse_kbinput(win)) == ERR)
313
	;
314

315
    /* If we read from the edit window, blank the statusbar if needed. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
316
    if (win == edit)
317
318
	check_statusblank();

319
320
321
    return kbinput;
}

322
323
324
325
326
327
328
/* Extract a single keystroke from the input stream.  Translate escape
 * sequences and extended keypad codes into their corresponding values.
 * Set meta_key to TRUE when we get a meta key sequence, and set func_key
 * to TRUE when we get a function key.  Supported extended keypad values
 * 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. */
329
int parse_kbinput(WINDOW *win)
330
{
331
    static int escapes = 0, byte_digits = 0;
332
    static bool double_esc = FALSE;
333
    int *kbinput, retval = ERR;
334

335
336
    meta_key = FALSE;
    func_key = FALSE;
337

338
    /* Read in a character. */
339
340
341
342
343
344
    kbinput = get_input(win, 1);

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

    while (kbinput == NULL)
345
	kbinput = get_input(win, 1);
346

347
348
349
350
351
352
    switch (*kbinput) {
	case ERR:
	    break;
	case NANO_CONTROL_3:
	    /* Increment the escape counter. */
	    escapes++;
353
354
355
356
	    /* If there are four consecutive escapes, discard three of them. */
	    if (escapes > 3)
		escapes = 1;
	    /* Wait for more input. */
357
358
359
360
	    break;
	default:
	    switch (escapes) {
		case 0:
361
		    /* One non-escape: normal input mode. */
362
		    retval = *kbinput;
363
364
		    break;
		case 1:
365
		    /* Reset the escape counter. */
366
		    escapes = 0;
367
		    if (get_key_buffer_len() == 0 || key_buffer[0] == 0x1b) {
368
369
			/* One escape followed by a single non-escape:
			 * meta key sequence mode. */
370
			meta_key = TRUE;
371
			retval = tolower(*kbinput);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
372
		    } else
373
374
			/* One escape followed by a non-escape, and there
			 * are more codes waiting: escape sequence mode. */
375
			retval = parse_escape_sequence(win, *kbinput);
376
377
		    break;
		case 2:
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
		    if (double_esc) {
			/* An "ESC ESC [ X" sequence from Option+arrow. */
			switch (*kbinput) {
			    case 'A':
				retval = KEY_HOME;
				break;
			    case 'B':
				retval = KEY_END;
				break;
#ifndef NANO_TINY
			    case 'C':
				retval = controlright;
				break;
			    case 'D':
				retval = controlleft;
				break;
#endif
			    default:
				retval = ERR;
				break;
			}
			double_esc = FALSE;
			escapes = 0;
		    } else if (get_key_buffer_len() == 0) {
402
403
404
			if (('0' <= *kbinput && *kbinput <= '2' &&
				byte_digits == 0) || ('0' <= *kbinput &&
				*kbinput <= '9' && byte_digits > 0)) {
405
406
407
408
409
410
			    /* 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. */
411
412
413
414
415
			    int byte;

			    byte_digits++;
			    byte = get_byte_kbinput(*kbinput);

416
417
418
419
			    /* If we've read in a complete byte sequence,
			     * reset the escape counter and the byte sequence
			     * counter, and put the obtained byte value back
			     * into the key buffer. */
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
			    if (byte != ERR) {
				char *byte_mb;
				int byte_mb_len, *seq, i;

				escapes = 0;
				byte_digits = 0;

				/* Put back the multibyte equivalent of
				 * the byte value. */
				byte_mb = make_mbchar((long)byte,
					&byte_mb_len);

				seq = (int *)nmalloc(byte_mb_len *
					sizeof(int));

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

				unget_input(seq, byte_mb_len);

				free(byte_mb);
				free(seq);
			    }
			} else {
			    /* Reset the escape counter. */
445
			    escapes = 0;
446
447
			    if (byte_digits == 0)
				/* Two escapes followed by a non-decimal
448
449
450
451
				 * 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. */
452
453
				retval = get_control_kbinput(*kbinput);
			    else {
454
455
456
				/* An invalid digit in the middle of a byte
				 * sequence: reset the byte sequence counter
				 * and save the code we got as the result. */
457
458
459
				byte_digits = 0;
				retval = *kbinput;
			    }
460
			}
461
462
463
		    } else if (*kbinput=='[') {
			/* This is an iTerm2 sequence: ^[ ^[ [ X. */
			double_esc = TRUE;
464
		    } else {
465
466
467
			/* Two escapes followed by a non-escape, and there
			 * are more codes waiting: combined meta and escape
			 * sequence mode. */
468
			escapes = 0;
469
			meta_key = TRUE;
470
			retval = parse_escape_sequence(win, *kbinput);
471
		    }
472
		    break;
473
474
475
476
		case 3:
		    /* Reset the escape counter. */
		    escapes = 0;
		    if (get_key_buffer_len() == 0)
477
478
			/* Three escapes followed by a non-escape, and no
			 * other codes are waiting: normal input mode. */
479
480
			retval = *kbinput;
		    else
481
482
483
484
			/* 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. */
485
			retval = get_control_kbinput(
486
				parse_escape_sequence(win, *kbinput));
487
		    break;
488
489
	    }
    }
490

491
    if (retval != ERR) {
492
493
	switch (retval) {
	    case NANO_CONTROL_8:
494
495
		retval = ISSET(REBIND_DELETE) ? sc_seq_or(do_delete, 0) :
			sc_seq_or(do_backspace, 0);
496
497
		break;
	    case KEY_DOWN:
498
499
500
501
#ifdef KEY_SDOWN
	    /* ncurses and Slang don't support KEY_SDOWN. */
	    case KEY_SDOWN:
#endif
502
		retval = sc_seq_or(do_down_void, *kbinput);
503
504
		break;
	    case KEY_UP:
505
506
507
508
#ifdef KEY_SUP
	    /* ncurses and Slang don't support KEY_SUP. */
	    case KEY_SUP:
#endif
509
		retval = sc_seq_or(do_up_void, *kbinput);
510
511
		break;
	    case KEY_LEFT:
512
513
514
515
#ifdef KEY_SLEFT
	    /* Slang doesn't support KEY_SLEFT. */
	    case KEY_SLEFT:
#endif
516
		retval = sc_seq_or(do_left, *kbinput);
517
518
		break;
	    case KEY_RIGHT:
519
520
521
522
#ifdef KEY_SRIGHT
	    /* Slang doesn't support KEY_SRIGHT. */
	    case KEY_SRIGHT:
#endif
523
		retval = sc_seq_or(do_right, *kbinput);
524
		break;
525
526
527
528
529
530
#ifdef KEY_SHOME
	    /* HP-UX 10-11 and Slang don't support KEY_SHOME. */
	    case KEY_SHOME:
#endif
	    case KEY_A1:	/* Home (7) on numeric keypad with
				 * NumLock off. */
531
		retval = sc_seq_or(do_home, *kbinput);
532
		break;
533
	    case KEY_BACKSPACE:
534
		retval = sc_seq_or(do_backspace, *kbinput);
535
		break;
536
537
538
539
#ifdef KEY_SDC
	    /* Slang doesn't support KEY_SDC. */
	    case KEY_SDC:
		if (ISSET(REBIND_DELETE))
540
		    retval = sc_seq_or(do_delete, *kbinput);
541
		else
542
		    retval = sc_seq_or(do_backspace, *kbinput);
543
		break;
544
#endif
545
546
547
#ifdef KEY_SIC
	    /* Slang doesn't support KEY_SIC. */
	    case KEY_SIC:
548
		retval = sc_seq_or(do_insertfile_void, *kbinput);
549
		break;
550
#endif
551
	    case KEY_C3:	/* PageDown (4) on numeric keypad with
552
				 * NumLock off. */
553
		retval = sc_seq_or(do_page_down, *kbinput);
554
555
556
		break;
	    case KEY_A3:	/* PageUp (9) on numeric keypad with
				 * NumLock off. */
557
		retval = sc_seq_or(do_page_up, *kbinput);
558
559
		break;
	    case KEY_ENTER:
560
		retval = sc_seq_or(do_enter, *kbinput);
561
562
563
564
565
566
567
		break;
	    case KEY_B2:	/* Center (5) on numeric keypad with
				 * NumLock off. */
		retval = ERR;
		break;
	    case KEY_C1:	/* End (1) on numeric keypad with
				 * NumLock off. */
568
569
570
571
#ifdef KEY_SEND
	    /* HP-UX 10-11 and Slang don't support KEY_SEND. */
	    case KEY_SEND:
#endif
572
		retval = sc_seq_or(do_end, *kbinput);
573
574
575
576
577
578
579
580
		break;
#ifdef KEY_BEG
	    /* Slang doesn't support KEY_BEG. */
	    case KEY_BEG:	/* Center (5) on numeric keypad with
				 * NumLock off. */
		retval = ERR;
		break;
#endif
581
582
583
#ifdef KEY_CANCEL
	    /* Slang doesn't support KEY_CANCEL. */
	    case KEY_CANCEL:
584
585
586
#ifdef KEY_SCANCEL
	    /* Slang doesn't support KEY_SCANCEL. */
	    case KEY_SCANCEL:
587
#endif
588
		retval = first_sc_for(currmenu, do_cancel)->seq;
589
590
591
592
593
594
595
596
597
598
599
600
		break;
#endif
#ifdef KEY_SBEG
	    /* Slang doesn't support KEY_SBEG. */
	    case KEY_SBEG:	/* Center (5) on numeric keypad with
				 * NumLock off. */
		retval = ERR;
		break;
#endif
#ifdef KEY_SSUSPEND
	    /* Slang doesn't support KEY_SSUSPEND. */
	    case KEY_SSUSPEND:
601
		retval = sc_seq_or(do_suspend_void, 0);
602
603
604
605
606
		break;
#endif
#ifdef KEY_SUSPEND
	    /* Slang doesn't support KEY_SUSPEND. */
	    case KEY_SUSPEND:
607
		retval = sc_seq_or(do_suspend_void, 0);
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
		break;
#endif
#ifdef PDCURSES
	    case KEY_SHIFT_L:
	    case KEY_SHIFT_R:
	    case KEY_CONTROL_L:
	    case KEY_CONTROL_R:
	    case KEY_ALT_L:
	    case KEY_ALT_R:
		retval = ERR;
		break;
#endif
#if !defined(NANO_TINY) && defined(KEY_RESIZE)
	    /* Since we don't change the default SIGWINCH handler when
	     * NANO_TINY is defined, KEY_RESIZE is never generated.
	     * Also, Slang and SunOS 5.7-5.9 don't support
	     * KEY_RESIZE. */
	    case KEY_RESIZE:
		retval = ERR;
		break;
628
#endif
629
	}
630

631
632
633
634
635
636
637
#ifndef NANO_TINY
	if (retval == controlleft)
	    retval = sc_seq_or(do_prev_word_void, 0);
	else if (retval == controlright)
	    retval = sc_seq_or(do_next_word_void, 0);
#endif

638
	/* If our result is an extended keypad value (i.e. a value
639
	 * outside of byte range), set func_key to TRUE. */
640
	if (retval != ERR)
641
	    func_key = !is_byte(retval);
642
    }
643
644

#ifdef DEBUG
645
    fprintf(stderr, "parse_kbinput(): kbinput = %d, meta_key = %s, func_key = %s, escapes = %d, byte_digits = %d, retval = %d\n", *kbinput, meta_key ? "TRUE" : "FALSE", func_key ? "TRUE" : "FALSE", escapes, byte_digits, retval);
646
647
#endif

648
649
    free(kbinput);

650
    /* Return the result. */
651
652
653
    return retval;
}

654
/* Translate escape sequences, most of which correspond to extended
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
655
 * keypad values, into their corresponding key values.  These sequences
656
657
 * are generated when the keypad doesn't support the needed keys.
 * Assume that Escape has already been read in. */
658
int convert_sequence(const int *seq, size_t seq_len)
659
{
660
    if (seq_len > 1) {
661
	switch (seq[0]) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
662
	    case 'O':
663
		switch (seq[1]) {
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
		    case '1':
			if (seq_len >= 3) {
			    switch (seq[2]) {
				case ';':
    if (seq_len >= 4) {
	switch (seq[3]) {
	    case '2':
		if (seq_len >= 5) {
		    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. */
681
			    return arrow_from_abcd(seq[4]);
682
			case 'P': /* Esc O 1 ; 2 P == F13 on Terminal. */
683
			    return KEY_F(13);
684
			case 'Q': /* Esc O 1 ; 2 Q == F14 on Terminal. */
685
			    return KEY_F(14);
686
			case 'R': /* Esc O 1 ; 2 R == F15 on Terminal. */
687
			    return KEY_F(15);
688
			case 'S': /* Esc O 1 ; 2 S == F16 on Terminal. */
689
			    return KEY_F(16);
690
691
692
693
694
695
		    }
		}
		break;
	    case '5':
		if (seq_len >= 5) {
		    switch (seq[4]) {
696
697
			case 'A': /* Esc O 1 ; 5 A == Ctrl-Up on Terminal. */
			case 'B': /* Esc O 1 ; 5 B == Ctrl-Down on Terminal. */
698
			    return arrow_from_abcd(seq[4]);
699
			case 'C': /* Esc O 1 ; 5 C == Ctrl-Right on Terminal. */
700
			    return CONTROL_RIGHT;
701
			case 'D': /* Esc O 1 ; 5 D == Ctrl-Left on Terminal. */
702
			    return CONTROL_LEFT;
703
704
705
706
707
708
709
710
711
		    }
		}
		break;
	}
    }
				    break;
			    }
			}
			break;
712
		    case '2':
713
			if (seq_len >= 3) {
714
			    switch (seq[2]) {
715
				case 'P': /* Esc O 2 P == F13 on xterm. */
716
				    return KEY_F(13);
717
				case 'Q': /* Esc O 2 Q == F14 on xterm. */
718
				    return KEY_F(14);
719
				case 'R': /* Esc O 2 R == F15 on xterm. */
720
				    return KEY_F(15);
721
				case 'S': /* Esc O 2 S == F16 on xterm. */
722
				    return KEY_F(16);
723
724
			    }
			}
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
725
			break;
726
		    case 'A': /* Esc O A == Up on VT100/VT320/xterm. */
727
728
729
		    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. */
730
			return arrow_from_abcd(seq[1]);
731
732
		    case 'E': /* Esc O E == Center (5) on numeric keypad
			       * with NumLock off on xterm. */
733
			return KEY_B2;
734
		    case 'F': /* Esc O F == End on xterm/Terminal. */
735
			return sc_seq_or(do_end, 0);
736
		    case 'H': /* Esc O H == Home on xterm/Terminal. */
737
			return sc_seq_or(do_home, 0);
738
		    case 'M': /* Esc O M == Enter on numeric keypad with
739
			       * NumLock off on VT100/VT220/VT320/xterm/
740
			       * rxvt/Eterm. */
741
			return sc_seq_or(do_home, 0);
742
		    case 'P': /* Esc O P == F1 on VT100/VT220/VT320/Mach
743
			       * console. */
744
			return KEY_F(1);
745
		    case 'Q': /* Esc O Q == F2 on VT100/VT220/VT320/Mach
746
			       * console. */
747
			return KEY_F(2);
748
		    case 'R': /* Esc O R == F3 on VT100/VT220/VT320/Mach
749
			       * console. */
750
			return KEY_F(3);
751
		    case 'S': /* Esc O S == F4 on VT100/VT220/VT320/Mach
752
			       * console. */
753
			return KEY_F(4);
754
		    case 'T': /* Esc O T == F5 on Mach console. */
755
			return KEY_F(5);
756
		    case 'U': /* Esc O U == F6 on Mach console. */
757
			return KEY_F(6);
758
		    case 'V': /* Esc O V == F7 on Mach console. */
759
			return KEY_F(7);
760
		    case 'W': /* Esc O W == F8 on Mach console. */
761
			return KEY_F(8);
762
		    case 'X': /* Esc O X == F9 on Mach console. */
763
			return KEY_F(9);
764
		    case 'Y': /* Esc O Y == F10 on Mach console. */
765
			return KEY_F(10);
766
767
		    case 'a': /* Esc O a == Ctrl-Up on rxvt. */
		    case 'b': /* Esc O b == Ctrl-Down on rxvt. */
768
			return arrow_from_abcd(seq[1]);
769
		    case 'c': /* Esc O c == Ctrl-Right on rxvt. */
770
			return CONTROL_RIGHT;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
771
		    case 'd': /* Esc O d == Ctrl-Left on rxvt. */
772
			return CONTROL_LEFT;
773
		    case 'j': /* Esc O j == '*' on numeric keypad with
774
			       * NumLock off on VT100/VT220/VT320/xterm/
775
			       * rxvt/Eterm/Terminal. */
776
			return '*';
777
		    case 'k': /* Esc O k == '+' on numeric keypad with
778
			       * NumLock off on VT100/VT220/VT320/xterm/
779
			       * rxvt/Eterm/Terminal. */
780
			return '+';
781
		    case 'l': /* Esc O l == ',' on numeric keypad with
782
			       * NumLock off on VT100/VT220/VT320/xterm/
783
			       * rxvt/Eterm/Terminal. */
784
			return ',';
785
		    case 'm': /* Esc O m == '-' on numeric keypad with
786
			       * NumLock off on VT100/VT220/VT320/xterm/
787
			       * rxvt/Eterm/Terminal. */
788
			return '-';
789
		    case 'n': /* Esc O n == Delete (.) on numeric keypad
790
			       * with NumLock off on VT100/VT220/VT320/
791
			       * xterm/rxvt/Eterm/Terminal. */
792
			return sc_seq_or(do_delete, 0);
793
		    case 'o': /* Esc O o == '/' on numeric keypad with
794
			       * NumLock off on VT100/VT220/VT320/xterm/
795
			       * rxvt/Eterm/Terminal. */
796
			return '/';
797
		    case 'p': /* Esc O p == Insert (0) on numeric keypad
798
			       * with NumLock off on VT100/VT220/VT320/
799
			       * rxvt/Eterm/Terminal. */
800
			return sc_seq_or(do_insertfile_void, 0);
801
		    case 'q': /* Esc O q == End (1) on numeric keypad
802
			       * with NumLock off on VT100/VT220/VT320/
803
			       * rxvt/Eterm/Terminal. */
804
			return sc_seq_or(do_end, 0);
805
		    case 'r': /* Esc O r == Down (2) on numeric keypad
806
			       * with NumLock off on VT100/VT220/VT320/
807
			       * rxvt/Eterm/Terminal. */
808
			return sc_seq_or(do_down_void, 0);
809
		    case 's': /* Esc O s == PageDown (3) on numeric
810
			       * keypad with NumLock off on VT100/VT220/
811
			       * VT320/rxvt/Eterm/Terminal. */
812
			return sc_seq_or(do_page_down, 0);
813
		    case 't': /* Esc O t == Left (4) on numeric keypad
814
			       * with NumLock off on VT100/VT220/VT320/
815
			       * rxvt/Eterm/Terminal. */
816
			return sc_seq_or(do_left, 0);
817
		    case 'u': /* Esc O u == Center (5) on numeric keypad
818
819
			       * with NumLock off on VT100/VT220/VT320/
			       * rxvt/Eterm. */
820
			return KEY_B2;
821
		    case 'v': /* Esc O v == Right (6) on numeric keypad
822
			       * with NumLock off on VT100/VT220/VT320/
823
			       * rxvt/Eterm/Terminal. */
824
			return sc_seq_or(do_right, 0);
825
		    case 'w': /* Esc O w == Home (7) on numeric keypad
826
			       * with NumLock off on VT100/VT220/VT320/
827
			       * rxvt/Eterm/Terminal. */
828
			return sc_seq_or(do_home, 0);
829
		    case 'x': /* Esc O x == Up (8) on numeric keypad
830
			       * with NumLock off on VT100/VT220/VT320/
831
			       * rxvt/Eterm/Terminal. */
832
			return sc_seq_or(do_up_void, 0);
833
		    case 'y': /* Esc O y == PageUp (9) on numeric keypad
834
			       * with NumLock off on VT100/VT220/VT320/
835
			       * rxvt/Eterm/Terminal. */
836
			return sc_seq_or(do_page_up, 0);
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
837
838
839
		}
		break;
	    case 'o':
840
		switch (seq[1]) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
841
842
		    case 'a': /* Esc o a == Ctrl-Up on Eterm. */
		    case 'b': /* Esc o b == Ctrl-Down on Eterm. */
843
			return arrow_from_abcd(seq[1]);
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
844
		    case 'c': /* Esc o c == Ctrl-Right on Eterm. */
845
			return CONTROL_RIGHT;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
846
		    case 'd': /* Esc o d == Ctrl-Left on Eterm. */
847
			return CONTROL_LEFT;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
848
849
850
		}
		break;
	    case '[':
851
		switch (seq[1]) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
852
		    case '1':
853
			if (seq_len >= 3) {
854
			    switch (seq[2]) {
855
				case '1': /* Esc [ 1 1 ~ == F1 on rxvt/Eterm. */
856
				    return KEY_F(1);
857
				case '2': /* Esc [ 1 2 ~ == F2 on rxvt/Eterm. */
858
				    return KEY_F(2);
859
				case '3': /* Esc [ 1 3 ~ == F3 on rxvt/Eterm. */
860
				    return KEY_F(3);
861
				case '4': /* Esc [ 1 4 ~ == F4 on rxvt/Eterm. */
862
				    return KEY_F(4);
863
864
				case '5': /* Esc [ 1 5 ~ == F5 on xterm/
					   * rxvt/Eterm. */
865
				    return KEY_F(5);
866
				case '7': /* Esc [ 1 7 ~ == F6 on
867
868
					   * VT220/VT320/Linux console/
					   * xterm/rxvt/Eterm. */
869
				    return KEY_F(6);
870
				case '8': /* Esc [ 1 8 ~ == F7 on
871
872
					   * VT220/VT320/Linux console/
					   * xterm/rxvt/Eterm. */
873
				    return KEY_F(7);
874
				case '9': /* Esc [ 1 9 ~ == F8 on
875
876
					   * VT220/VT320/Linux console/
					   * xterm/rxvt/Eterm. */
877
				    return KEY_F(8);
878
				case ';':
879
    if (seq_len >= 4) {
880
	switch (seq[3]) {
881
	    case '2':
882
		if (seq_len >= 5) {
883
		    switch (seq[4]) {
884
885
886
887
			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. */
888
			    return arrow_from_abcd(seq[4]);
889
890
891
892
		    }
		}
		break;
	    case '5':
893
		if (seq_len >= 5) {
894
		    switch (seq[4]) {
895
896
			case 'A': /* Esc [ 1 ; 5 A == Ctrl-Up on xterm. */
			case 'B': /* Esc [ 1 ; 5 B == Ctrl-Down on xterm. */
897
			    return arrow_from_abcd(seq[4]);
898
			case 'C': /* Esc [ 1 ; 5 C == Ctrl-Right on xterm. */
899
			    return CONTROL_RIGHT;
900
			case 'D': /* Esc [ 1 ; 5 D == Ctrl-Left on xterm. */
901
			    return CONTROL_LEFT;
902
903
904
905
906
907
		    }
		}
		break;
	}
    }
				    break;
908
909
				default: /* Esc [ 1 ~ == Home on
					  * VT320/Linux console. */
910
				    return sc_seq_or(do_home, 0);
911
912
913
914
			    }
			}
			break;
		    case '2':
915
			if (seq_len >= 3) {
916
			    switch (seq[2]) {
917
918
				case '0': /* Esc [ 2 0 ~ == F9 on VT220/VT320/
					   * Linux console/xterm/rxvt/Eterm. */
919
				    return KEY_F(9);
920
921
				case '1': /* Esc [ 2 1 ~ == F10 on VT220/VT320/
					   * Linux console/xterm/rxvt/Eterm. */
922
				    return KEY_F(10);
923
924
				case '3': /* Esc [ 2 3 ~ == F11 on VT220/VT320/
					   * Linux console/xterm/rxvt/Eterm. */
925
				    return KEY_F(11);
926
927
				case '4': /* Esc [ 2 4 ~ == F12 on VT220/VT320/
					   * Linux console/xterm/rxvt/Eterm. */
928
				    return KEY_F(12);
929
930
				case '5': /* Esc [ 2 5 ~ == F13 on VT220/VT320/
					   * Linux console/rxvt/Eterm. */
931
				    return KEY_F(13);
932
933
				case '6': /* Esc [ 2 6 ~ == F14 on VT220/VT320/
					   * Linux console/rxvt/Eterm. */
934
				    return KEY_F(14);
935
936
				case '8': /* Esc [ 2 8 ~ == F15 on VT220/VT320/
					   * Linux console/rxvt/Eterm. */
937
				    return KEY_F(15);
938
939
				case '9': /* Esc [ 2 9 ~ == F16 on VT220/VT320/
					   * Linux console/rxvt/Eterm. */
940
				    return KEY_F(16);
941
942
				default: /* Esc [ 2 ~ == Insert on VT220/VT320/
					  * Linux console/xterm/Terminal. */
943
				    return sc_seq_or(do_insertfile_void, 0);
944
			    }
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
945
946
			}
			break;
947
		    case '3': /* Esc [ 3 ~ == Delete on VT220/VT320/
948
			       * Linux console/xterm/Terminal. */
949
			return sc_seq_or(do_delete, 0);
950
		    case '4': /* Esc [ 4 ~ == End on VT220/VT320/Linux
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
951
			       * console/xterm. */
952
			return sc_seq_or(do_end, 0);
953
		    case '5': /* Esc [ 5 ~ == PageUp on VT220/VT320/
954
955
			       * Linux console/xterm/Terminal;
			       * Esc [ 5 ^ == PageUp on Eterm. */
956
			return sc_seq_or(do_page_up, 0);
957
		    case '6': /* Esc [ 6 ~ == PageDown on VT220/VT320/
958
			       * Linux console/xterm/Terminal;
959
			       * Esc [ 6 ^ == PageDown on Eterm. */
960
			return sc_seq_or(do_page_down, 0);
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
961
		    case '7': /* Esc [ 7 ~ == Home on rxvt. */
962
			return sc_seq_or(do_home, 0);
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
963
		    case '8': /* Esc [ 8 ~ == End on rxvt. */
964
			return sc_seq_or(do_end, 0);
965
		    case '9': /* Esc [ 9 == Delete on Mach console. */
966
			return sc_seq_or(do_delete, 0);
967
		    case '@': /* Esc [ @ == Insert on Mach console. */
968
			return sc_seq_or(do_insertfile_void, 0);
969
		    case 'A': /* Esc [ A == Up on ANSI/VT220/Linux
970
			       * console/FreeBSD console/Mach console/
971
			       * rxvt/Eterm/Terminal. */
972
		    case 'B': /* Esc [ B == Down on ANSI/VT220/Linux
973
			       * console/FreeBSD console/Mach console/
974
			       * rxvt/Eterm/Terminal. */
975
		    case 'C': /* Esc [ C == Right on ANSI/VT220/Linux
976
			       * console/FreeBSD console/Mach console/
977
			       * rxvt/Eterm/Terminal. */
978
		    case 'D': /* Esc [ D == Left on ANSI/VT220/Linux
979
			       * console/FreeBSD console/Mach console/
980
			       * rxvt/Eterm/Terminal. */
981
			return arrow_from_abcd(seq[1]);
982
		    case 'E': /* Esc [ E == Center (5) on numeric keypad
983
984
			       * with NumLock off on FreeBSD console/
			       * Terminal. */
985
			return KEY_B2;
986
		    case 'F': /* Esc [ F == End on FreeBSD console/Eterm. */
987
			return sc_seq_or(do_end, 0);
988
		    case 'G': /* Esc [ G == PageDown on FreeBSD console. */
989
			return sc_seq_or(do_page_down, 0);
990
		    case 'H': /* Esc [ H == Home on ANSI/VT220/FreeBSD
991
			       * console/Mach console/Eterm. */
992
			return sc_seq_or(do_home, 0);
993
		    case 'I': /* Esc [ I == PageUp on FreeBSD console. */
994
			return sc_seq_or(do_page_up, 0);
995
		    case 'L': /* Esc [ L == Insert on ANSI/FreeBSD console. */
996
			return sc_seq_or(do_insertfile_void, 0);
997
		    case 'M': /* Esc [ M == F1 on FreeBSD console. */
998
			return KEY_F(1);
999
		    case 'N': /* Esc [ N == F2 on FreeBSD console. */
1000
			return KEY_F(2);
1001
		    case 'O':
1002
			if (seq_len >= 3) {
1003
			    switch (seq[2]) {
1004
				case 'P': /* Esc [ O P == F1 on xterm. */
1005
				    return KEY_F(1);
1006
				case 'Q': /* Esc [ O Q == F2 on xterm. */
1007
				    return KEY_F(2);
1008
				case 'R': /* Esc [ O R == F3 on xterm. */
1009
				    return KEY_F(3);
1010
				case 'S': /* Esc [ O S == F4 on xterm. */
1011
				    return KEY_F(4);
1012
			    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1013
			} else
1014
			    /* Esc [ O == F3 on FreeBSD console. */
1015
			    return KEY_F(3);
1016
1017
			break;
		    case 'P': /* Esc [ P == F4 on FreeBSD console. */
1018
			return KEY_F(4);
1019
		    case 'Q': /* Esc [ Q == F5 on FreeBSD console. */
1020
			return KEY_F(5);
1021
		    case 'R': /* Esc [ R == F6 on FreeBSD console. */
1022
			return KEY_F(6);
1023
		    case 'S': /* Esc [ S == F7 on FreeBSD console. */
1024
			return KEY_F(7);
1025
		    case 'T': /* Esc [ T == F8 on FreeBSD console. */
1026
			return KEY_F(8);
1027
		    case 'U': /* Esc [ U == PageDown on Mach console. */
1028
			return sc_seq_or(do_page_down, 0);
1029
		    case 'V': /* Esc [ V == PageUp on Mach console. */
1030
			return sc_seq_or(do_page_up, 0);
1031
		    case 'W': /* Esc [ W == F11 on FreeBSD console. */
1032
			return KEY_F(11);
1033
		    case 'X': /* Esc [ X == F12 on FreeBSD console. */
1034
			return KEY_F(12);
1035
		    case 'Y': /* Esc [ Y == End on Mach console. */
1036
			return sc_seq_or(do_end, 0);
1037
		    case 'Z': /* Esc [ Z == F14 on FreeBSD console. */
1038
			return KEY_F(14);
1039
		    case 'a': /* Esc [ a == Shift-Up on rxvt/Eterm. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1040
		    case 'b': /* Esc [ b == Shift-Down on rxvt/Eterm. */
1041
		    case 'c': /* Esc [ c == Shift-Right on rxvt/Eterm. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1042
		    case 'd': /* Esc [ d == Shift-Left on rxvt/Eterm. */
1043
			return arrow_from_abcd(seq[1]);
1044
		    case '[':
1045
			if (seq_len >= 3) {
1046
			    switch (seq[2]) {
1047
1048
				case 'A': /* Esc [ [ A == F1 on Linux
					   * console. */
1049
				    return KEY_F(1);
1050
1051
				case 'B': /* Esc [ [ B == F2 on Linux
					   * console. */
1052
				    return KEY_F(2);
1053
1054
				case 'C': /* Esc [ [ C == F3 on Linux
					   * console. */
1055
				    return KEY_F(3);
1056
1057
				case 'D': /* Esc [ [ D == F4 on Linux
					   * console. */
1058
				    return KEY_F(4);
1059
1060
				case 'E': /* Esc [ [ E == F5 on Linux
					   * console. */
1061
				    return KEY_F(5);
1062
1063
			    }
			}
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1064
1065
1066
1067
			break;
		}
		break;
	}
1068
1069
    }

1070
    return ERR;
1071
1072
}

1073
/* Return the equivalent arrow key value for the case-insensitive
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1074
 * letters A (up), B (down), C (right), and D (left).  These are common
1075
 * to many escape sequences. */
1076
int arrow_from_abcd(int kbinput)
1077
1078
1079
{
    switch (tolower(kbinput)) {
	case 'a':
1080
	    return sc_seq_or(do_up_void, 0);
1081
	case 'b':
1082
	    return sc_seq_or(do_down_void, 0);
1083
	case 'c':
1084
	    return sc_seq_or(do_right, 0);
1085
	case 'd':
1086
	    return sc_seq_or(do_left, 0);
1087
1088
1089
1090
1091
	default:
	    return ERR;
    }
}

1092
/* Interpret the escape sequence in the keystroke buffer, the first
1093
1094
 * character of which is kbinput.  Assume that the keystroke buffer
 * isn't empty, and that the initial escape has already been read in. */
1095
int parse_escape_sequence(WINDOW *win, int kbinput)
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
{
    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);
    seq_len = get_key_buffer_len();
    seq = get_input(NULL, seq_len);
1106
    retval = convert_sequence(seq, seq_len);
1107
1108
1109

    free(seq);

1110
    /* If we got an unrecognized escape sequence, notify the user. */
1111
    if (retval == ERR) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1112
	if (win == edit) {
1113
1114
	    /* TRANSLATORS: This refers to a sequence of escape codes
	     * (from the keyboard) that nano does not know about. */
1115
	    statusline(ALERT, _("Unknown sequence"));
1116
	    suppress_cursorpos = FALSE;
1117
	    lastmessage = HUSH;
1118
1119
1120
1121
	    if (currmenu == MMAIN) {
		reset_cursor();
		curs_set(1);
	    }
1122
1123
1124
	}
    }

1125
#ifdef DEBUG
1126
1127
    fprintf(stderr, "parse_escape_sequence(): kbinput = %d, seq_len = %lu, retval = %d\n",
		kbinput, (unsigned long)seq_len, retval);
1128
1129
1130
1131
1132
#endif

    return retval;
}

1133
1134
/* Translate a byte sequence: turn a three-digit decimal number (from
 * 000 to 255) into its corresponding byte value. */
1135
int get_byte_kbinput(int kbinput)
1136
{
1137
    static int byte_digits = 0, byte = 0;
1138
    int retval = ERR;
1139

1140
1141
    /* Increment the byte digit counter. */
    byte_digits++;
1142

1143
    switch (byte_digits) {
1144
	case 1:
1145
1146
	    /* First digit: This must be from zero to two.  Put it in
	     * the 100's position of the byte sequence holder. */
1147
	    if ('0' <= kbinput && kbinput <= '2')
1148
		byte = (kbinput - '0') * 100;
1149
	    else
1150
1151
		/* This isn't the start of a byte sequence.  Return this
		 * character as the result. */
1152
1153
1154
		retval = kbinput;
	    break;
	case 2:
1155
1156
1157
1158
	    /* 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. */
1159
1160
1161
	    if (('0' <= kbinput && kbinput <= '5') || (byte < 200 &&
		'6' <= kbinput && kbinput <= '9'))
		byte += (kbinput - '0') * 10;
1162
	    else
1163
1164
		/* This isn't the second digit of a byte sequence.
		 * Return this character as the result. */
1165
1166
1167
		retval = kbinput;
	    break;
	case 3:
1168
	    /* Third digit: This must be from zero to five if the first
1169
1170
1171
	     * 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. */
1172
1173
	    if (('0' <= kbinput && kbinput <= '5') || (byte < 250 &&
		'6' <= kbinput && kbinput <= '9')) {
1174
		byte += kbinput - '0';
1175
		/* The byte sequence is complete. */
1176
		retval = byte;
1177
	    } else
1178
1179
		/* This isn't the third digit of a byte sequence.
		 * Return this character as the result. */
1180
1181
		retval = kbinput;
	    break;
1182
	default:
1183
1184
1185
	    /* If there are more than three digits, return this
	     * character as the result.  (Maybe we should produce an
	     * error instead?) */
1186
1187
1188
1189
1190
1191
1192
1193
	    retval = kbinput;
	    break;
    }

    /* If we have a result, reset the byte digit counter and the byte
     * sequence holder. */
    if (retval != ERR) {
	byte_digits = 0;
1194
	byte = 0;
1195
1196
1197
    }

#ifdef DEBUG
1198
    fprintf(stderr, "get_byte_kbinput(): kbinput = %d, byte_digits = %d, byte = %d, retval = %d\n", kbinput, byte_digits, byte, retval);
1199
1200
1201
1202
1203
#endif

    return retval;
}

1204
#ifdef ENABLE_UTF8
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1205
/* If the character in kbinput is a valid hexadecimal digit, multiply it
1206
 * by factor and add the result to uni, and return ERR to signify okay. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1207
1208
1209
1210
1211
1212
1213
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
1214
1215
	/* The character isn't hexadecimal; give it as the result. */
	return (long)kbinput;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1216

1217
    return ERR;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1218
1219
}

1220
/* Translate a Unicode sequence: turn a six-digit hexadecimal number
1221
 * (from 000000 to 10FFFF, case-insensitive) into its corresponding
1222
 * multibyte value. */
1223
long get_unicode_kbinput(int kbinput)
1224
{
1225
1226
1227
    static int uni_digits = 0;
    static long uni = 0;
    long retval = ERR;
1228

1229
    /* Increment the Unicode digit counter. */
1230
    uni_digits++;
1231

1232
    switch (uni_digits) {
1233
	case 1:
1234
1235
	    /* First digit: This must be zero or one.  Put it in the
	     * 0x100000's position of the Unicode sequence holder. */
1236
	    if ('0' <= kbinput && kbinput <= '1')
1237
		uni = (kbinput - '0') * 0x100000;
1238
	    else
1239
1240
		/* This isn't the first digit of a Unicode sequence.
		 * Return this character as the result. */
1241
1242
1243
		retval = kbinput;
	    break;
	case 2:
1244
1245
1246
1247
1248
1249
	    /* Second digit: This must be zero if the first was one, and
	     * may be any hexadecimal value if the first was zero.  Put
	     * it in the 0x10000's position of the Unicode sequence
	     * holder. */
	    if (uni == 0 || '0' == kbinput)
		retval = add_unicode_digit(kbinput, 0x10000, &uni);
1250
	    else
1251
1252
		/* This isn't the second digit of a Unicode sequence.
		 * Return this character as the result. */
1253
1254
1255
		retval = kbinput;
	    break;
	case 3:
1256
1257
1258
1259
	    /* Third digit: This may be any hexadecimal value.  Put it
	     * in the 0x1000's position of the Unicode sequence
	     * holder. */
	    retval = add_unicode_digit(kbinput, 0x1000, &uni);
1260
	    break;
1261
	case 4:
1262
1263
1264
1265
	    /* Fourth digit: This may be any hexadecimal value.  Put it
	     * in the 0x100's position of the Unicode sequence
	     * holder. */
	    retval = add_unicode_digit(kbinput, 0x100, &uni);
1266
	    break;
1267
	case 5:
1268
1269
1270
	    /* Fifth digit: This may be any hexadecimal value.  Put it
	     * in the 0x10's position of the Unicode sequence holder. */
	    retval = add_unicode_digit(kbinput, 0x10, &uni);
1271
	    break;
1272
	case 6:
1273
1274
1275
1276
1277
1278
	    /* Sixth digit: This may be any hexadecimal value.  Put it
	     * in the 0x1's position of the Unicode sequence holder. */
	    retval = add_unicode_digit(kbinput, 0x1, &uni);
	    /* If this character is a valid hexadecimal value, then the
	     * Unicode sequence is complete. */
	    if (retval == ERR)
1279
		retval = uni;
1280
1281
	    break;
    }
1282

1283
1284
    /* If we have a result, reset the Unicode digit counter and the
     * Unicode sequence holder. */
1285
    if (retval != ERR) {
1286
1287
	uni_digits = 0;
	uni = 0;
1288
    }
1289

1290
#ifdef DEBUG
1291
    fprintf(stderr, "get_unicode_kbinput(): kbinput = %d, uni_digits = %d, uni = %ld, retval = %ld\n", kbinput, uni_digits, uni, retval);
1292
1293
#endif

1294
1295
    return retval;
}
1296
#endif /* ENABLE_UTF8 */
1297

1298
1299
1300
1301
1302
1303
/* Translate a control character sequence: turn an ASCII non-control
 * character into its corresponding control character. */
int get_control_kbinput(int kbinput)
{
    int retval;

1304
    /* Ctrl-Space (Ctrl-2, Ctrl-@, Ctrl-`) */
1305
1306
    if (kbinput == ' ' || kbinput == '2')
	retval = NANO_CONTROL_SPACE;
1307
1308
    /* Ctrl-/ (Ctrl-7, Ctrl-_) */
    else if (kbinput == '/')
1309
	retval = NANO_CONTROL_7;
1310
    /* Ctrl-3 (Ctrl-[, Esc) to Ctrl-7 (Ctrl-/, Ctrl-_) */
1311
1312
1313
    else if ('3' <= kbinput && kbinput <= '7')
	retval = kbinput - 24;
    /* Ctrl-8 (Ctrl-?) */
1314
1315
    else if (kbinput == '8' || kbinput == '?')
	retval = NANO_CONTROL_8;
1316
1317
    /* Ctrl-@ (Ctrl-Space, Ctrl-2, Ctrl-`) to Ctrl-_ (Ctrl-/, Ctrl-7) */
    else if ('@' <= kbinput && kbinput <= '_')
1318
	retval = kbinput - '@';
1319
1320
    /* Ctrl-` (Ctrl-2, Ctrl-Space, Ctrl-@) to Ctrl-~ (Ctrl-6, Ctrl-^) */
    else if ('`' <= kbinput && kbinput <= '~')
1321
	retval = kbinput - '`';
1322
1323
1324
    else
	retval = kbinput;

1325
#ifdef DEBUG
1326
    fprintf(stderr, "get_control_kbinput(): kbinput = %d, retval = %d\n", kbinput, retval);
1327
1328
#endif

1329
1330
    return retval;
}
1331

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1332
1333
/* Put the output-formatted characters in output back into the keystroke
 * buffer, so that they can be parsed and displayed as output again. */
1334
void unparse_kbinput(char *output, size_t output_len)
1335
{
1336
1337
    int *input;
    size_t i;
1338

1339
1340
1341
1342
    if (output_len == 0)
	return;

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

1344
1345
    for (i = 0; i < output_len; i++)
	input[i] = (int)output[i];
1346

1347
    unget_input(input, output_len);
1348

1349
    free(input);
1350
1351
}

1352
/* Read in a stream of characters verbatim, and return the length of the
1353
1354
1355
1356
 * string in kbinput_len.  Assume nodelay(win) is FALSE. */
int *get_verbatim_kbinput(WINDOW *win, size_t *kbinput_len)
{
    int *retval;
1357

1358
    /* Turn off flow control characters if necessary so that we can type
1359
1360
     * them in verbatim, and turn the keypad off if necessary so that we
     * don't get extended keypad values. */
1361
1362
    if (ISSET(PRESERVE))
	disable_flow_control();
1363
1364
    if (!ISSET(REBIND_KEYPAD))
	keypad(win, FALSE);
1365
1366
1367

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

    /* Turn flow control characters back on if necessary and turn the
1370
     * keypad back on if necessary now that we're done. */
1371
1372
    if (ISSET(PRESERVE))
	enable_flow_control();
1373
1374
    if (!ISSET(REBIND_KEYPAD))
	keypad(win, TRUE);
1375

1376
    return retval;
1377
1378
}

1379
1380
/* Read in a stream of all available characters, and return the length
 * of the string in kbinput_len.  Translate the first few characters of
1381
 * the input into the corresponding multibyte value if possible.  After
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1382
 * that, leave the input as-is. */
1383
int *parse_verbatim_kbinput(WINDOW *win, size_t *kbinput_len)
1384
{
1385
    int *kbinput, *retval;
1386

1387
    /* Read in the first keystroke. */
1388
1389
    while ((kbinput = get_input(win, 1)) == NULL)
	;
1390

1391
#ifdef ENABLE_UTF8
1392
1393
1394
    if (using_utf8()) {
	/* Check whether the first keystroke is a valid hexadecimal
	 * digit. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1395
	long uni = get_unicode_kbinput(*kbinput);
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414

	/* If the first keystroke isn't a valid hexadecimal digit, put
	 * back the first keystroke. */
	if (uni != ERR)
	    unget_input(kbinput, 1);

	/* Otherwise, read in keystrokes until we have a complete
	 * Unicode sequence, and put back the corresponding Unicode
	 * value. */
	else {
	    char *uni_mb;
	    int uni_mb_len, *seq, i;

	    if (win == edit)
		/* TRANSLATORS: This is displayed during the input of a
		 * six-digit hexadecimal Unicode character code. */
		statusbar(_("Unicode Input"));

	    while (uni == ERR) {
1415
1416
		while ((kbinput = get_input(win, 1)) == NULL)
		    ;
1417
1418
		uni = get_unicode_kbinput(*kbinput);
	    }
1419

1420
1421
1422
	    /* Put back the multibyte equivalent of the Unicode
	     * value. */
	    uni_mb = make_mbchar(uni, &uni_mb_len);
1423

1424
	    seq = (int *)nmalloc(uni_mb_len * sizeof(int));
1425

1426
1427
	    for (i = 0; i < uni_mb_len; i++)
		seq[i] = (unsigned char)uni_mb[i];
1428

1429
	    unget_input(seq, uni_mb_len);
1430

1431
1432
	    free(seq);
	    free(uni_mb);
1433
	}
1434
    } else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1435
1436
#endif /* ENABLE_UTF8 */

1437
1438
	/* Put back the first keystroke. */
	unget_input(kbinput, 1);
1439

1440
1441
    free(kbinput);

1442
    /* Get the complete sequence, and save the characters in it as the
1443
     * result. */
1444
    *kbinput_len = get_key_buffer_len();
1445
    retval = get_input(NULL, *kbinput_len);
1446
1447
1448
1449

    return retval;
}

1450
#ifndef DISABLE_MOUSE
1451
/* Handle any mouse event that may have occurred.  We currently handle
1452
1453
1454
1455
1456
1457
1458
 * 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
1459
1460
1461
1462
1463
1464
 * 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. */
1465
int get_mouseinput(int *mouse_x, int *mouse_y, bool allow_shortcuts)
1466
1467
{
    MEVENT mevent;
1468
    bool in_bottomwin;
1469
    subnfunc *f;
1470
1471
1472
1473
1474
1475

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

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

1478
1479
1480
    /* Save the screen coordinates where the mouse event took place. */
    *mouse_x = mevent.x;
    *mouse_y = mevent.y;
1481

1482
1483
    in_bottomwin = wenclose(bottomwin, *mouse_y, *mouse_x);

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1484
    /* Handle releases/clicks of the first mouse button. */
1485
    if (mevent.bstate & (BUTTON1_RELEASED | BUTTON1_CLICKED)) {
1486
1487
	/* If we're allowing shortcuts, the current shortcut list is
	 * being displayed on the last two lines of the screen, and the
1488
1489
1490
	 * 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. */
1491
	if (allow_shortcuts && !ISSET(NO_HELP) && in_bottomwin) {
1492
1493
1494
1495
	    int i;
		/* The width of all the shortcuts, except for the last
		 * two, in the shortcut list in bottomwin. */
	    int j;
1496
		/* The calculated index number of the clicked item. */
1497
1498
1499
1500
	    size_t currslen;
		/* The number of shortcuts in the current shortcut
		 * list. */

1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
	    /* 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. */
		*mouse_x = mevent.x;
		*mouse_y = mevent.y;

		return 0;
	    }
1515

1516
	    /* Get the shortcut lists' length. */
1517
	    if (currmenu == MMAIN)
1518
		currslen = MAIN_VISIBLE;
1519
	    else {
1520
		currslen = length_of_list(currmenu);
1521

1522
1523
1524
1525
1526
		/* We don't show any more shortcuts than the main list
		 * does. */
		if (currslen > MAIN_VISIBLE)
		    currslen = MAIN_VISIBLE;
	    }
1527

1528
1529
1530
1531
1532
1533
1534
1535
	    /* 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. */
	    if (currslen < 2)
		i = COLS / (MAIN_VISIBLE / 2);
	    else
		i = COLS / ((currslen / 2) + (currslen % 2));

1536
1537
	    /* Calculate the one-based index in the shortcut list. */
	    j = (*mouse_x / i) * 2 + *mouse_y;
1538

1539
1540
	    /* Adjust the index if we hit the last two wider ones. */
	    if ((j > currslen) && (*mouse_x % i < COLS % i))
1541
		j -= 2;
1542
1543
1544
#ifdef DEBUG
	    fprintf(stderr, "Calculated %i as index in shortcut list, currmenu = %x.\n", j, currmenu);
#endif
1545
1546
	    /* Ignore releases/clicks of the first mouse button beyond
	     * the last shortcut. */
1547
	    if (j > currslen)
1548
		return 2;
1549

1550
1551
	    /* Go through the list of functions to determine which
	     * shortcut in the current menu we released/clicked on. */
1552
	    for (f = allfuncs; f != NULL; f = f->next) {
1553
		if ((f->menus & currmenu) == 0)
1554
1555
		    continue;
		if (first_sc_for(currmenu, f->scfunc) == NULL)
1556
		    continue;
1557
1558
		/* Tick off an actually shown shortcut. */
		j -= 1;
1559
1560
		if (j == 0)
		    break;
1561
	    }
1562
#ifdef DEBUG
1563
	    fprintf(stderr, "Stopped on func %ld present in menus %x\n", (long)f->scfunc, f->menus);
1564
#endif
1565

1566
	    /* And put the corresponding key into the keyboard buffer. */
1567
	    if (f != NULL) {
1568
		const sc *s = first_sc_for(currmenu, f->scfunc);
1569
		unget_kbinput(s->seq, s->type == META, s->type == FKEY);
1570
	    }
1571
	    return 1;
1572
	} else
1573
1574
	    /* Handle releases/clicks of the first mouse button that
	     * aren't on the current shortcut list elsewhere. */
1575
	    return 0;
1576
    }
1577
1578
#if NCURSES_MOUSE_VERSION >= 2
    /* Handle presses of the fourth mouse button (upward rolls of the
1579
1580
1581
     * 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
1582
	bool in_edit = wenclose(edit, *mouse_y, *mouse_x);
1583

1584
1585
1586
1587
	if (in_bottomwin)
	    /* Translate the mouse event coordinates so that they're
	     * relative to bottomwin. */
	    wmouse_trafo(bottomwin, mouse_y, mouse_x, FALSE);
1588

1589
	if (in_edit || (in_bottomwin && *mouse_y == 0)) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1590
1591
	    int i;

1592
1593
1594
1595
1596
	    /* 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) ?
1597
			sc_seq_or(do_up_void, 0) : sc_seq_or(do_down_void, 0),
1598
			FALSE, FALSE);
1599
1600
1601
1602
1603
1604
1605

	    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;
1606
1607
    }
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1608
1609
1610

    /* Ignore all other mouse events. */
    return 2;
1611
}
1612
1613
#endif /* !DISABLE_MOUSE */

1614
1615
1616
1617
/* 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. */
1618
const sc *get_shortcut(int *kbinput)
1619
{
1620
    sc *s;
1621

1622
#ifdef DEBUG
1623
    fprintf(stderr, "get_shortcut(): kbinput = %d, meta_key = %s -- ", *kbinput, meta_key ? "TRUE" : "FALSE");
1624
1625
#endif

1626
    for (s = sclist; s != NULL; s = s->next) {
1627
	if ((s->menus & currmenu) && *kbinput == s->seq
1628
		&& meta_key == (s->type == META)) {
1629
#ifdef DEBUG
1630
	    fprintf (stderr, "matched seq \"%s\", and btw meta was %d (menu is %x from %x)\n",
1631
				s->keystr, meta_key, currmenu, s->menus);
1632
#endif
1633
	    return s;
1634
1635
	}
    }
1636
#ifdef DEBUG
1637
    fprintf (stderr, "matched nothing, btw meta was %d\n", meta_key);
1638
#endif
1639
1640
1641
1642

    return NULL;
}

1643
1644
1645
1646
1647
/* Move to (x, y) in win, and display a line of n spaces with the
 * current attributes. */
void blank_line(WINDOW *win, int y, int x, int n)
{
    wmove(win, y, x);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1648

1649
1650
1651
1652
    for (; n > 0; n--)
	waddch(win, ' ');
}

1653
/* Blank the first line of the top portion of the window. */
1654
void blank_titlebar(void)
Chris Allegretta's avatar
Chris Allegretta committed
1655
{
1656
    blank_line(topwin, 0, 0, COLS);
1657
1658
}

1659
1660
/* If the MORE_SPACE flag isn't set, blank the second line of the top
 * portion of the window. */
1661
1662
1663
void blank_topbar(void)
{
    if (!ISSET(MORE_SPACE))
1664
	blank_line(topwin, 1, 0, COLS);
1665
1666
}

1667
/* Blank all the lines of the middle portion of the window, i.e. the
1668
 * edit window. */
Chris Allegretta's avatar
Chris Allegretta committed
1669
1670
void blank_edit(void)
{
1671
    int i;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1672

1673
    for (i = 0; i < editwinrows; i++)
1674
	blank_line(edit, i, 0, COLS);
Chris Allegretta's avatar
Chris Allegretta committed
1675
1676
}

1677
/* Blank the first line of the bottom portion of the window. */
Chris Allegretta's avatar
Chris Allegretta committed
1678
1679
void blank_statusbar(void)
{
1680
    blank_line(bottomwin, 0, 0, COLS);
Chris Allegretta's avatar
Chris Allegretta committed
1681
1682
}

1683
1684
/* If the NO_HELP flag isn't set, blank the last two lines of the bottom
 * portion of the window. */
1685
1686
1687
void blank_bottombars(void)
{
    if (!ISSET(NO_HELP)) {
1688
1689
	blank_line(bottomwin, 1, 0, COLS);
	blank_line(bottomwin, 2, 0, COLS);
1690
1691
1692
    }
}

1693
1694
/* Check if the number of keystrokes needed to blank the statusbar has
 * been pressed.  If so, blank the statusbar, unless constant cursor
1695
 * position display is on and we are in the editing screen. */
1696
void check_statusblank(void)
Chris Allegretta's avatar
Chris Allegretta committed
1697
{
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
    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
1712
1713
1714
    }
}

1715
1716
1717
1718
/* Convert buf into a string that can be displayed on screen.  The
 * caller wants to display buf starting with column start_col, and
 * extending for at most len columns.  start_col is zero-based.  len is
 * one-based, so len == 0 means you get "" returned.  The returned
1719
1720
1721
1722
1723
 * string is dynamically allocated, and should be freed.  If dollars is
 * TRUE, the caller might put "$" at the beginning or end of the line if
 * it's too long. */
char *display_string(const char *buf, size_t start_col, size_t len, bool
	dollars)
1724
1725
{
    size_t start_index;
1726
	/* Index in buf of the first character shown. */
1727
    size_t column;
1728
	/* Screen column that start_index corresponds to. */
1729
1730
1731
1732
1733
1734
    size_t alloc_len;
	/* The length of memory allocated for converted. */
    char *converted;
	/* The string we return. */
    size_t index;
	/* Current position in converted. */
1735
    char *buf_mb;
1736
1737
    int buf_mb_len;

1738
1739
1740
1741
1742
    /* If dollars is TRUE, make room for the "$" at the end of the
     * line. */
    if (dollars && len > 0 && strlenpt(buf) > start_col + len)
	len--;

1743
1744
1745
    if (len == 0)
	return mallocstrcpy(NULL, "");

1746
1747
    buf_mb = charalloc(mb_cur_max());

1748
1749
    start_index = actual_x(buf, start_col);
    column = strnlenpt(buf, start_index);
1750

1751
    assert(column <= start_col);
1752

1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
    /* Make sure there's enough room for the initial character, whether
     * it's a multibyte control character, a non-control multibyte
     * character, a tab character, or a null terminator.  Rationale:
     *
     * multibyte control character followed by a null terminator:
     *     1 byte ('^') + mb_cur_max() bytes + 1 byte ('\0')
     * multibyte non-control character followed by a null terminator:
     *     mb_cur_max() bytes + 1 byte ('\0')
     * tab character followed by a null terminator:
     *     mb_cur_max() bytes + (tabsize - 1) bytes + 1 byte ('\0')
     *
     * Since tabsize has a minimum value of 1, it can substitute for 1
     * byte above. */
    alloc_len = (mb_cur_max() + tabsize + 1) * MAX_BUF_SIZE;
    converted = charalloc(alloc_len);
1768

1769
    index = 0;
1770
    seen_wide = FALSE;
1771

1772
1773
    if (buf[start_index] != '\0' && buf[start_index] != '\t' &&
	(column < start_col || (dollars && column > 0))) {
1774
1775
	/* We don't display all of buf[start_index] since it starts to
	 * the left of the screen. */
1776
	buf_mb_len = parse_mbchar(buf + start_index, buf_mb, NULL);
1777

1778
	if (is_cntrl_mbchar(buf_mb)) {
1779
	    if (column < start_col) {
1780
1781
		char *character = charalloc(mb_cur_max());
		int charlen, i;
1782

1783
		character = control_mbrep(buf_mb, character, &charlen);
1784

1785
1786
		for (i = 0; i < charlen; i++)
		    converted[index++] = character[i];
1787

1788
		start_col += mbwidth(character);
1789

1790
		free(character);
1791

1792
		start_index += buf_mb_len;
1793
	    }
1794
	}
1795
#ifdef ENABLE_UTF8
1796
1797
1798
1799
1800
1801
	else if (using_utf8() && mbwidth(buf_mb) == 2) {
	    if (column >= start_col) {
		converted[index++] = ' ';
		start_col++;
	    }

1802
	    converted[index++] = ' ';
1803
	    start_col++;
1804
1805

	    start_index += buf_mb_len;
1806
	}
1807
#endif
1808
1809
    }

1810
    while (buf[start_index] != '\0') {
1811
	buf_mb_len = parse_mbchar(buf + start_index, buf_mb, NULL);
1812

1813
1814
1815
	if (mbwidth(buf + start_index) > 1)
	    seen_wide = TRUE;

1816
1817
1818
1819
1820
1821
1822
1823
	/* Make sure there's enough room for the next character, whether
	 * it's a multibyte control character, a non-control multibyte
	 * character, a tab character, or a null terminator. */
	if (index + mb_cur_max() + tabsize + 1 >= alloc_len - 1) {
	    alloc_len += (mb_cur_max() + tabsize + 1) * MAX_BUF_SIZE;
	    converted = charealloc(converted, alloc_len);
	}

1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
	if (*buf_mb == ' ') {
	    /* 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++;
	} else if (*buf_mb == '\t') {
1837
	    /* Show a tab as a visible character, or as as a space. */
1838
#ifndef NANO_TINY
1839
	    if (ISSET(WHITESPACE_DISPLAY)) {
1840
		int i = 0;
1841

1842
1843
		while (i < whitespace_len[0])
		    converted[index++] = whitespace[i++];
1844
	    } else
1845
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1846
		converted[index++] = ' ';
1847
	    start_col++;
1848
	    /* Fill the tab up with the required number of spaces. */
1849
	    while (start_col % tabsize != 0) {
1850
		converted[index++] = ' ';
1851
1852
		start_col++;
	    }
1853
	/* If buf contains a control character, interpret it. */
1854
	} else if (is_cntrl_mbchar(buf_mb)) {
1855
1856
	    char *character = charalloc(mb_cur_max());
	    int charlen, i;
1857

1858
	    converted[index++] = '^';
1859
1860
	    start_col++;

1861
	    character = control_mbrep(buf_mb, character, &charlen);
1862

1863
1864
	    for (i = 0; i < charlen; i++)
		converted[index++] = character[i];
1865

1866
	    start_col += mbwidth(character);
1867

1868
	    free(character);
1869
1870
	/* If buf contains a non-control character, interpret it.  If buf
	 * contains an invalid multibyte sequence, display it as such. */
1871
	} else {
1872
1873
	    char *character = charalloc(mb_cur_max());
	    int charlen, i;
1874

1875
#ifdef ENABLE_UTF8
1876
1877
1878
	    /* Make sure an invalid sequence-starter byte is properly
	     * terminated, so that it doesn't pick up lingering bytes
	     * of any previous content. */
1879
1880
1881
	    if (using_utf8() && buf_mb_len == 1)
		buf_mb[1] = '\0';
#endif
1882
	    character = mbrep(buf_mb, character, &charlen);
1883

1884
1885
	    for (i = 0; i < charlen; i++)
		converted[index++] = character[i];
1886

1887
	    start_col += mbwidth(character);
1888

1889
	    free(character);
1890
1891
	}

1892
	start_index += buf_mb_len;
1893
1894
    }

1895
1896
    free(buf_mb);

1897
1898
    assert(alloc_len >= index + 1);

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1899
    /* Null-terminate converted. */
1900
    converted[index] = '\0';
1901
1902
1903

    /* Make sure converted takes up no more than len columns. */
    index = actual_x(converted, len);
1904
    null_at(&converted, index);
1905

1906
    return converted;
1907
1908
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1909
1910
1911
1912
1913
1914
/* 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. */
1915
void titlebar(const char *path)
Chris Allegretta's avatar
Chris Allegretta committed
1916
{
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
    size_t verlen, prefixlen, pathlen, statelen;
	/* The width of the different titlebar elements, in columns. */
    size_t pluglen = 0;
	/* The width that "Modified" would take up. */
    size_t offset = 0;
	/* The position at which the center part of the titlebar starts. */
    const char *prefix = "";
	/* What is shown before the path -- "File:", "DIR:", or "". */
    const char *state = "";
	/* The state of the current buffer -- "Modified", "View", or "". */
    char *fragment;
	/* The tail part of the pathname when dottified. */
1929

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

1932
1933
1934
    if (interface_color_pair[TITLE_BAR].bright)
	wattron(topwin, A_BOLD);
    wattron(topwin, interface_color_pair[TITLE_BAR].pairnum);
1935

1936
    blank_titlebar();
Chris Allegretta's avatar
Chris Allegretta committed
1937

1938
1939
1940
    /* 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
1941

1942
    /* Figure out the path, prefix and state strings. */
1943
#ifndef DISABLE_BROWSER
1944
1945
1946
1947
    if (path != NULL)
	prefix = _("DIR:");
    else
#endif
1948
1949
1950
1951
1952
1953
1954
    {
	if (openfile->filename[0] == '\0')
	    path = _("New Buffer");
	else {
	    path = openfile->filename;
	    prefix = _("File:");
	}
1955

1956
1957
1958
1959
	if (openfile->modified)
	    state = _("Modified");
	else if (ISSET(VIEW_MODE))
	    state = _("View");
1960

1961
1962
	pluglen = strlenpt(_("Modified")) + 1;
    }
1963

1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
    /* 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;
1974
1975
    }

1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
    /* 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;
1988
1989
1990
	}
    }

1991
1992
1993
1994
    /* If we have side spaces left, center the path name. */
    if (verlen > 0)
	offset = verlen + (COLS - (verlen + pluglen + statelen) -
					(prefixlen + pathlen)) / 2;
1995

1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
    /* Only print the prefix when there is room for it. */
    if (verlen + prefixlen + pathlen + pluglen + statelen <= COLS) {
	mvwaddstr(topwin, 0, offset, prefix);
	if (prefixlen > 0)
	    waddstr(topwin, " ");
    } else
	wmove(topwin, 0, offset);

    /* Print the full path if there's room; otherwise, dottify it. */
    if (pathlen + pluglen + statelen <= COLS)
	waddstr(topwin, path);
    else if (5 + statelen <= COLS) {
	waddstr(topwin, "...");
	fragment = display_string(path, 3 + pathlen - COLS + statelen,
					COLS - statelen, FALSE);
	waddstr(topwin, fragment);
	free(fragment);
2013
    }
2014

2015
2016
2017
2018
2019
2020
    /* 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));

2021
2022
    wattroff(topwin, A_BOLD);
    wattroff(topwin, interface_color_pair[TITLE_BAR].pairnum);
2023

2024
    wnoutrefresh(topwin);
Chris Allegretta's avatar
Chris Allegretta committed
2025
    reset_cursor();
2026
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
2027
2028
}

2029
2030
2031
2032
2033
2034
/* Display a normal message on the statusbar, quietly. */
void statusbar(const char *msg)
{
    statusline(HUSH, msg);
}

2035
/* Display a message on the statusbar, and set suppress_cursorpos to
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2036
2037
 * TRUE, so that the message won't be immediately overwritten if
 * constant cursor position display is on. */
2038
void statusline(message_type importance, const char *msg, ...)
2039
2040
{
    va_list ap;
2041
    char *bar, *foo;
Benno Schulenberg's avatar
Benno Schulenberg committed
2042
    size_t start_x;
2043
2044
2045
2046
#ifndef NANO_TINY
    bool old_whitespace = ISSET(WHITESPACE_DISPLAY);

    UNSET(WHITESPACE_DISPLAY);
2047
#endif
2048
2049
2050
2051
2052

    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(). */
2053
    if (isendwin()) {
2054
2055
2056
2057
2058
	vfprintf(stderr, msg, ap);
	va_end(ap);
	return;
    }

2059
2060
2061
2062
2063
2064
2065
    /* If there already was an alert message, ignore lesser ones. */
    if ((lastmessage == ALERT && importance != ALERT) ||
		(lastmessage == MILD && importance == HUSH))
	return;

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

2068
    if (importance == ALERT)
2069
	beep();
2070
2071

    lastmessage = importance;
2072

2073
2074
2075
    /* Turn the cursor off while fiddling in the statusbar. */
    curs_set(0);

2076
2077
    blank_statusbar();

2078
2079
2080
2081
    bar = charalloc(mb_cur_max() * (COLS - 3));
    vsnprintf(bar, mb_cur_max() * (COLS - 3), msg, ap);
    va_end(ap);
    foo = display_string(bar, 0, COLS - 4, FALSE);
Benno Schulenberg's avatar
Benno Schulenberg committed
2082
    free(bar);
2083
2084

#ifndef NANO_TINY
2085
2086
    if (old_whitespace)
	SET(WHITESPACE_DISPLAY);
2087
#endif
Benno Schulenberg's avatar
Benno Schulenberg committed
2088
    start_x = (COLS - strlenpt(foo) - 4) / 2;
2089

2090
    wmove(bottomwin, 0, start_x);
2091
2092
2093
    if (interface_color_pair[STATUS_BAR].bright)
	wattron(bottomwin, A_BOLD);
    wattron(bottomwin, interface_color_pair[STATUS_BAR].pairnum);
2094
2095
2096
2097
    waddstr(bottomwin, "[ ");
    waddstr(bottomwin, foo);
    free(foo);
    waddstr(bottomwin, " ]");
2098
2099
    wattroff(bottomwin, A_BOLD);
    wattroff(bottomwin, interface_color_pair[STATUS_BAR].pairnum);
2100

2101
    /* Push the message to the screen straightaway. */
2102
    wnoutrefresh(bottomwin);
2103
    doupdate();
2104

2105
    suppress_cursorpos = TRUE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2106

2107
    /* If we're doing quick statusbar blanking, blank it after just one
2108
2109
     * keystroke.  Otherwise, blank it after twenty-six keystrokes, as
     * Pico does. */
2110
#ifndef NANO_TINY
2111
2112
2113
    if (ISSET(QUICK_BLANK))
	statusblank = 1;
    else
2114
#endif
2115
	statusblank = 26;
2116
2117
}

2118
2119
/* Display the shortcut list corresponding to menu on the last two rows
 * of the bottom portion of the window. */
2120
void bottombars(int menu)
Chris Allegretta's avatar
Chris Allegretta committed
2121
{
2122
    size_t i, colwidth, slen;
2123
2124
    subnfunc *f;
    const sc *s;
2125

2126
2127
2128
    /* Set the global variable to the given menu. */
    currmenu = menu;

Chris Allegretta's avatar
Chris Allegretta committed
2129
2130
2131
    if (ISSET(NO_HELP))
	return;

2132
    if (menu == MMAIN) {
2133
	slen = MAIN_VISIBLE;
2134

2135
	assert(slen <= length_of_list(menu));
2136
    } else {
2137
	slen = length_of_list(menu);
2138

2139
	/* Don't show any more shortcuts than the main list does. */
2140
2141
2142
2143
	if (slen > MAIN_VISIBLE)
	    slen = MAIN_VISIBLE;
    }

2144
2145
2146
2147
    /* There will be this many characters per column, except for the
     * last two, which will be longer by (COLS % colwidth) columns so as
     * to not waste space.  We need at least three columns to display
     * anything properly. */
2148
    colwidth = COLS / ((slen / 2) + (slen % 2));
Chris Allegretta's avatar
Chris Allegretta committed
2149

2150
    blank_bottombars();
2151

2152
2153
2154
#ifdef DEBUG
    fprintf(stderr, "In bottombars, and slen == \"%d\"\n", (int) slen);
#endif
2155

2156
    for (f = allfuncs, i = 0; i < slen && f != NULL; f = f->next) {
2157

2158
#ifdef DEBUG
2159
	fprintf(stderr, "Checking menu items....");
2160
#endif
2161
	if ((f->menus & menu) == 0)
2162
	    continue;
2163

2164
#ifdef DEBUG
2165
	fprintf(stderr, "found one! f->menus = %x, desc = \"%s\"\n", f->menus, f->desc);
2166
#endif
2167
2168
	s = first_sc_for(menu, f->scfunc);
	if (s == NULL) {
2169
2170
2171
#ifdef DEBUG
	    fprintf(stderr, "Whoops, guess not, no shortcut key found for func!\n");
#endif
2172
2173
	    continue;
	}
2174
	wmove(bottomwin, 1 + i % 2, (i / 2) * colwidth);
2175
#ifdef DEBUG
2176
	fprintf(stderr, "Calling onekey with keystr \"%s\" and desc \"%s\"\n", s->keystr, f->desc);
2177
#endif
2178
	onekey(s->keystr, _(f->desc), colwidth + (COLS % colwidth));
2179
	i++;
Chris Allegretta's avatar
Chris Allegretta committed
2180
    }
2181

2182
2183
    wnoutrefresh(bottomwin);
    reset_cursor();
2184
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
2185
2186
}

2187
2188
/* 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
2189
 * to write at most length characters, even if length is very small and
2190
2191
 * keystroke and desc are long.  Note that waddnstr(,,(size_t)-1) adds
 * the whole string!  We do not bother padding the entry with blanks. */
2192
void onekey(const char *keystroke, const char *desc, int length)
Chris Allegretta's avatar
Chris Allegretta committed
2193
{
2194
2195
    assert(keystroke != NULL && desc != NULL);

2196
2197
2198
    if (interface_color_pair[KEY_COMBO].bright)
	wattron(bottomwin, A_BOLD);
    wattron(bottomwin, interface_color_pair[KEY_COMBO].pairnum);
2199
    waddnstr(bottomwin, keystroke, actual_x(keystroke, length));
2200
2201
    wattroff(bottomwin, A_BOLD);
    wattroff(bottomwin, interface_color_pair[KEY_COMBO].pairnum);
2202

2203
    length -= strlenpt(keystroke) + 1;
2204

2205
    if (length > 0) {
2206
	waddch(bottomwin, ' ');
2207
2208
2209
	if (interface_color_pair[FUNCTION_TAG].bright)
	    wattron(bottomwin, A_BOLD);
	wattron(bottomwin, interface_color_pair[FUNCTION_TAG].pairnum);
2210
	waddnstr(bottomwin, desc, actual_x(desc, length));
2211
2212
	wattroff(bottomwin, A_BOLD);
	wattroff(bottomwin, interface_color_pair[FUNCTION_TAG].pairnum);
Chris Allegretta's avatar
Chris Allegretta committed
2213
2214
2215
    }
}

2216
2217
/* Redetermine current_y from the position of current relative to edittop,
 * and put the cursor in the edit window at (current_y, current_x). */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2218
2219
void reset_cursor(void)
{
2220
    size_t xpt = xplustabs();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2221

2222
#ifndef NANO_TINY
2223
    if (ISSET(SOFTWRAP)) {
2224
	filestruct *line = openfile->edittop;
2225
2226
	openfile->current_y = 0;

2227
2228
2229
2230
	while (line != NULL && line != openfile->current) {
	    openfile->current_y += strlenpt(line->data) / COLS + 1;
	    line = line->next;
	}
2231
	openfile->current_y += xpt / COLS;
2232

2233
	if (openfile->current_y < editwinrows)
2234
	    wmove(edit, openfile->current_y, xpt % COLS);
2235
2236
2237
    } else
#endif
    {
2238
	openfile->current_y = openfile->current->lineno -
2239
				openfile->edittop->lineno;
2240
2241
2242
2243

	if (openfile->current_y < editwinrows)
	    wmove(edit, openfile->current_y, xpt - get_page_start(xpt));
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2244
}
Chris Allegretta's avatar
Chris Allegretta committed
2245

2246
2247
2248
2249
2250
2251
2252
2253
/* edit_draw() takes care of the job of actually painting a line into
 * the edit window.  fileptr is the line to be painted, at row line of
 * the window.  converted is the actual string to be written to the
 * window, with tabs and control characters replaced by strings of
 * regular characters.  start is the column number of the first
 * character of this page.  That is, the first character of converted
 * corresponds to character number actual_x(fileptr->data, start) of the
 * line. */
2254
void edit_draw(filestruct *fileptr, const char *converted, int
2255
	line, size_t start)
Chris Allegretta's avatar
Chris Allegretta committed
2256
{
2257
#if !defined(NANO_TINY) || !defined(DISABLE_COLOR)
2258
2259
2260
2261
2262
2263
2264
2265
2266
    size_t startpos = actual_x(fileptr->data, start);
	/* The position in fileptr->data of the leftmost character
	 * that displays at least partially on the window. */
    size_t endpos = actual_x(fileptr->data, start + COLS - 1) + 1;
	/* The position in fileptr->data of the first character that is
	 * completely off the window to the right.
	 *
	 * Note that endpos might be beyond the null terminator of the
	 * string. */
2267
2268
#endif

2269
    assert(openfile != NULL && fileptr != NULL && converted != NULL);
2270
    assert(strlenpt(converted) <= COLS);
2271

2272
2273
2274
    /* First simply paint the line -- then we'll add colors or the
     * marking highlight on just the pieces that need it. */
    mvwaddstr(edit, line, 0, converted);
2275

2276
#ifndef USE_SLANG
2277
2278
2279
    /* 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. */
2280
2281
    if (seen_wide)
	wredrawln(edit, line, 1);
2282
#endif
2283

2284
#ifndef DISABLE_COLOR
2285
2286
2287
    /* If color syntaxes are available and turned on, we need to display
     * them. */
    if (openfile->colorstrings != NULL && !ISSET(NO_COLOR_SYNTAX)) {
2288
	const colortype *varnish = openfile->colorstrings;
2289

2290
2291
2292
2293
	/* If there are multiline regexes, make sure there is a cache. */
	if (openfile->syntax->nmultis > 0)
	    alloc_multidata_if_needed(fileptr);

2294
	for (; varnish != NULL; varnish = varnish->next) {
2295
2296
	    int x_start;
		/* Starting column for mvwaddnstr.  Zero-based. */
2297
	    int paintlen = 0;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2298
2299
		/* Number of chars to paint on this line.  There are
		 * COLS characters on a whole line. */
2300
	    size_t index;
2301
		/* Index in converted where we paint. */
2302
2303
2304
2305
	    regmatch_t startmatch;
		/* Match position for start_regex. */
	    regmatch_t endmatch;
		/* Match position for end_regex. */
2306

2307
	    if (varnish->bright)
2308
		wattron(edit, A_BOLD);
2309
	    wattron(edit, COLOR_PAIR(varnish->pairnum));
2310
2311
2312
	    /* 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. */
2313

2314
2315
	    /* First case: varnish is a single-line expression. */
	    if (varnish->end == NULL) {
2316
2317
2318
		size_t k = 0;

		/* We increment k by rm_eo, to move past the end of the
2319
		 * last match.  Even though two matches may overlap, we
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2320
2321
		 * want to ignore them, so that we can highlight e.g. C
		 * strings correctly. */
2322
2323
2324
		while (k < endpos) {
		    /* Note the fifth parameter to regexec().  It says
		     * not to match the beginning-of-line character
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2325
2326
2327
		     * unless k is zero.  If regexec() returns
		     * REG_NOMATCH, there are no more matches in the
		     * line. */
2328
		    if (regexec(varnish->start, &fileptr->data[k], 1,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2329
2330
			&startmatch, (k == 0) ? 0 : REG_NOTBOL) ==
			REG_NOMATCH)
2331
			break;
2332
2333
		    /* Translate the match to the beginning of the
		     * line. */
2334
2335
		    startmatch.rm_so += k;
		    startmatch.rm_eo += k;
2336
2337
2338

		    /* Skip over a zero-length regex match. */
		    if (startmatch.rm_so == startmatch.rm_eo)
2339
			startmatch.rm_eo++;
2340
		    else if (startmatch.rm_so < endpos &&
2341
			startmatch.rm_eo > startpos) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2342
2343
			x_start = (startmatch.rm_so <= startpos) ? 0 :
				strnlenpt(fileptr->data,
2344
				startmatch.rm_so) - start;
2345

2346
2347
2348
			index = actual_x(converted, x_start);

			paintlen = actual_x(converted + index,
2349
2350
				strnlenpt(fileptr->data,
				startmatch.rm_eo) - start - x_start);
2351
2352
2353

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

2354
			mvwaddnstr(edit, line, x_start, converted +
2355
				index, paintlen);
2356
		    }
2357
		    k = startmatch.rm_eo;
Chris Allegretta's avatar
Chris Allegretta committed
2358
		}
2359
	    } else {	/* Second case: varnish is a multiline expression. */
2360
		const filestruct *start_line = fileptr->prev;
2361
		    /* The first line before fileptr that matches 'start'. */
2362
		size_t start_col;
2363
		    /* Where the match starts in that line. */
2364
		const filestruct *end_line;
2365
		    /* The line that matches 'end'. */
2366

2367
		/* First see if the multidata was maybe already calculated. */
2368
		if (fileptr->multidata[varnish->id] == CNONE)
2369
		    goto tail_of_loop;
2370
		else if (fileptr->multidata[varnish->id] == CWHOLELINE) {
2371
		    mvwaddnstr(edit, line, 0, converted, -1);
2372
		    goto tail_of_loop;
2373
2374
		} else if (fileptr->multidata[varnish->id] == CBEGINBEFORE) {
		    regexec(varnish->end, fileptr->data, 1, &endmatch, 0);
2375
2376
		    /* If the coloured part is scrolled off, skip it. */
		    if (endmatch.rm_eo <= startpos)
2377
			goto tail_of_loop;
2378
2379
2380
		    paintlen = actual_x(converted, strnlenpt(fileptr->data,
			endmatch.rm_eo) - start);
		    mvwaddnstr(edit, line, 0, converted, paintlen);
2381
		    goto tail_of_loop;
2382
		} if (fileptr->multidata[varnish->id] == -1)
2383
		    /* Assume this until proven otherwise below. */
2384
		    fileptr->multidata[varnish->id] = CNONE;
2385

2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
		/* There is no precalculated multidata, so find it out now.
		 * First check if the beginning of the line is colored by a
		 * start on an earlier line, and an end on this line or later.
		 *
		 * So: find the first line before fileptr matching the start.
		 * If every match on that line is followed by an end, then go
		 * to step two.  Otherwise, find a line after start_line that
		 * matches the end.  If that line is not before fileptr, then
		 * paint the beginning of this line. */

2396
		while (start_line != NULL && regexec(varnish->start,
2397
2398
2399
			start_line->data, 1, &startmatch, 0) == REG_NOMATCH) {
		    /* There is no start; but if there is an end on this line,
		     * there is no need to look for starts on earlier lines. */
2400
		    if (regexec(varnish->end, start_line->data, 0, NULL, 0) == 0)
2401
2402
2403
			goto step_two;
		    start_line = start_line->prev;
		}
2404

2405
2406
2407
2408
		/* If no start was found, skip to the next step. */
		if (start_line == NULL)
		    goto step_two;

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

2416
		/* Skip over a zero-length regex match. */
2417
		if (startmatch.rm_so == startmatch.rm_eo)
2418
2419
		    goto tail_of_loop;

2420
2421
2422
2423
2424
2425
2426
		/* Now start_line is the first line before fileptr containing
		 * a start match.  Is there a start on that line not followed
		 * by an end on that line? */
		start_col = 0;
		while (TRUE) {
		    start_col += startmatch.rm_so;
		    startmatch.rm_eo -= startmatch.rm_so;
2427
		    if (regexec(varnish->end, start_line->data +
2428
2429
2430
				start_col + startmatch.rm_eo, 0, NULL,
				(start_col + startmatch.rm_eo == 0) ?
				0 : REG_NOTBOL) == REG_NOMATCH)
2431
2432
2433
			/* No end found after this start. */
			break;
		    start_col++;
2434
2435
		    if (regexec(varnish->start, start_line->data + start_col,
				1, &startmatch, REG_NOTBOL) == REG_NOMATCH)
2436
2437
2438
2439
2440
2441
2442
2443
2444
			/* No later start on this line. */
			goto step_two;
		}
		/* Indeed, there is a start without an end on that line. */

		/* We've already checked that there is no end before fileptr
		 * and after the start.  But is there an end after the start
		 * at all?  We don't paint unterminated starts. */
		end_line = fileptr;
2445
		while (end_line != NULL && regexec(varnish->end,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2446
			end_line->data, 1, &endmatch, 0) == REG_NOMATCH)
2447
		    end_line = end_line->next;
2448

2449
2450
2451
2452
		/* If no end was found, or it is too early, next step. */
		if (end_line == NULL)
		    goto step_two;
		if (end_line == fileptr && endmatch.rm_eo <= startpos) {
2453
		    fileptr->multidata[varnish->id] = CBEGINBEFORE;
2454
2455
		    goto step_two;
		}
2456

2457
2458
2459
2460
2461
2462
2463
		/* Now paint the start of fileptr.  If the start of fileptr
		 * is on a different line from the end, paintlen is -1, which
		 * means that everything on the line gets painted.  Otherwise,
		 * paintlen is the expanded location of the end of the match
		 * minus the expanded location of the beginning of the page. */
		if (end_line != fileptr) {
		    paintlen = -1;
2464
		    fileptr->multidata[varnish->id] = CWHOLELINE;
2465
#ifdef DEBUG
2466
    fprintf(stderr, "  Marking for id %i  line %i as CWHOLELINE\n", varnish->id, line);
2467
#endif
2468
2469
2470
		} else {
		    paintlen = actual_x(converted, strnlenpt(fileptr->data,
						endmatch.rm_eo) - start);
2471
		    fileptr->multidata[varnish->id] = CBEGINBEFORE;
2472
#ifdef DEBUG
2473
    fprintf(stderr, "  Marking for id %i  line %i as CBEGINBEFORE\n", varnish->id, line);
2474
#endif
2475
2476
2477
2478
2479
2480
		}
		mvwaddnstr(edit, line, 0, converted, paintlen);
		/* If the whole line has been painted, don't bother looking
		 * for any more starts. */
		if (paintlen < 0)
		    goto tail_of_loop;
2481
  step_two:
2482
2483
2484
2485
2486
		/* Second step: look for starts on this line, but start
		 * looking only after an end match, if there is one. */
		start_col = (paintlen == 0) ? 0 : endmatch.rm_eo;

		while (start_col < endpos) {
2487
		    if (regexec(varnish->start, fileptr->data + start_col,
2488
2489
				1, &startmatch, (start_col == 0) ?
				0 : REG_NOTBOL) == REG_NOMATCH ||
2490
				start_col + startmatch.rm_so >= endpos)
2491
2492
			/* No more starts on this line. */
			break;
2493

2494
2495
2496
2497
2498
2499
2500
		    /* Translate the match to be relative to the
		     * beginning of the line. */
		    startmatch.rm_so += start_col;
		    startmatch.rm_eo += start_col;

		    x_start = (startmatch.rm_so <= startpos) ?
				0 : strnlenpt(fileptr->data,
2501
				startmatch.rm_so) - start;
2502

2503
		    index = actual_x(converted, x_start);
2504

2505
		    if (regexec(varnish->end, fileptr->data +
2506
				startmatch.rm_eo, 1, &endmatch,
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
				(startmatch.rm_eo == 0) ?
				0 : REG_NOTBOL) == 0) {
			/* Translate the end match to be relative to
			 * the beginning of the line. */
			endmatch.rm_so += startmatch.rm_eo;
			endmatch.rm_eo += startmatch.rm_eo;
			/* There is an end on this line.  But does
			 * it appear on this page, and is the match
			 * more than zero characters long? */
			if (endmatch.rm_eo > startpos &&
2517
				endmatch.rm_eo > startmatch.rm_so) {
2518
			    paintlen = actual_x(converted + index,
2519
					strnlenpt(fileptr->data,
2520
					endmatch.rm_eo) - start - x_start);
2521

2522
			    assert(0 <= x_start && x_start < COLS);
2523

2524
			    mvwaddnstr(edit, line, x_start,
2525
					converted + index, paintlen);
2526
			    if (paintlen > 0) {
2527
				fileptr->multidata[varnish->id] = CSTARTENDHERE;
2528
#ifdef DEBUG
2529
    fprintf(stderr, "  Marking for id %i  line %i as CSTARTENDHERE\n", varnish->id, line);
2530
#endif
2531
			    }
2532
2533
			}
			start_col = endmatch.rm_eo;
2534
2535
2536
			/* Skip over a zero-length match. */
			if (endmatch.rm_so == endmatch.rm_eo)
			    start_col += 1;
2537
2538
2539
2540
		    } else {
			/* There is no end on this line.  But we haven't yet
			 * looked for one on later lines. */
			end_line = fileptr->next;
2541

2542
			while (end_line != NULL &&
2543
				regexec(varnish->end, end_line->data,
2544
				0, NULL, 0) == REG_NOMATCH)
2545
			    end_line = end_line->next;
2546

2547
2548
2549
			/* If there is no end, we're done on this line. */
			if (end_line == NULL)
			    break;
2550

2551
2552
2553
2554
			assert(0 <= x_start && x_start < COLS);

			/* Paint the rest of the line. */
			mvwaddnstr(edit, line, x_start, converted + index, -1);
2555
			fileptr->multidata[varnish->id] = CENDAFTER;
2556
#ifdef DEBUG
2557
    fprintf(stderr, "  Marking for id %i  line %i as CENDAFTER\n", varnish->id, line);
2558
#endif
2559
2560
2561
			/* We've painted to the end of the line, so don't
			 * bother checking for any more starts. */
			break;
2562
		    }
2563
2564
		}
	    }
2565
  tail_of_loop:
2566
	    wattroff(edit, A_BOLD);
2567
	    wattroff(edit, COLOR_PAIR(varnish->pairnum));
2568
	}
2569
    }
2570
#endif /* !DISABLE_COLOR */
2571

2572
#ifndef NANO_TINY
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
    /* 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. */
2584
	int x_start;
2585
	    /* The starting column for mvwaddnstr().  Zero-based. */
2586
	int paintlen;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2587
2588
	    /* Number of characters to paint on this line.  There are
	     * COLS characters on a whole line. */
2589
	size_t index;
2590
	    /* Index in converted where we paint. */
2591

2592
	mark_order(&top, &top_x, &bot, &bot_x, NULL);
2593
2594
2595
2596
2597

	if (top->lineno < fileptr->lineno || top_x < startpos)
	    top_x = startpos;
	if (bot->lineno > fileptr->lineno || bot_x > endpos)
	    bot_x = endpos;
Chris Allegretta's avatar
Chris Allegretta committed
2598

2599
	/* Only paint if the marked bit of fileptr is on this page. */
2600
2601
	if (top_x < endpos && bot_x > startpos) {
	    assert(startpos <= top_x);
2602
2603
2604
2605

	    /* x_start is the expanded location of the beginning of the
	     * mark minus the beginning of the page. */
	    x_start = strnlenpt(fileptr->data, top_x) - start;
2606

2607
2608
2609
2610
2611
	    /* If the end of the mark is off the page, paintlen is -1,
	     * meaning that everything on the line gets painted.
	     * Otherwise, paintlen is the expanded location of the end
	     * of the mark minus the expanded location of the beginning
	     * of the mark. */
2612
2613
2614
	    if (bot_x >= endpos)
		paintlen = -1;
	    else
2615
		paintlen = strnlenpt(fileptr->data, bot_x) - (x_start + start);
2616
2617
2618
2619
2620
2621
2622
2623

	    /* If x_start is before the beginning of the page, shift
	     * paintlen x_start characters to compensate, and put
	     * x_start at the beginning of the page. */
	    if (x_start < 0) {
		paintlen += x_start;
		x_start = 0;
	    }
2624
2625
2626

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

2627
	    index = actual_x(converted, x_start);
2628

2629
2630
2631
	    if (paintlen > 0)
		paintlen = actual_x(converted + index, paintlen);

2632
	    wattron(edit, hilite_attribute);
2633
	    mvwaddnstr(edit, line, x_start, converted + index, paintlen);
2634
	    wattroff(edit, hilite_attribute);
Chris Allegretta's avatar
Chris Allegretta committed
2635
	}
2636
    }
2637
#endif /* !NANO_TINY */
Chris Allegretta's avatar
Chris Allegretta committed
2638
2639
}

2640
/* Just update one line in the edit buffer.  This is basically a wrapper
2641
 * for edit_draw().  The line will be displayed starting with
2642
 * fileptr->data[index].  Likely arguments are current_x or zero.
2643
 * Returns: Number of additional lines consumed (needed for SOFTWRAP). */
2644
int update_line(filestruct *fileptr, size_t index)
Chris Allegretta's avatar
Chris Allegretta committed
2645
{
2646
    int line = 0;
2647
	/* The line in the edit window that we want to update. */
2648
    int extralinesused = 0;
2649
2650
2651
2652
    char *converted;
	/* fileptr->data converted to have tabs and control characters
	 * expanded. */
    size_t page_start;
Chris Allegretta's avatar
Chris Allegretta committed
2653

2654
    assert(fileptr != NULL);
2655

2656
#ifndef NANO_TINY
2657
    if (ISSET(SOFTWRAP)) {
2658
2659
	filestruct *tmp;

2660
2661
	for (tmp = openfile->edittop; tmp && tmp != fileptr; tmp = tmp->next)
	    line += (strlenpt(tmp->data) / COLS) + 1;
2662
    } else
2663
#endif
2664
2665
	line = fileptr->lineno - openfile->edittop->lineno;

2666
    if (line < 0 || line >= editwinrows)
Chris Allegretta's avatar
Chris Allegretta committed
2667
	return 1;
2668

2669
    /* First, blank out the line. */
2670
    blank_line(edit, line, 0, COLS);
2671

2672
2673
    /* Next, convert variables that index the line to their equivalent
     * positions in the expanded line. */
2674
#ifndef NANO_TINY
2675
2676
2677
    if (ISSET(SOFTWRAP))
	index = 0;
    else
2678
#endif
2679
	index = strnlenpt(fileptr->data, index);
2680
    page_start = get_page_start(index);
2681

2682
2683
    /* Expand the line, replacing tabs with spaces, and control
     * characters with their displayed forms. */
2684
2685
2686
#ifdef NANO_TINY
    converted = display_string(fileptr->data, page_start, COLS, TRUE);
#else
2687
2688
2689
    converted = display_string(fileptr->data, page_start, COLS, !ISSET(SOFTWRAP));
#ifdef DEBUG
    if (ISSET(SOFTWRAP) && strlen(converted) >= COLS - 2)
2690
	fprintf(stderr, "update_line(): converted(1) line = %s\n", converted);
2691
#endif
2692
#endif /* !NANO_TINY */
2693

2694
    /* Paint the line. */
2695
    edit_draw(fileptr, converted, line, page_start);
2696
    free(converted);
Chris Allegretta's avatar
Chris Allegretta committed
2697

2698
#ifndef NANO_TINY
2699
    if (!ISSET(SOFTWRAP)) {
2700
#endif
2701
2702
2703
2704
	if (page_start > 0)
	    mvwaddch(edit, line, 0, '$');
	if (strlenpt(fileptr->data) > page_start + COLS)
	    mvwaddch(edit, line, COLS - 1, '$');
2705
#ifndef NANO_TINY
2706
    } else {
2707
	size_t full_length = strlenpt(fileptr->data);
2708
	for (index += COLS; index <= full_length && line < editwinrows - 1; index += COLS) {
2709
2710
	    line++;
#ifdef DEBUG
2711
	    fprintf(stderr, "update_line(): softwrap code, moving to %d index %lu\n", line, (unsigned long)index);
2712
#endif
2713
	    blank_line(edit, line, 0, COLS);
2714
2715

	    /* Expand the line, replacing tabs with spaces, and control
2716
	     * characters with their displayed forms. */
2717
2718
2719
2720
2721
	    converted = display_string(fileptr->data, index, COLS, !ISSET(SOFTWRAP));
#ifdef DEBUG
	    if (ISSET(SOFTWRAP) && strlen(converted) >= COLS - 2)
		fprintf(stderr, "update_line(): converted(2) line = %s\n", converted);
#endif
2722
2723
2724

	    /* Paint the line. */
	    edit_draw(fileptr, converted, line, index);
2725
	    free(converted);
2726
2727
2728
	    extralinesused++;
	}
    }
2729
#endif /* !NANO_TINY */
2730
    return extralinesused;
Chris Allegretta's avatar
Chris Allegretta committed
2731
2732
}

2733
2734
2735
2736
/* Return TRUE if we need an update after moving the cursor, and
 * FALSE otherwise.  We need an update if the mark is on, or if
 * pww_save and placewewant are on different pages. */
bool need_screen_update(size_t pww_save)
2737
2738
{
    return
2739
#ifndef NANO_TINY
2740
	openfile->mark_set ||
2741
#endif
2742
	get_page_start(pww_save) != get_page_start(openfile->placewewant);
2743
2744
}

2745
/* When edittop changes, try and figure out how many lines
2746
 * we really have to work with (i.e. set maxrows). */
2747
2748
2749
2750
2751
void compute_maxrows(void)
{
    int n;
    filestruct *foo = openfile->edittop;

2752
2753
2754
2755
2756
    if (!ISSET(SOFTWRAP)) {
	maxrows = editwinrows;
	return;
    }

2757
2758
    maxrows = 0;
    for (n = 0; n < editwinrows && foo; n++) {
2759
	maxrows++;
2760
	n += strlenpt(foo->data) / COLS;
2761
2762
2763
	foo = foo->next;
    }

2764
2765
2766
    if (n < editwinrows)
	maxrows += editwinrows - n;

2767
#ifdef DEBUG
2768
    fprintf(stderr, "compute_maxrows(): maxrows = %d\n", maxrows);
2769
2770
2771
#endif
}

2772
2773
/* Scroll the edit window in the given direction and the given number
 * of lines, and draw new lines on the blank lines left after the
2774
2775
 * scrolling.  direction is the direction to scroll, either UPWARD or
 * DOWNWARD, and nlines is the number of lines to scroll.  We change
2776
2777
 * edittop, and assume that current and current_x are up to date.  We
 * also assume that scrollok(edit) is FALSE. */
2778
void edit_scroll(scroll_dir direction, ssize_t nlines)
2779
{
2780
    ssize_t i;
2781
    filestruct *foo;
2782

2783
    assert(nlines > 0);
2784

2785
2786
2787
    /* Part 1: nlines is the number of lines we're going to scroll the
     * text of the edit window. */

2788
    /* Move the top line of the edit window up or down (depending on the
2789
2790
     * value of direction) nlines lines, or as many lines as we can if
     * there are fewer than nlines lines available. */
2791
    for (i = nlines; i > 0; i--) {
2792
	if (direction == UPWARD) {
2793
	    if (openfile->edittop == openfile->fileage)
2794
		break;
2795
	    openfile->edittop = openfile->edittop->prev;
2796
	} else {
2797
	    if (openfile->edittop == openfile->filebot)
2798
		break;
2799
	    openfile->edittop = openfile->edittop->next;
2800
	}
2801
2802

#ifndef NANO_TINY
2803
	/* Don't over-scroll on long lines. */
2804
	if (ISSET(SOFTWRAP) && direction == UPWARD) {
2805
	    ssize_t len = strlenpt(openfile->edittop->data) / COLS;
2806
	    i -= len;
2807
	    if (len > 0)
2808
		refresh_needed = TRUE;
2809
	}
2810
#endif
2811
2812
    }

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

2816
2817
2818
2819
    /* Don't bother scrolling zero lines, nor more than the window can hold. */
    if (nlines == 0)
	return;
    if (nlines >= editwinrows)
2820
	refresh_needed = TRUE;
2821

2822
    if (refresh_needed == TRUE)
2823
	return;
2824
2825
2826

    /* Scroll the text of the edit window up or down nlines lines,
     * depending on the value of direction. */
2827
    scrollok(edit, TRUE);
2828
    wscrl(edit, (direction == UPWARD) ? -nlines : nlines);
2829
2830
    scrollok(edit, FALSE);

2831
2832
2833
    /* Part 2: nlines is the number of lines in the scrolled region of
     * the edit window that we need to draw. */

2834
2835
    /* If the top or bottom line of the file is now visible in the edit
     * window, we need to draw the entire edit window. */
2836
2837
    if ((direction == UPWARD && openfile->edittop ==
	openfile->fileage) || (direction == DOWNWARD &&
2838
2839
	openfile->edittop->lineno + editwinrows - 1 >=
	openfile->filebot->lineno))
2840
	nlines = editwinrows;
2841

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

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

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

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

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

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

    openfile->placewewant = xplustabs();

2890
2891
    /* If the current line is offscreen, scroll until it's onscreen. */
    if (openfile->current->lineno >= openfile->edittop->lineno + maxrows ||
2892
		openfile->current->lineno < openfile->edittop->lineno) {
2893
	edit_update((focusing || !ISSET(SMOOTH_SCROLL)) ? CENTERING : FLOWING);
2894
	refresh_needed = TRUE;
2895
    }
2896

2897
#ifndef NANO_TINY
2898
2899
2900
    /* If the mark is on, update all lines between old_current and current. */
    if (openfile->mark_set) {
	filestruct *foo = old_current;
2901

2902
	while (foo != openfile->current) {
2903
	    update_line(foo, 0);
2904

2905
2906
2907
	    foo = (foo->lineno > openfile->current->lineno) ?
			foo->prev : foo->next;
	}
2908
2909
2910
2911
2912
2913
    } 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);
2914
2915
2916
2917
2918

    /* Update current if we've changed page, or if it differs from
     * old_current and needs to be horizontally scrolled. */
    if (need_screen_update(was_pww) || (old_current != openfile->current &&
			get_page_start(openfile->placewewant) > 0))
2919
	update_line(openfile->current, openfile->current_x);
2920
2921
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2922
2923
/* 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
2924
2925
void edit_refresh(void)
{
2926
    filestruct *foo;
2927
    int nlines;
2928

2929
    /* Figure out what maxrows should really be. */
2930
    compute_maxrows();
2931

2932
2933
    if (openfile->current->lineno < openfile->edittop->lineno ||
	openfile->current->lineno >= openfile->edittop->lineno +
2934
2935
	maxrows) {
#ifdef DEBUG
2936
2937
	fprintf(stderr, "edit_refresh(): line = %ld, edittop %ld + maxrows %d\n",
		(long)openfile->current->lineno, (long)openfile->edittop->lineno, maxrows);
2938
2939
#endif

2940
	/* Make sure the current line is on the screen. */
2941
	edit_update((focusing || !ISSET(SMOOTH_SCROLL)) ? CENTERING : STATIONARY);
2942
    }
Chris Allegretta's avatar
Chris Allegretta committed
2943

2944
2945
    foo = openfile->edittop;

2946
#ifdef DEBUG
2947
    fprintf(stderr, "edit_refresh(): edittop->lineno = %ld\n", (long)openfile->edittop->lineno);
2948
#endif
2949

2950
    for (nlines = 0; nlines < editwinrows && foo != NULL; nlines++) {
2951
	nlines += update_line(foo, (foo == openfile->current) ?
2952
		openfile->current_x : 0);
2953
2954
2955
	foo = foo->next;
    }

2956
    for (; nlines < editwinrows; nlines++)
2957
2958
2959
	blank_line(edit, nlines, 0, COLS);

    reset_cursor();
2960
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
2961
2962
}

2963
2964
2965
2966
2967
2968
/* 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. */
void edit_update(update_type manner)
Chris Allegretta's avatar
Chris Allegretta committed
2969
{
2970
    int goal = 0;
2971

2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
    /* If manner is CENTERING, move edittop half the number of window
     * lines back from current.  If manner is STATIONARY, move edittop
     * back current_y lines if current_y is in range of the screen,
     * 0 lines if current_y is below zero, or (editwinrows - 1) lines
     * if current_y is too big.  This puts current at the same place
     * on the screen as before, or at the top or bottom if current_y is
     * beyond either.  If manner is FLOWING, move edittop back 0 lines
     * or (editwinrows - 1) lines, depending or where current has moved.
     * This puts the cursor on the first or the last line. */
    if (manner == CENTERING)
2982
	goal = editwinrows / 2;
2983
    else if (manner == FLOWING) {
2984
	if (openfile->current->lineno >= openfile->edittop->lineno)
2985
2986
	    goal = editwinrows - 1;
    } else {
2987
	goal = openfile->current_y;
2988

2989
	/* Limit goal to (editwinrows - 1) lines maximum. */
2990
2991
	if (goal > editwinrows - 1)
	    goal = editwinrows - 1;
Chris Allegretta's avatar
Chris Allegretta committed
2992
    }
2993

2994
2995
2996
2997
2998
    openfile->edittop = openfile->current;

    while (goal > 0 && openfile->edittop->prev != NULL) {
	openfile->edittop = openfile->edittop->prev;
	goal --;
2999
#ifndef NANO_TINY
3000
3001
	if (ISSET(SOFTWRAP))
	    goal -= strlenpt(openfile->edittop->data) / COLS;
3002
#endif
3003
    }
3004
#ifdef DEBUG
3005
    fprintf(stderr, "edit_update(): setting edittop to lineno %ld\n", (long)openfile->edittop->lineno);
3006
#endif
3007
    compute_maxrows();
Chris Allegretta's avatar
Chris Allegretta committed
3008
3009
}

3010
/* Unconditionally redraw the entire screen. */
3011
void total_redraw(void)
3012
{
3013
3014
3015
3016
3017
3018
#ifdef USE_SLANG
    /* Slang curses emulation brain damage, part 4: Slang doesn't define
     * curscr. */
    SLsmg_touch_screen();
    SLsmg_refresh();
#else
3019
    wrefresh(curscr);
3020
#endif
3021
3022
}

3023
3024
/* Unconditionally redraw the entire screen, and then refresh it using
 * the current file. */
3025
3026
void total_refresh(void)
{
3027
    total_redraw();
3028
    titlebar(NULL);
3029
    edit_refresh();
3030
    bottombars(currmenu);
3031
3032
}

3033
3034
/* Display the main shortcut list on the last two rows of the bottom
 * portion of the window. */
3035
3036
void display_main_list(void)
{
3037
#ifndef DISABLE_COLOR
3038
3039
    if (openfile->syntax &&
		(openfile->syntax->formatter || openfile->syntax->linter))
3040
	set_lint_or_format_shortcuts();
3041
3042
3043
3044
    else
	set_spell_shortcuts();
#endif

3045
    bottombars(MMAIN);
3046
3047
}

3048
/* If constant is TRUE, we display the current cursor position only if
3049
3050
 * suppress_cursorpos is FALSE.  If constant is FALSE, we display the
 * position always.  In any case we reset suppress_cursorpos to FALSE. */
3051
void do_cursorpos(bool constant)
Chris Allegretta's avatar
Chris Allegretta committed
3052
{
3053
    filestruct *f;
3054
    char c;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3055
    size_t i, cur_xpt = xplustabs() + 1;
3056
    size_t cur_lenpt = strlenpt(openfile->current->data) + 1;
3057
    int linepct, colpct, charpct;
Chris Allegretta's avatar
Chris Allegretta committed
3058

3059
    assert(openfile->fileage != NULL && openfile->current != NULL);
3060

3061
    /* Determine the size of the file up to the cursor. */
3062
    f = openfile->current->next;
3063
    c = openfile->current->data[openfile->current_x];
3064
3065

    openfile->current->next = NULL;
3066
    openfile->current->data[openfile->current_x] = '\0';
3067
3068
3069

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

3070
    openfile->current->data[openfile->current_x] = c;
3071
    openfile->current->next = f;
3072

3073
    /* If the position needs to be suppressed, don't suppress it next time. */
3074
    if (suppress_cursorpos && constant) {
3075
	suppress_cursorpos = FALSE;
3076
	return;
3077
    }
Chris Allegretta's avatar
Chris Allegretta committed
3078

3079
    /* Display the current cursor position on the statusbar. */
3080
    linepct = 100 * openfile->current->lineno / openfile->filebot->lineno;
3081
    colpct = 100 * cur_xpt / cur_lenpt;
3082
    charpct = (openfile->totsize == 0) ? 0 : 100 * i / openfile->totsize;
3083

3084
    statusline(HUSH,
3085
	_("line %ld/%ld (%d%%), col %lu/%lu (%d%%), char %lu/%lu (%d%%)"),
3086
	(long)openfile->current->lineno,
3087
	(long)openfile->filebot->lineno, linepct,
3088
	(unsigned long)cur_xpt, (unsigned long)cur_lenpt, colpct,
3089
	(unsigned long)i, (unsigned long)openfile->totsize, charpct);
3090
3091
3092

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

3095
/* Unconditionally display the current cursor position. */
3096
void do_cursorpos_void(void)
3097
{
3098
    do_cursorpos(FALSE);
3099
3100
}

3101
3102
void enable_nodelay(void)
{
3103
3104
    nodelay_mode = TRUE;
    nodelay(edit, TRUE);
3105
3106
3107
3108
}

void disable_nodelay(void)
{
3109
3110
    nodelay_mode = FALSE;
    nodelay(edit, FALSE);
3111
3112
}

3113
3114
/* Highlight the current word being replaced or spell checked.  We
 * expect word to have tabs and control characters expanded. */
3115
void spotlight(bool active, const char *word)
Chris Allegretta's avatar
Chris Allegretta committed
3116
{
3117
    size_t word_len = strlenpt(word), room;
Chris Allegretta's avatar
Chris Allegretta committed
3118

3119
    /* Compute the number of columns that are available for the word. */
3120
    room = COLS + get_page_start(xplustabs()) - xplustabs();
Chris Allegretta's avatar
Chris Allegretta committed
3121

3122
    assert(room > 0);
3123

3124
3125
    if (word_len > room)
	room--;
3126

Chris Allegretta's avatar
Chris Allegretta committed
3127
    reset_cursor();
3128
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
3129

3130
    if (active)
3131
	wattron(edit, hilite_attribute);
Chris Allegretta's avatar
Chris Allegretta committed
3132

3133
    /* This is so we can show zero-length matches. */
3134
    if (word_len == 0)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3135
	waddch(edit, ' ');
3136
    else
3137
	waddnstr(edit, word, actual_x(word, room));
3138

3139
    if (word_len > room)
3140
	waddch(edit, '$');
Chris Allegretta's avatar
Chris Allegretta committed
3141

3142
    if (active)
3143
	wattroff(edit, hilite_attribute);
Chris Allegretta's avatar
Chris Allegretta committed
3144
3145
}

3146
#ifndef DISABLE_EXTRA
3147
3148
#define CREDIT_LEN 54
#define XLCREDIT_LEN 9
3149

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

3214
    const char *xlcredits[XLCREDIT_LEN] = {
3215
3216
3217
3218
3219
	N_("The nano text editor"),
	N_("version"),
	N_("Brought to you by:"),
	N_("Special thanks to:"),
	N_("The Free Software Foundation"),
3220
	N_("the many translators and the TP"),
3221
3222
3223
	N_("For ncurses:"),
	N_("and anyone else we forgot..."),
	N_("Thank you for using nano!")
3224
    };
3225

3226
3227
3228
3229
3230
3231
    if (!old_more_space || !old_no_help) {
	SET(MORE_SPACE);
	SET(NO_HELP);
	window_init();
    }

3232
3233
    curs_set(0);
    nodelay(edit, TRUE);
3234

3235
    blank_titlebar();
3236
    blank_topbar();
Chris Allegretta's avatar
Chris Allegretta committed
3237
    blank_edit();
3238
3239
    blank_statusbar();
    blank_bottombars();
3240

3241
    wrefresh(topwin);
Chris Allegretta's avatar
Chris Allegretta committed
3242
    wrefresh(edit);
3243
    wrefresh(bottomwin);
3244
    napms(700);
3245

3246
    for (crpos = 0; crpos < CREDIT_LEN + editwinrows / 2; crpos++) {
3247
	if ((kbinput = wgetch(edit)) != ERR)
3248
	    break;
3249

3250
	if (crpos < CREDIT_LEN) {
3251
	    const char *what;
3252
3253
	    size_t start_x;

3254
	    if (credits[crpos] == NULL) {
3255
		assert(0 <= xlpos && xlpos < XLCREDIT_LEN);
3256

3257
		what = _(xlcredits[xlpos]);
3258
		xlpos++;
3259
	    } else
3260
		what = credits[crpos];
3261

3262
	    start_x = COLS / 2 - strlenpt(what) / 2 - 1;
3263
3264
	    mvwaddstr(edit, editwinrows - 1 - (editwinrows % 2),
		start_x, what);
3265
	}
3266

3267
3268
3269
3270
	wrefresh(edit);

	if ((kbinput = wgetch(edit)) != ERR)
	    break;
3271
	napms(700);
3272

3273
	scrollok(edit, TRUE);
3274
	wscrl(edit, 1);
3275
	scrollok(edit, FALSE);
3276
	wrefresh(edit);
3277

3278
	if ((kbinput = wgetch(edit)) != ERR)
3279
	    break;
3280
	napms(700);
3281

3282
	scrollok(edit, TRUE);
3283
	wscrl(edit, 1);
3284
	scrollok(edit, FALSE);
3285
	wrefresh(edit);
3286
3287
    }

3288
3289
3290
    if (kbinput != ERR)
	ungetch(kbinput);

3291
    if (!old_more_space)
3292
	UNSET(MORE_SPACE);
3293
    if (!old_no_help)
3294
	UNSET(NO_HELP);
3295
    window_init();
3296

3297
    nodelay(edit, FALSE);
3298

3299
    total_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
3300
}
3301
#endif /* !DISABLE_EXTRA */