winio.c 99.1 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
#include "revision.h"
25

26
#if defined(__linux__) && !defined(NANO_TINY)
27
#include <sys/ioctl.h>
28
#endif
29

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

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

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

58
#ifndef NANO_TINY
59
60
61
62
63
64
65
66
67
68
69
70
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;
}
71
#endif
72

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

127
/* Read in a sequence of keystrokes from win and save them in the
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
128
129
 * keystroke buffer.  This should only be called when the keystroke
 * buffer is empty. */
130
void get_key_buffer(WINDOW *win)
131
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
132
    int input;
133
    size_t errcount = 0;
134
135
136
137
138

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

139
140
141
142
    /* Just before reading in the first character, display any pending
     * screen updates. */
    doupdate();

143
    /* Read in the first character using whatever mode we're in. */
144
145
    input = wgetch(win);

146
#ifndef NANO_TINY
147
148
    if (the_window_resized()) {
	ungetch(input);
149
	input = KEY_WINCH;
150
    }
151
152
#endif

153
154
155
156
157
158
159
160
161
162
163
    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);

164
#ifndef NANO_TINY
165
	if (the_window_resized()) {
166
167
	    input = KEY_WINCH;
	    break;
168
	}
169
170
#endif
	input = wgetch(win);
171
    }
172

173
174
    /* Increment the length of the keystroke buffer, and save the value
     * of the keystroke at the end of it. */
175
    key_buffer_len++;
176
177
    key_buffer = (int *)nmalloc(sizeof(int));
    key_buffer[0] = input;
178

179
180
181
182
183
184
185
#ifndef NANO_TINY
    /* If we got SIGWINCH, get out immediately since the win argument is
     * no longer valid. */
    if (input == KEY_WINCH)
	return;
#endif

186
187
188
189
    /* Read in the remaining characters using non-blocking input. */
    nodelay(win, TRUE);

    while (TRUE) {
190
	input = wgetch(win);
191

192
	/* If there aren't any more characters, stop reading. */
193
	if (input == ERR)
194
195
	    break;

196
197
	/* Otherwise, increment the length of the keystroke buffer, and
	 * save the value of the keystroke at the end of it. */
198
	key_buffer_len++;
199
200
201
	key_buffer = (int *)nrealloc(key_buffer, key_buffer_len *
		sizeof(int));
	key_buffer[key_buffer_len - 1] = input;
202
203
    }

204
205
206
    /* Restore waiting mode if it was on. */
    if (!nodelay_mode)
	nodelay(win, FALSE);
207
208

#ifdef DEBUG
209
210
    {
	size_t i;
211
	fprintf(stderr, "\nget_key_buffer(): the sequence of hex codes:");
212
213
214
215
	for (i = 0; i < key_buffer_len; i++)
	    fprintf(stderr, " %3x", key_buffer[i]);
	fprintf(stderr, "\n");
    }
216
#endif
217
}
218

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
219
/* Return the length of the keystroke buffer. */
220
size_t get_key_buffer_len(void)
221
222
223
224
{
    return key_buffer_len;
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
225
/* Add the keystrokes in input to the keystroke buffer. */
226
void unget_input(int *input, size_t input_len)
227
228
{
    /* If input is empty, get out. */
229
    if (input_len == 0)
230
231
	return;

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
232
233
    /* If adding input would put the keystroke buffer beyond maximum
     * capacity, only add enough of input to put it at maximum
234
     * capacity. */
235
236
    if (key_buffer_len + input_len < key_buffer_len)
	input_len = (size_t)-1 - key_buffer_len;
237

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
238
239
240
    /* 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. */
241
242
243
    key_buffer_len += input_len;
    key_buffer = (int *)nrealloc(key_buffer, key_buffer_len *
	sizeof(int));
244

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
245
246
    /* If the keystroke buffer wasn't empty before, move its beginning
     * forward far enough so that we can add input to its beginning. */
247
248
249
    if (key_buffer_len > input_len)
	memmove(key_buffer + input_len, key_buffer,
		(key_buffer_len - input_len) * sizeof(int));
250

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
251
    /* Copy input to the beginning of the keystroke buffer. */
252
    memcpy(key_buffer, input, input_len * sizeof(int));
253
254
}

255
256
257
/* Put the character given in kbinput back into the input stream.  If it
 * is a Meta key, also insert an Escape character in front of it. */
void unget_kbinput(int kbinput, bool metakey)
258
{
259
    unget_input(&kbinput, 1);
260

261
    if (metakey) {
262
	kbinput = ESC_CODE;
263
	unget_input(&kbinput, 1);
264
265
266
    }
}

267
/* Try to read input_len codes from the keystroke buffer.  If the
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
268
 * keystroke buffer is empty and win isn't NULL, try to read in more
269
 * codes from win and add them to the keystroke buffer before doing
270
 * anything else.  If the keystroke buffer is (still) empty, return NULL. */
271
int *get_input(WINDOW *win, size_t input_len)
272
{
273
    int *input;
274

275
276
    if (key_buffer_len == 0 && win != NULL)
	get_key_buffer(win);
277

278
279
    if (key_buffer_len == 0)
	return NULL;
280

281
    /* Limit the request to the number of available codes in the buffer. */
282
283
284
    if (input_len > key_buffer_len)
	input_len = key_buffer_len;

285
    /* Copy input_len codes from the head of the keystroke buffer. */
286
287
    input = (int *)nmalloc(input_len * sizeof(int));
    memcpy(input, key_buffer, input_len * sizeof(int));
288
    key_buffer_len -= input_len;
289

290
    /* If the keystroke buffer is now empty, mark it as such. */
291
292
293
294
    if (key_buffer_len == 0) {
	free(key_buffer);
	key_buffer = NULL;
    } else {
295
	/* Trim from the buffer the codes that were copied. */
296
	memmove(key_buffer, key_buffer + input_len, key_buffer_len *
297
298
299
		sizeof(int));
	key_buffer = (int *)nrealloc(key_buffer, key_buffer_len *
		sizeof(int));
300
301
302
    }

    return input;
303
304
}

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

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

314
315
316
317
318
#ifdef DEBUG
    fprintf(stderr, "after parsing:  kbinput = %d, meta_key = %s\n",
	kbinput, meta_key ? "TRUE" : "FALSE");
#endif

319
    /* If we read from the edit window, blank the statusbar if needed. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
320
    if (win == edit)
321
322
	check_statusblank();

323
324
325
    return kbinput;
}

326
327
/* Extract a single keystroke from the input stream.  Translate escape
 * sequences and extended keypad codes into their corresponding values.
328
 * Set meta_key to TRUE when appropriate.  Supported extended keypad values
329
330
331
 * 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. */
332
int parse_kbinput(WINDOW *win)
333
{
334
    static int escapes = 0, byte_digits = 0;
335
    static bool double_esc = FALSE;
336
    int *kbinput, keycode, retval = ERR;
337

338
    meta_key = FALSE;
339
    shift_held = FALSE;
340

341
    /* Read in a character. */
342
343
344
345
346
347
    kbinput = get_input(win, 1);

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

    while (kbinput == NULL)
348
	kbinput = get_input(win, 1);
349

350
351
352
    keycode = *kbinput;
    free(kbinput);

353
354
355
356
357
#ifdef DEBUG
    fprintf(stderr, "before parsing:  keycode = %d, escapes = %d, byte_digits = %d\n",
	keycode, escapes, byte_digits);
#endif

358
359
360
    if (keycode == ERR)
	return ERR;

361
    if (keycode == ESC_CODE) {
362
363
364
365
366
367
368
	/* Increment the escape counter, but trim an overabundance. */
	escapes++;
	if (escapes > 3)
	    escapes = 1;
	/* Take note when an Esc arrived by itself. */
	solitary = (escapes == 1 && key_buffer_len == 0);
	return ERR;
369
370
    }

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

		    byte_digits++;
		    byte = get_byte_kbinput(keycode);

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

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

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

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

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

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

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

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

499
500
501
502
503
504
505
506
507
#ifndef NANO_TINY
    if (retval == controlleft)
	return sc_seq_or(do_prev_word_void, 0);
    else if (retval == controlright)
	return sc_seq_or(do_next_word_void, 0);
    else if (retval == controlup)
	return sc_seq_or(do_prev_block, 0);
    else if (retval == controldown)
	return sc_seq_or(do_next_block, 0);
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
    else if (retval == shiftcontrolleft) {
	shift_held = TRUE;
	return sc_seq_or(do_prev_word_void, 0);
    } else if (retval == shiftcontrolright) {
	shift_held = TRUE;
	return sc_seq_or(do_next_word_void, 0);
    } else if (retval == shiftcontrolup) {
	shift_held = TRUE;
	return sc_seq_or(do_prev_block, 0);
    } else if (retval == shiftcontroldown) {
	shift_held = TRUE;
	return sc_seq_or(do_next_block, 0);
    } else if (retval == shiftaltleft) {
	shift_held = TRUE;
	return sc_seq_or(do_home, 0);
    } else if (retval == shiftaltright) {
	shift_held = TRUE;
	return sc_seq_or(do_end, 0);
    } else if (retval == shiftaltup) {
	shift_held = TRUE;
	return sc_seq_or(do_page_up, 0);
    } else if (retval == shiftaltdown) {
	shift_held = TRUE;
	return sc_seq_or(do_page_down, 0);
    }
533
534
#endif

535
#if defined(__linux__) && !defined(NANO_TINY)
536
    /* When not running under X, check for the bare arrow keys whether
537
538
539
540
541
542
     * Shift/Ctrl/Alt are being held together with them. */
    unsigned char modifiers = 6;

    if (console && ioctl(0, TIOCLINUX, &modifiers) >= 0) {
	if (modifiers & 0x01)
	    shift_held =TRUE;
543

544
545
	/* Is Ctrl being held? */
	if (modifiers & 0x04) {
546
547
548
549
550
551
	    if (retval == KEY_UP)
		return sc_seq_or(do_prev_block, 0);
	    else if (retval == KEY_DOWN)
		return sc_seq_or(do_next_block, 0);
	    else if (retval == KEY_LEFT)
		return sc_seq_or(do_prev_word_void, 0);
552
	    else if (retval == KEY_RIGHT)
553
554
		return sc_seq_or(do_next_word_void, 0);
	}
555
556
557
558
559
560
561
562
563
564
565
566

	/* Are both Shift and Alt being held? */
	if ((modifiers & 0x09) == 0x09) {
	    if (retval == KEY_UP)
		return sc_seq_or(do_page_up, 0);
	    else if (retval == KEY_DOWN)
		return sc_seq_or(do_page_down, 0);
	    else if (retval == KEY_LEFT)
		return sc_seq_or(do_home, 0);
	    else if (retval == KEY_RIGHT)
		return sc_seq_or(do_end, 0);
	}
567
    }
568
#endif /* __linux__ && !NANO_TINY */
569

570
    switch (retval) {
571
#ifdef KEY_SLEFT
572
573
	/* Slang doesn't support KEY_SLEFT. */
	case KEY_SLEFT:
574
	    shift_held = TRUE;
575
	    return sc_seq_or(do_left, keycode);
576
#endif
577
#ifdef KEY_SRIGHT
578
579
	/* Slang doesn't support KEY_SRIGHT. */
	case KEY_SRIGHT:
580
	    shift_held = TRUE;
581
	    return sc_seq_or(do_right, keycode);
582
#endif
583
#ifdef KEY_SUP
584
585
	/* ncurses and Slang don't support KEY_SUP. */
	case KEY_SUP:
586
#endif
587
588
589
	case KEY_SR:	/* Scroll backward, on Xfce4-terminal. */
	    shift_held = TRUE;
	    return sc_seq_or(do_up_void, keycode);
590
#ifdef KEY_SDOWN
591
592
	/* ncurses and Slang don't support KEY_SDOWN. */
	case KEY_SDOWN:
593
#endif
594
595
596
	case KEY_SF:	/* Scroll forward, on Xfce4-terminal. */
	    shift_held = TRUE;
	    return sc_seq_or(do_down_void, keycode);
597
#ifdef KEY_SHOME
598
599
	/* HP-UX 10-11 and Slang don't support KEY_SHOME. */
	case KEY_SHOME:
600
	    shift_held = TRUE;
601
#endif
602
603
	case KEY_A1:	/* Home (7) on keypad with NumLock off. */
	    return sc_seq_or(do_home, keycode);
604
#ifdef KEY_SEND
605
606
	/* HP-UX 10-11 and Slang don't support KEY_SEND. */
	case KEY_SEND:
607
	    shift_held = TRUE;
608
#endif
609
610
	case KEY_C1:	/* End (1) on keypad with NumLock off. */
	    return sc_seq_or(do_end, keycode);
611
612
613
614
#ifndef NANO_TINY
	case SHIFT_PAGEUP:		/* Fake key, from Shift+Alt+Up. */
	    shift_held = TRUE;
#endif
615
616
	case KEY_A3:	/* PageUp (9) on keypad with NumLock off. */
	    return sc_seq_or(do_page_up, keycode);
617
618
619
620
#ifndef NANO_TINY
	case SHIFT_PAGEDOWN:	/* Fake key, from Shift+Alt+Down. */
	    shift_held = TRUE;
#endif
621
622
	case KEY_C3:	/* PageDown (3) on keypad with NumLock off. */
	    return sc_seq_or(do_page_down, keycode);
623
#ifdef KEY_SDC
624
625
	/* Slang doesn't support KEY_SDC. */
	case KEY_SDC:
626
#endif
627
	case DEL_CODE:
628
629
630
631
	    if (ISSET(REBIND_DELETE))
		return sc_seq_or(do_delete, keycode);
	    else
		return sc_seq_or(do_backspace, keycode);
632
#ifdef KEY_SIC
633
634
635
	/* Slang doesn't support KEY_SIC. */
	case KEY_SIC:
	    return sc_seq_or(do_insertfile_void, keycode);
636
#endif
637
#ifdef KEY_SBEG
638
639
	/* Slang doesn't support KEY_SBEG. */
	case KEY_SBEG:
640
#endif
641
#ifdef KEY_BEG
642
643
	/* Slang doesn't support KEY_BEG. */
	case KEY_BEG:
644
#endif
645
646
	case KEY_B2:	/* Center (5) on keypad with NumLock off. */
	    return ERR;
647
#ifdef KEY_CANCEL
648
#ifdef KEY_SCANCEL
649
650
	/* Slang doesn't support KEY_SCANCEL. */
	case KEY_SCANCEL:
651
#endif
652
653
654
	/* Slang doesn't support KEY_CANCEL. */
	case KEY_CANCEL:
	    return first_sc_for(currmenu, do_cancel)->keycode;
655
#endif
656
#ifdef KEY_SUSPEND
657
#ifdef KEY_SSUSPEND
658
659
	/* Slang doesn't support KEY_SSUSPEND. */
	case KEY_SSUSPEND:
660
#endif
661
662
663
	/* Slang doesn't support KEY_SUSPEND. */
	case KEY_SUSPEND:
	    return sc_seq_or(do_suspend_void, 0);
664
665
#endif
#ifdef PDCURSES
666
667
668
669
670
671
672
	case KEY_SHIFT_L:
	case KEY_SHIFT_R:
	case KEY_CONTROL_L:
	case KEY_CONTROL_R:
	case KEY_ALT_L:
	case KEY_ALT_R:
	    return ERR;
673
674
#endif
#if !defined(NANO_TINY) && defined(KEY_RESIZE)
675
676
677
678
679
	/* 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:
	    return ERR;
680
#endif
681
    }
682

683
684
685
    return retval;
}

686
/* Translate escape sequences, most of which correspond to extended
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
687
 * keypad values, into their corresponding key values.  These sequences
688
689
 * are generated when the keypad doesn't support the needed keys.
 * Assume that Escape has already been read in. */
690
int convert_sequence(const int *seq, size_t seq_len)
691
{
692
    if (seq_len > 1) {
693
	switch (seq[0]) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
694
	    case 'O':
695
		switch (seq[1]) {
696
		    case '1':
697
698
			if (seq_len > 4  && seq[2] == ';') {

699
700
	switch (seq[3]) {
	    case '2':
701
702
703
704
705
		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. */
706
			shift_held = TRUE;
707
708
709
710
711
712
713
714
715
716
			return arrow_from_abcd(seq[4]);
		    case 'P': /* Esc O 1 ; 2 P == F13 on Terminal. */
			return KEY_F(13);
		    case 'Q': /* Esc O 1 ; 2 Q == F14 on Terminal. */
			return KEY_F(14);
		    case 'R': /* Esc O 1 ; 2 R == F15 on Terminal. */
			return KEY_F(15);
		    case 'S': /* Esc O 1 ; 2 S == F16 on Terminal. */
			return KEY_F(16);
		}
717
718
		break;
	    case '5':
719
720
721
722
723
724
725
726
727
728
		switch (seq[4]) {
		    case 'A': /* Esc O 1 ; 5 A == Ctrl-Up on Terminal. */
			return CONTROL_UP;
		    case 'B': /* Esc O 1 ; 5 B == Ctrl-Down on Terminal. */
			return CONTROL_DOWN;
		    case 'C': /* Esc O 1 ; 5 C == Ctrl-Right on Terminal. */
			return CONTROL_RIGHT;
		    case 'D': /* Esc O 1 ; 5 D == Ctrl-Left on Terminal. */
			return CONTROL_LEFT;
		}
729
730
		break;
	}
731

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

905
	switch (seq[3]) {
906
	    case '2':
907
908
909
910
911
		switch (seq[4]) {
		    case 'A': /* Esc [ 1 ; 2 A == Shift-Up on xterm. */
		    case 'B': /* Esc [ 1 ; 2 B == Shift-Down on xterm. */
		    case 'C': /* Esc [ 1 ; 2 C == Shift-Right on xterm. */
		    case 'D': /* Esc [ 1 ; 2 D == Shift-Left on xterm. */
912
			shift_held = TRUE;
913
914
			return arrow_from_abcd(seq[4]);
		}
915
		break;
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
#ifndef NANO_TINY
	    case '4':
		/* When the arrow keys are held together with Shift+Meta,
		 * act as if they are Home/End/PgUp/PgDown with Shift. */
		switch (seq[4]) {
		    case 'A': /* Esc [ 1 ; 4 A == Shift-Alt-Up on xterm. */
			return SHIFT_PAGEUP;
		    case 'B': /* Esc [ 1 ; 4 B == Shift-Alt-Down on xterm. */
			return SHIFT_PAGEDOWN;
		    case 'C': /* Esc [ 1 ; 4 C == Shift-Alt-Right on xterm. */
			return KEY_SEND;
		    case 'D': /* Esc [ 1 ; 4 D == Shift-Alt-Left on xterm. */
			return KEY_SHOME;
		}
		break;
#endif
932
	    case '5':
933
934
935
936
937
938
939
940
941
942
		switch (seq[4]) {
		    case 'A': /* Esc [ 1 ; 5 A == Ctrl-Up on xterm. */
			return CONTROL_UP;
		    case 'B': /* Esc [ 1 ; 5 B == Ctrl-Down on xterm. */
			return CONTROL_DOWN;
		    case 'C': /* Esc [ 1 ; 5 C == Ctrl-Right on xterm. */
			return CONTROL_RIGHT;
		    case 'D': /* Esc [ 1 ; 5 D == Ctrl-Left on xterm. */
			return CONTROL_LEFT;
		}
943
		break;
944
945
946
947
948
949
950
951
952
953
954
955
956
957
#ifndef NANO_TINY
	    case '6':
		switch (seq[4]) {
		    case 'A': /* Esc [ 1 ; 6 A == Shift-Ctrl-Up on xterm. */
			return shiftcontrolup;
		    case 'B': /* Esc [ 1 ; 6 B == Shift-Ctrl-Down on xterm. */
			return shiftcontroldown;
		    case 'C': /* Esc [ 1 ; 6 C == Shift-Ctrl-Right on xterm. */
			return shiftcontrolright;
		    case 'D': /* Esc [ 1 ; 6 D == Shift-Ctrl-Left on xterm. */
			return shiftcontrolleft;
		}
		break;
#endif
958
	}
959
960
961
962

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

1121
    return ERR;
1122
1123
}

1124
1125
/* Return the equivalent arrow-key value for the first four letters
 * in the alphabet, common to many escape sequences. */
1126
int arrow_from_abcd(int kbinput)
1127
1128
1129
{
    switch (tolower(kbinput)) {
	case 'a':
1130
	    return KEY_UP;
1131
	case 'b':
1132
	    return KEY_DOWN;
1133
	case 'c':
1134
	    return KEY_RIGHT;
1135
	case 'd':
1136
	    return KEY_LEFT;
1137
1138
1139
1140
1141
	default:
	    return ERR;
    }
}

1142
/* Interpret the escape sequence in the keystroke buffer, the first
1143
1144
 * character of which is kbinput.  Assume that the keystroke buffer
 * isn't empty, and that the initial escape has already been read in. */
1145
int parse_escape_sequence(WINDOW *win, int kbinput)
1146
1147
1148
1149
1150
1151
1152
1153
{
    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);
1154
    seq_len = key_buffer_len;
1155
    seq = get_input(NULL, seq_len);
1156
    retval = convert_sequence(seq, seq_len);
1157
1158
1159

    free(seq);

1160
    /* If we got an unrecognized escape sequence, notify the user. */
1161
    if (retval == ERR) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1162
	if (win == edit) {
1163
1164
	    /* TRANSLATORS: This refers to a sequence of escape codes
	     * (from the keyboard) that nano does not know about. */
1165
	    statusline(ALERT, _("Unknown sequence"));
1166
	    suppress_cursorpos = FALSE;
1167
	    lastmessage = HUSH;
1168
1169
1170
1171
	    if (currmenu == MMAIN) {
		reset_cursor();
		curs_set(1);
	    }
1172
1173
1174
	}
    }

1175
#ifdef DEBUG
1176
1177
    fprintf(stderr, "parse_escape_sequence(): kbinput = %d, seq_len = %lu, retval = %d\n",
		kbinput, (unsigned long)seq_len, retval);
1178
1179
1180
1181
1182
#endif

    return retval;
}

1183
1184
/* Translate a byte sequence: turn a three-digit decimal number (from
 * 000 to 255) into its corresponding byte value. */
1185
int get_byte_kbinput(int kbinput)
1186
{
1187
    static int byte_digits = 0, byte = 0;
1188
    int retval = ERR;
1189

1190
1191
    /* Increment the byte digit counter. */
    byte_digits++;
1192

1193
    switch (byte_digits) {
1194
	case 1:
1195
1196
	    /* First digit: This must be from zero to two.  Put it in
	     * the 100's position of the byte sequence holder. */
1197
	    if ('0' <= kbinput && kbinput <= '2')
1198
		byte = (kbinput - '0') * 100;
1199
	    else
1200
1201
		/* This isn't the start of a byte sequence.  Return this
		 * character as the result. */
1202
1203
1204
		retval = kbinput;
	    break;
	case 2:
1205
1206
1207
1208
	    /* 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. */
1209
1210
1211
	    if (('0' <= kbinput && kbinput <= '5') || (byte < 200 &&
		'6' <= kbinput && kbinput <= '9'))
		byte += (kbinput - '0') * 10;
1212
	    else
1213
1214
		/* This isn't the second digit of a byte sequence.
		 * Return this character as the result. */
1215
1216
1217
		retval = kbinput;
	    break;
	case 3:
1218
	    /* Third digit: This must be from zero to five if the first
1219
1220
1221
	     * 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. */
1222
1223
	    if (('0' <= kbinput && kbinput <= '5') || (byte < 250 &&
		'6' <= kbinput && kbinput <= '9')) {
1224
		byte += kbinput - '0';
1225
		/* The byte sequence is complete. */
1226
		retval = byte;
1227
	    } else
1228
1229
		/* This isn't the third digit of a byte sequence.
		 * Return this character as the result. */
1230
1231
		retval = kbinput;
	    break;
1232
	default:
1233
1234
1235
	    /* If there are more than three digits, return this
	     * character as the result.  (Maybe we should produce an
	     * error instead?) */
1236
1237
1238
1239
1240
1241
1242
1243
	    retval = kbinput;
	    break;
    }

    /* If we have a result, reset the byte digit counter and the byte
     * sequence holder. */
    if (retval != ERR) {
	byte_digits = 0;
1244
	byte = 0;
1245
1246
1247
    }

#ifdef DEBUG
1248
    fprintf(stderr, "get_byte_kbinput(): kbinput = %d, byte_digits = %d, byte = %d, retval = %d\n", kbinput, byte_digits, byte, retval);
1249
1250
1251
1252
1253
#endif

    return retval;
}

1254
#ifdef ENABLE_UTF8
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1255
/* If the character in kbinput is a valid hexadecimal digit, multiply it
1256
 * by factor and add the result to uni, and return ERR to signify okay. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1257
1258
1259
1260
1261
1262
1263
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
1264
1265
	/* The character isn't hexadecimal; give it as the result. */
	return (long)kbinput;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1266

1267
    return ERR;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1268
1269
}

1270
/* Translate a Unicode sequence: turn a six-digit hexadecimal number
1271
 * (from 000000 to 10FFFF, case-insensitive) into its corresponding
1272
 * multibyte value. */
1273
long get_unicode_kbinput(WINDOW *win, int kbinput)
1274
{
1275
1276
1277
    static int uni_digits = 0;
    static long uni = 0;
    long retval = ERR;
1278

1279
    /* Increment the Unicode digit counter. */
1280
    uni_digits++;
1281

1282
    switch (uni_digits) {
1283
	case 1:
1284
1285
1286
1287
	    /* The first digit must be zero or one.  Put it in the
	     * 0x100000's position of the Unicode sequence holder.
	     * Otherwise, return the character itself as the result. */
	    if (kbinput == '0' || kbinput == '1')
1288
		uni = (kbinput - '0') * 0x100000;
1289
1290
1291
1292
	    else
		retval = kbinput;
	    break;
	case 2:
1293
1294
1295
	    /* The second digit must be zero if the first was one, but
	     * may be any hexadecimal value if the first was zero. */
	    if (kbinput == '0' || uni == 0)
1296
		retval = add_unicode_digit(kbinput, 0x10000, &uni);
1297
1298
1299
1300
	    else
		retval = kbinput;
	    break;
	case 3:
1301
	    /* Later digits may be any hexadecimal value. */
1302
	    retval = add_unicode_digit(kbinput, 0x1000, &uni);
1303
	    break;
1304
	case 4:
1305
	    retval = add_unicode_digit(kbinput, 0x100, &uni);
1306
	    break;
1307
	case 5:
1308
	    retval = add_unicode_digit(kbinput, 0x10, &uni);
1309
	    break;
1310
	case 6:
1311
	    retval = add_unicode_digit(kbinput, 0x1, &uni);
1312
1313
	    /* If also the sixth digit was a valid hexadecimal value, then
	     * the Unicode sequence is complete, so return it. */
1314
	    if (retval == ERR)
1315
		retval = uni;
1316
1317
	    break;
    }
1318

1319
    /* Show feedback only when editing, not when at a prompt. */
1320
    if (retval == ERR && win == edit) {
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
	char partial[7] = "......";

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

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

1332
#ifdef DEBUG
1333
1334
    fprintf(stderr, "get_unicode_kbinput(): kbinput = %d, uni_digits = %d, uni = %ld, retval = %ld\n",
						kbinput, uni_digits, uni, retval);
1335
1336
#endif

1337
1338
1339
1340
    /* If we have an end result, reset the Unicode digit counter. */
    if (retval != ERR)
	uni_digits = 0;

1341
1342
    return retval;
}
1343
#endif /* ENABLE_UTF8 */
1344

1345
1346
1347
1348
1349
1350
/* Translate a control character sequence: turn an ASCII non-control
 * character into its corresponding control character. */
int get_control_kbinput(int kbinput)
{
    int retval;

1351
    /* Ctrl-Space (Ctrl-2, Ctrl-@, Ctrl-`) */
1352
    if (kbinput == ' ' || kbinput == '2')
1353
	retval = 0;
1354
1355
    /* Ctrl-/ (Ctrl-7, Ctrl-_) */
    else if (kbinput == '/')
1356
	retval = 31;
1357
    /* Ctrl-3 (Ctrl-[, Esc) to Ctrl-7 (Ctrl-/, Ctrl-_) */
1358
1359
1360
    else if ('3' <= kbinput && kbinput <= '7')
	retval = kbinput - 24;
    /* Ctrl-8 (Ctrl-?) */
1361
    else if (kbinput == '8' || kbinput == '?')
1362
	retval = DEL_CODE;
1363
1364
    /* Ctrl-@ (Ctrl-Space, Ctrl-2, Ctrl-`) to Ctrl-_ (Ctrl-/, Ctrl-7) */
    else if ('@' <= kbinput && kbinput <= '_')
1365
	retval = kbinput - '@';
1366
1367
    /* Ctrl-` (Ctrl-2, Ctrl-Space, Ctrl-@) to Ctrl-~ (Ctrl-6, Ctrl-^) */
    else if ('`' <= kbinput && kbinput <= '~')
1368
	retval = kbinput - '`';
1369
1370
1371
    else
	retval = kbinput;

1372
#ifdef DEBUG
1373
    fprintf(stderr, "get_control_kbinput(): kbinput = %d, retval = %d\n", kbinput, retval);
1374
1375
#endif

1376
1377
    return retval;
}
1378

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1379
1380
/* Put the output-formatted characters in output back into the keystroke
 * buffer, so that they can be parsed and displayed as output again. */
1381
void unparse_kbinput(char *output, size_t output_len)
1382
{
1383
1384
    int *input;
    size_t i;
1385

1386
1387
1388
1389
    if (output_len == 0)
	return;

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

1391
1392
    for (i = 0; i < output_len; i++)
	input[i] = (int)output[i];
1393

1394
    unget_input(input, output_len);
1395

1396
    free(input);
1397
1398
}

1399
/* Read in a stream of characters verbatim, and return the length of the
1400
1401
1402
1403
 * string in kbinput_len.  Assume nodelay(win) is FALSE. */
int *get_verbatim_kbinput(WINDOW *win, size_t *kbinput_len)
{
    int *retval;
1404

1405
    /* Turn off flow control characters if necessary so that we can type
1406
1407
     * them in verbatim, and turn the keypad off if necessary so that we
     * don't get extended keypad values. */
1408
1409
    if (ISSET(PRESERVE))
	disable_flow_control();
1410
1411
    if (!ISSET(REBIND_KEYPAD))
	keypad(win, FALSE);
1412
1413
1414

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

    /* Turn flow control characters back on if necessary and turn the
1417
     * keypad back on if necessary now that we're done. */
1418
1419
    if (ISSET(PRESERVE))
	enable_flow_control();
1420
1421
1422
1423
1424
1425
    /* Use the global window pointers, because a resize may have freed
     * the data that the win parameter points to. */
    if (!ISSET(REBIND_KEYPAD)) {
	keypad(edit, TRUE);
	keypad(bottomwin, TRUE);
    }
1426

1427
    return retval;
1428
1429
}

1430
1431
1432
1433
1434
/* Read in one control character (or an iTerm double Escape), or convert a
 * series of six digits into a Unicode codepoint.  Return in count either 1
 * (for a control character or the first byte of a multibyte sequence), or 2
 * (for an iTerm double Escape). */
int *parse_verbatim_kbinput(WINDOW *win, size_t *count)
1435
{
1436
    int *kbinput;
1437

1438
    /* Read in the first code. */
1439
1440
    while ((kbinput = get_input(win, 1)) == NULL)
	;
1441

1442
#ifndef NANO_TINY
1443
1444
1445
    /* When the window was resized, abort and return nothing. */
    if (*kbinput == KEY_WINCH) {
	free(kbinput);
1446
	*count = 0;
1447
1448
	return NULL;
    }
1449
#endif
1450

1451
#ifdef ENABLE_UTF8
1452
    if (using_utf8()) {
1453
	/* Check whether the first code is a valid starter digit: 0 or 1. */
1454
	long uni = get_unicode_kbinput(win, *kbinput);
1455

1456
	/* If the first code isn't the digit 0 nor 1, put it back. */
1457
1458
	if (uni != ERR)
	    unget_input(kbinput, 1);
1459
1460
	/* Otherwise, continue reading in digits until we have a complete
	 * Unicode value, and put back the corresponding byte(s). */
1461
1462
1463
1464
1465
	else {
	    char *uni_mb;
	    int uni_mb_len, *seq, i;

	    while (uni == ERR) {
1466
		free(kbinput);
1467
1468
		while ((kbinput = get_input(win, 1)) == NULL)
		    ;
1469
		uni = get_unicode_kbinput(win, *kbinput);
1470
	    }
1471

1472
	    /* Convert the Unicode value to a multibyte sequence. */
1473
	    uni_mb = make_mbchar(uni, &uni_mb_len);
1474

1475
	    seq = (int *)nmalloc(uni_mb_len * sizeof(int));
1476

1477
1478
	    for (i = 0; i < uni_mb_len; i++)
		seq[i] = (unsigned char)uni_mb[i];
1479

1480
	    /* Insert the multibyte sequence into the input buffer. */
1481
	    unget_input(seq, uni_mb_len);
1482

1483
1484
	    free(seq);
	    free(uni_mb);
1485
	}
1486
    } else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1487
#endif /* ENABLE_UTF8 */
1488
	/* Put back the first code. */
1489
	unget_input(kbinput, 1);
1490

1491
1492
    free(kbinput);

1493
    *count = 1;
1494

1495
1496
1497
1498
1499
1500
    /* If this is an iTerm double escape, take both Escapes. */
    if (key_buffer_len > 3 && *key_buffer == ESC_CODE &&
		key_buffer[1] == ESC_CODE && key_buffer[2] == '[')
	*count = 2;

    return get_input(NULL, *count);
1501
1502
}

1503
#ifndef DISABLE_MOUSE
1504
/* Handle any mouse event that may have occurred.  We currently handle
1505
1506
1507
1508
1509
1510
1511
 * 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
1512
1513
1514
1515
1516
1517
 * 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. */
1518
int get_mouseinput(int *mouse_x, int *mouse_y, bool allow_shortcuts)
1519
1520
{
    MEVENT mevent;
1521
    bool in_bottomwin;
1522
    subnfunc *f;
1523
1524
1525
1526
1527
1528

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

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

1531
1532
1533
    /* Save the screen coordinates where the mouse event took place. */
    *mouse_x = mevent.x;
    *mouse_y = mevent.y;
1534

1535
1536
    in_bottomwin = wenclose(bottomwin, *mouse_y, *mouse_x);

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1537
    /* Handle releases/clicks of the first mouse button. */
1538
    if (mevent.bstate & (BUTTON1_RELEASED | BUTTON1_CLICKED)) {
1539
1540
	/* If we're allowing shortcuts, the current shortcut list is
	 * being displayed on the last two lines of the screen, and the
1541
1542
1543
	 * 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. */
1544
	if (allow_shortcuts && !ISSET(NO_HELP) && in_bottomwin) {
1545
1546
1547
1548
	    int i;
		/* The width of all the shortcuts, except for the last
		 * two, in the shortcut list in bottomwin. */
	    int j;
1549
		/* The calculated index number of the clicked item. */
1550
1551
	    size_t number;
		/* The number of available shortcuts in the current menu. */
1552

1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
	    /* 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;
	    }
1567

1568
	    /* Determine how many shortcuts are being shown. */
1569
	    number = length_of_list(currmenu);
1570

1571
1572
	    if (number > MAIN_VISIBLE)
		number = MAIN_VISIBLE;
1573

1574
1575
1576
	    /* 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. */
1577
	    if (number < 2)
1578
1579
		i = COLS / (MAIN_VISIBLE / 2);
	    else
1580
		i = COLS / ((number / 2) + (number % 2));
1581

1582
1583
	    /* Calculate the one-based index in the shortcut list. */
	    j = (*mouse_x / i) * 2 + *mouse_y;
1584

1585
	    /* Adjust the index if we hit the last two wider ones. */
1586
	    if ((j > number) && (*mouse_x % i < COLS % i))
1587
		j -= 2;
1588
1589
1590
#ifdef DEBUG
	    fprintf(stderr, "Calculated %i as index in shortcut list, currmenu = %x.\n", j, currmenu);
#endif
1591
1592
	    /* Ignore releases/clicks of the first mouse button beyond
	     * the last shortcut. */
1593
	    if (j > number)
1594
		return 2;
1595

1596
1597
	    /* Go through the list of functions to determine which
	     * shortcut in the current menu we released/clicked on. */
1598
	    for (f = allfuncs; f != NULL; f = f->next) {
1599
		if ((f->menus & currmenu) == 0)
1600
1601
		    continue;
		if (first_sc_for(currmenu, f->scfunc) == NULL)
1602
		    continue;
1603
1604
		/* Tick off an actually shown shortcut. */
		j -= 1;
1605
1606
		if (j == 0)
		    break;
1607
	    }
1608
#ifdef DEBUG
1609
	    fprintf(stderr, "Stopped on func %ld present in menus %x\n", (long)f->scfunc, f->menus);
1610
#endif
1611

1612
	    /* And put the corresponding key into the keyboard buffer. */
1613
	    if (f != NULL) {
1614
		const sc *s = first_sc_for(currmenu, f->scfunc);
1615
		unget_kbinput(s->keycode, s->meta);
1616
	    }
1617
	    return 1;
1618
	} else
1619
1620
	    /* Handle releases/clicks of the first mouse button that
	     * aren't on the current shortcut list elsewhere. */
1621
	    return 0;
1622
    }
1623
1624
#if NCURSES_MOUSE_VERSION >= 2
    /* Handle presses of the fourth mouse button (upward rolls of the
1625
1626
1627
     * 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
1628
	bool in_edit = wenclose(edit, *mouse_y, *mouse_x);
1629

1630
1631
1632
1633
	if (in_bottomwin)
	    /* Translate the mouse event coordinates so that they're
	     * relative to bottomwin. */
	    wmouse_trafo(bottomwin, mouse_y, mouse_x, FALSE);
1634

1635
	if (in_edit || (in_bottomwin && *mouse_y == 0)) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1636
1637
	    int i;

1638
1639
1640
1641
1642
	    /* 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) ?
1643
				KEY_PPAGE : KEY_NPAGE, FALSE);
1644
1645
1646
1647
1648
1649
1650

	    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;
1651
1652
    }
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1653
1654
1655

    /* Ignore all other mouse events. */
    return 2;
1656
}
1657
1658
#endif /* !DISABLE_MOUSE */

1659
1660
1661
1662
/* 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. */
1663
const sc *get_shortcut(int *kbinput)
1664
{
1665
    sc *s;
1666

1667
#ifdef DEBUG
1668
1669
    fprintf(stderr, "get_shortcut(): kbinput = %d, meta_key = %s -- ",
				*kbinput, meta_key ? "TRUE" : "FALSE");
1670
1671
#endif

1672
    for (s = sclist; s != NULL; s = s->next) {
1673
	if ((s->menus & currmenu) && *kbinput == s->keycode &&
1674
					meta_key == s->meta) {
1675
#ifdef DEBUG
1676
1677
	    fprintf (stderr, "matched seq '%s'  (menu is %x from %x)\n",
				s->keystr, currmenu, s->menus);
1678
#endif
1679
	    return s;
1680
1681
	}
    }
1682
#ifdef DEBUG
1683
    fprintf (stderr, "matched nothing\n");
1684
#endif
1685
1686
1687
1688

    return NULL;
}

1689
1690
1691
1692
1693
/* 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
1694

1695
1696
1697
1698
    for (; n > 0; n--)
	waddch(win, ' ');
}

1699
/* Blank the first line of the top portion of the window. */
1700
void blank_titlebar(void)
Chris Allegretta's avatar
Chris Allegretta committed
1701
{
1702
    blank_line(topwin, 0, 0, COLS);
1703
1704
}

1705
/* Blank all the lines of the middle portion of the window, i.e. the
1706
 * edit window. */
Chris Allegretta's avatar
Chris Allegretta committed
1707
1708
void blank_edit(void)
{
1709
    int i;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1710

1711
    for (i = 0; i < editwinrows; i++)
1712
	blank_line(edit, i, 0, COLS);
Chris Allegretta's avatar
Chris Allegretta committed
1713
1714
}

1715
/* Blank the first line of the bottom portion of the window. */
Chris Allegretta's avatar
Chris Allegretta committed
1716
1717
void blank_statusbar(void)
{
1718
    blank_line(bottomwin, 0, 0, COLS);
Chris Allegretta's avatar
Chris Allegretta committed
1719
1720
}

1721
1722
/* If the NO_HELP flag isn't set, blank the last two lines of the bottom
 * portion of the window. */
1723
1724
void blank_bottombars(void)
{
1725
    if (!ISSET(NO_HELP) && LINES > 4) {
1726
1727
	blank_line(bottomwin, 1, 0, COLS);
	blank_line(bottomwin, 2, 0, COLS);
1728
1729
1730
    }
}

1731
1732
/* Check if the number of keystrokes needed to blank the statusbar has
 * been pressed.  If so, blank the statusbar, unless constant cursor
1733
 * position display is on and we are in the editing screen. */
1734
void check_statusblank(void)
Chris Allegretta's avatar
Chris Allegretta committed
1735
{
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
    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
1750
    }
1751
1752
1753
1754

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

1757
1758
/* Convert buf into a string that can be displayed on screen.  The
 * caller wants to display buf starting with column start_col, and
1759
1760
 * extending for at most span columns.  start_col is zero-based.  span
 * is one-based, so span == 0 means you get "" returned.  The returned
1761
1762
1763
 * 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. */
1764
1765
char *display_string(const char *buf, size_t start_col, size_t span,
	bool dollars)
1766
1767
{
    size_t start_index;
1768
	/* Index in buf of the first character shown. */
1769
    size_t column;
1770
	/* Screen column that start_index corresponds to. */
1771
1772
1773
1774
    char *converted;
	/* The string we return. */
    size_t index;
	/* Current position in converted. */
1775

1776
1777
    /* If dollars is TRUE, make room for the "$" at the end of the
     * line. */
1778
1779
    if (dollars && span > 0 && strlenpt(buf) > start_col + span)
	span--;
1780

1781
    if (span == 0)
1782
1783
1784
1785
	return mallocstrcpy(NULL, "");

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

1787
    assert(column <= start_col);
1788

1789
1790
    /* Allocate enough space to hold the entire converted buffer. */
    converted = charalloc(strlen(buf) * (mb_cur_max() + tabsize) + 1);
1791

1792
    index = 0;
1793
#ifdef USING_OLD_NCURSES
1794
    seen_wide = FALSE;
1795
#endif
1796
    buf += start_index;
1797

1798
    if (*buf != '\0' && *buf != '\t' &&
1799
	(column < start_col || (dollars && column > 0))) {
1800
	/* We don't display the complete first character as it starts to
1801
	 * the left of the screen. */
1802
	if (is_cntrl_mbchar(buf)) {
1803
	    if (column < start_col) {
1804
		converted[index++] = control_mbrep(buf);
1805
		start_col++;
1806
		buf += parse_mbchar(buf, NULL, NULL);
1807
	    }
1808
	}
1809
#ifdef ENABLE_UTF8
1810
	else if (using_utf8() && mbwidth(buf) == 2) {
1811
1812
1813
1814
1815
	    if (column >= start_col) {
		converted[index++] = ' ';
		start_col++;
	    }

1816
	    converted[index++] = ' ';
1817
	    start_col++;
1818

1819
	    buf += parse_mbchar(buf, NULL, NULL);
1820
	}
1821
#endif
1822
1823
    }

1824
    while (*buf != '\0') {
1825
	int charlength, charwidth = 1;
1826

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

1847
1848
		while (i < whitespace_len[0])
		    converted[index++] = whitespace[i++];
1849
	    } else
1850
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1851
		converted[index++] = ' ';
1852
	    start_col++;
1853
	    /* Fill the tab up with the required number of spaces. */
1854
	    while (start_col % tabsize != 0) {
1855
		converted[index++] = ' ';
1856
1857
		start_col++;
	    }
1858
1859
1860
1861
	    buf++;
	    continue;
	}

1862
	charlength = length_of_char(buf, &charwidth);
1863

1864
	/* If buf contains a control character, represent it. */
1865
	if (is_cntrl_mbchar(buf)) {
1866
	    converted[index++] = '^';
1867
	    converted[index++] = control_mbrep(buf);
1868
	    start_col += 2;
1869
1870
1871
	    buf += charlength;
	    continue;
	}
1872

1873
1874
1875
1876
	/* If buf contains a valid non-control character, simply copy it. */
	if (charlength > 0) {
	    for (; charlength > 0; charlength--)
		converted[index++] = *(buf++);
1877

1878
	    start_col += charwidth;
1879
#ifdef USING_OLD_NCURSES
1880
	    if (charwidth > 1)
1881
		seen_wide = TRUE;
1882
#endif
1883
	    continue;
1884
1885
	}

1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
	/* Represent an invalid sequence with the Replacement Character. */
	converted[index++] = '\xEF';
	converted[index++] = '\xBF';
	converted[index++] = '\xBD';

	start_col += 1;
	buf++;

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

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 span columns. */
    index = actual_x(converted, span);
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
1931
1932
1933
    /* If the screen is too small, there is no titlebar. */
    if (topwin == NULL)
	return;

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

1936
    wattron(topwin, interface_color_pair[TITLE_BAR]);
1937

1938
    blank_titlebar();
Chris Allegretta's avatar
Chris Allegretta committed
1939

1940
1941
1942
    /* 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
1943

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

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

1963
1964
	pluglen = strlenpt(_("Modified")) + 1;
    }
1965

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

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

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

1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
    /* 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);
2015
    }
2016

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

2023
    wattroff(topwin, interface_color_pair[TITLE_BAR]);
2024

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

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

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

    UNSET(WHITESPACE_DISPLAY);
2049
#endif
2050
2051
2052
2053
2054

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

2061
2062
2063
2064
2065
2066
2067
    /* 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)
2068
2069
	napms(1200);

2070
    if (importance == ALERT)
2071
	beep();
2072
2073

    lastmessage = importance;
2074

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

2078
2079
    blank_statusbar();

2080
2081
2082
    /* Construct the message out of all the arguments. */
    compound = charalloc(mb_cur_max() * (COLS + 1));
    vsnprintf(compound, mb_cur_max() * (COLS + 1), msg, ap);
2083
    va_end(ap);
2084
2085
    message = display_string(compound, 0, COLS, FALSE);
    free(compound);
2086

2087
    start_x = (COLS - strlenpt(message)) / 2;
2088
    bracketed = (start_x > 1);
2089

2090
    wmove(bottomwin, 0, (bracketed ? start_x - 2 : start_x));
2091
    wattron(bottomwin, interface_color_pair[STATUS_BAR]);
2092
2093
    if (bracketed)
	waddstr(bottomwin, "[ ");
2094
2095
    waddstr(bottomwin, message);
    free(message);
2096
2097
    if (bracketed)
	waddstr(bottomwin, " ]");
2098
    wattroff(bottomwin, interface_color_pair[STATUS_BAR]);
2099

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

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

2106
#ifndef NANO_TINY
2107
2108
2109
2110
2111
    if (old_whitespace)
	SET(WHITESPACE_DISPLAY);

    /* If doing quick blanking, blank the statusbar after just one keystroke.
     * Otherwise, blank it after twenty-six keystrokes, as Pico does. */
2112
2113
2114
    if (ISSET(QUICK_BLANK))
	statusblank = 1;
    else
2115
#endif
2116
	statusblank = 26;
2117
2118
}

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

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

2130
    if (ISSET(NO_HELP) || LINES < 5)
Chris Allegretta's avatar
Chris Allegretta committed
2131
2132
	return;

2133
    /* Determine how many shortcuts there are to show. */
2134
    number = length_of_list(menu);
2135

2136
2137
    if (number > MAIN_VISIBLE)
	number = MAIN_VISIBLE;
2138

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

2142
    /* If there is no room, don't print anything. */
2143
    if (itemwidth == 0)
2144
2145
	return;

2146
    blank_bottombars();
2147

2148
#ifdef DEBUG
2149
    fprintf(stderr, "In bottombars, number of items == \"%d\"\n", (int) number);
2150
#endif
2151

2152
    for (f = allfuncs, i = 0; i < number && f != NULL; f = f->next) {
2153
#ifdef DEBUG
2154
	fprintf(stderr, "Checking menu items....");
2155
#endif
2156
	if ((f->menus & menu) == 0)
2157
	    continue;
2158

2159
#ifdef DEBUG
2160
	fprintf(stderr, "found one! f->menus = %x, desc = \"%s\"\n", f->menus, f->desc);
2161
#endif
2162
2163
	s = first_sc_for(menu, f->scfunc);
	if (s == NULL) {
2164
2165
2166
#ifdef DEBUG
	    fprintf(stderr, "Whoops, guess not, no shortcut key found for func!\n");
#endif
2167
2168
	    continue;
	}
2169
2170

	wmove(bottomwin, 1 + i % 2, (i / 2) * itemwidth);
2171
#ifdef DEBUG
2172
	fprintf(stderr, "Calling onekey with keystr \"%s\" and desc \"%s\"\n", s->keystr, f->desc);
2173
#endif
2174
	onekey(s->keystr, _(f->desc), itemwidth + (COLS % itemwidth));
2175
	i++;
Chris Allegretta's avatar
Chris Allegretta committed
2176
    }
2177

2178
2179
    wnoutrefresh(bottomwin);
    reset_cursor();
2180
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
2181
2182
}

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

2192
    wattron(bottomwin, interface_color_pair[KEY_COMBO]);
2193
    waddnstr(bottomwin, keystroke, actual_x(keystroke, length));
2194
    wattroff(bottomwin, interface_color_pair[KEY_COMBO]);
2195

2196
    length -= strlenpt(keystroke) + 1;
2197

2198
    if (length > 0) {
2199
	waddch(bottomwin, ' ');
2200
	wattron(bottomwin, interface_color_pair[FUNCTION_TAG]);
2201
	waddnstr(bottomwin, desc, actual_x(desc, length));
2202
	wattroff(bottomwin, interface_color_pair[FUNCTION_TAG]);
Chris Allegretta's avatar
Chris Allegretta committed
2203
2204
2205
    }
}

2206
2207
/* 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
2208
2209
void reset_cursor(void)
{
2210
    size_t xpt = xplustabs();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2211

2212
#ifndef NANO_TINY
2213
    if (ISSET(SOFTWRAP)) {
2214
	filestruct *line = openfile->edittop;
2215
2216
	openfile->current_y = 0;

2217
2218
2219
2220
	while (line != NULL && line != openfile->current) {
	    openfile->current_y += strlenpt(line->data) / COLS + 1;
	    line = line->next;
	}
2221
	openfile->current_y += xpt / COLS;
2222

2223
	if (openfile->current_y < editwinrows)
2224
	    wmove(edit, openfile->current_y, xpt % COLS);
2225
2226
2227
    } else
#endif
    {
2228
	openfile->current_y = openfile->current->lineno -
2229
				openfile->edittop->lineno;
2230
2231
2232
2233

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

2236
2237
2238
2239
2240
2241
2242
2243
/* 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. */
2244
void edit_draw(filestruct *fileptr, const char *converted, int
2245
	line, size_t start)
Chris Allegretta's avatar
Chris Allegretta committed
2246
{
2247
#if !defined(NANO_TINY) || !defined(DISABLE_COLOR)
2248
2249
2250
2251
2252
2253
2254
2255
2256
    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. */
2257
2258
#endif

2259
    assert(openfile != NULL && fileptr != NULL && converted != NULL);
2260
    assert(strlenpt(converted) <= COLS);
2261

2262
2263
2264
    /* 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);
2265

2266
#ifdef USING_OLD_NCURSES
2267
2268
2269
    /* 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. */
2270
2271
    if (seen_wide)
	wredrawln(edit, line, 1);
2272
#endif
2273

2274
#ifndef DISABLE_COLOR
2275
2276
2277
    /* If color syntaxes are available and turned on, we need to display
     * them. */
    if (openfile->colorstrings != NULL && !ISSET(NO_COLOR_SYNTAX)) {
2278
	const colortype *varnish = openfile->colorstrings;
2279

2280
2281
2282
2283
	/* If there are multiline regexes, make sure there is a cache. */
	if (openfile->syntax->nmultis > 0)
	    alloc_multidata_if_needed(fileptr);

2284
	for (; varnish != NULL; varnish = varnish->next) {
2285
2286
	    int x_start;
		/* Starting column for mvwaddnstr.  Zero-based. */
2287
	    int paintlen = 0;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2288
2289
		/* Number of chars to paint on this line.  There are
		 * COLS characters on a whole line. */
2290
	    size_t index;
2291
		/* Index in converted where we paint. */
2292
2293
2294
2295
	    regmatch_t startmatch;
		/* Match position for start_regex. */
	    regmatch_t endmatch;
		/* Match position for end_regex. */
2296

2297
	    wattron(edit, varnish->attributes);
2298
2299
2300
	    /* 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. */
2301

2302
2303
	    /* First case: varnish is a single-line expression. */
	    if (varnish->end == NULL) {
2304
2305
2306
		size_t k = 0;

		/* We increment k by rm_eo, to move past the end of the
2307
		 * last match.  Even though two matches may overlap, we
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2308
2309
		 * want to ignore them, so that we can highlight e.g. C
		 * strings correctly. */
2310
2311
2312
		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
2313
2314
2315
		     * unless k is zero.  If regexec() returns
		     * REG_NOMATCH, there are no more matches in the
		     * line. */
2316
		    if (regexec(varnish->start, &fileptr->data[k], 1,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2317
2318
			&startmatch, (k == 0) ? 0 : REG_NOTBOL) ==
			REG_NOMATCH)
2319
			break;
2320
2321
		    /* Translate the match to the beginning of the
		     * line. */
2322
2323
		    startmatch.rm_so += k;
		    startmatch.rm_eo += k;
2324
2325
2326

		    /* Skip over a zero-length regex match. */
		    if (startmatch.rm_so == startmatch.rm_eo)
2327
			startmatch.rm_eo++;
2328
		    else if (startmatch.rm_so < endpos &&
2329
			startmatch.rm_eo > startpos) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2330
2331
			x_start = (startmatch.rm_so <= startpos) ? 0 :
				strnlenpt(fileptr->data,
2332
				startmatch.rm_so) - start;
2333

2334
2335
2336
			index = actual_x(converted, x_start);

			paintlen = actual_x(converted + index,
2337
2338
				strnlenpt(fileptr->data,
				startmatch.rm_eo) - start - x_start);
2339
2340
2341

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

2342
			mvwaddnstr(edit, line, x_start, converted +
2343
				index, paintlen);
2344
		    }
2345
		    k = startmatch.rm_eo;
Chris Allegretta's avatar
Chris Allegretta committed
2346
		}
2347
	    } else {	/* Second case: varnish is a multiline expression. */
2348
		const filestruct *start_line = fileptr->prev;
2349
		    /* The first line before fileptr that matches 'start'. */
2350
		size_t start_col;
2351
		    /* Where the match starts in that line. */
2352
		const filestruct *end_line;
2353
		    /* The line that matches 'end'. */
2354

2355
		/* First see if the multidata was maybe already calculated. */
2356
		if (fileptr->multidata[varnish->id] == CNONE)
2357
		    goto tail_of_loop;
2358
		else if (fileptr->multidata[varnish->id] == CWHOLELINE) {
2359
		    mvwaddnstr(edit, line, 0, converted, -1);
2360
		    goto tail_of_loop;
2361
2362
		} else if (fileptr->multidata[varnish->id] == CBEGINBEFORE) {
		    regexec(varnish->end, fileptr->data, 1, &endmatch, 0);
2363
2364
		    /* If the coloured part is scrolled off, skip it. */
		    if (endmatch.rm_eo <= startpos)
2365
			goto tail_of_loop;
2366
2367
2368
		    paintlen = actual_x(converted, strnlenpt(fileptr->data,
			endmatch.rm_eo) - start);
		    mvwaddnstr(edit, line, 0, converted, paintlen);
2369
		    goto tail_of_loop;
2370
		} if (fileptr->multidata[varnish->id] == -1)
2371
		    /* Assume this until proven otherwise below. */
2372
		    fileptr->multidata[varnish->id] = CNONE;
2373

2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
		/* 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. */

2384
		while (start_line != NULL && regexec(varnish->start,
2385
2386
2387
			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. */
2388
		    if (regexec(varnish->end, start_line->data, 0, NULL, 0) == 0)
2389
2390
2391
			goto step_two;
		    start_line = start_line->prev;
		}
2392

2393
2394
2395
2396
		/* If no start was found, skip to the next step. */
		if (start_line == NULL)
		    goto step_two;

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

2404
		/* Skip over a zero-length regex match. */
2405
		if (startmatch.rm_so == startmatch.rm_eo)
2406
2407
		    goto tail_of_loop;

2408
2409
2410
2411
2412
2413
2414
		/* 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;
2415
		    if (regexec(varnish->end, start_line->data +
2416
2417
2418
				start_col + startmatch.rm_eo, 0, NULL,
				(start_col + startmatch.rm_eo == 0) ?
				0 : REG_NOTBOL) == REG_NOMATCH)
2419
2420
2421
			/* No end found after this start. */
			break;
		    start_col++;
2422
2423
		    if (regexec(varnish->start, start_line->data + start_col,
				1, &startmatch, REG_NOTBOL) == REG_NOMATCH)
2424
2425
2426
2427
2428
2429
2430
2431
2432
			/* 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;
2433
		while (end_line != NULL && regexec(varnish->end,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2434
			end_line->data, 1, &endmatch, 0) == REG_NOMATCH)
2435
		    end_line = end_line->next;
2436

2437
2438
2439
2440
		/* 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) {
2441
		    fileptr->multidata[varnish->id] = CBEGINBEFORE;
2442
2443
		    goto step_two;
		}
2444

2445
2446
2447
2448
2449
2450
2451
		/* 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;
2452
		    fileptr->multidata[varnish->id] = CWHOLELINE;
2453
#ifdef DEBUG
2454
    fprintf(stderr, "  Marking for id %i  line %i as CWHOLELINE\n", varnish->id, line);
2455
#endif
2456
2457
2458
		} else {
		    paintlen = actual_x(converted, strnlenpt(fileptr->data,
						endmatch.rm_eo) - start);
2459
		    fileptr->multidata[varnish->id] = CBEGINBEFORE;
2460
#ifdef DEBUG
2461
    fprintf(stderr, "  Marking for id %i  line %i as CBEGINBEFORE\n", varnish->id, line);
2462
#endif
2463
2464
2465
2466
2467
2468
		}
		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;
2469
  step_two:
2470
2471
2472
2473
2474
		/* 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) {
2475
		    if (regexec(varnish->start, fileptr->data + start_col,
2476
2477
				1, &startmatch, (start_col == 0) ?
				0 : REG_NOTBOL) == REG_NOMATCH ||
2478
				start_col + startmatch.rm_so >= endpos)
2479
2480
			/* No more starts on this line. */
			break;
2481

2482
2483
2484
2485
2486
2487
2488
		    /* 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,
2489
				startmatch.rm_so) - start;
2490

2491
		    index = actual_x(converted, x_start);
2492

2493
		    if (regexec(varnish->end, fileptr->data +
2494
				startmatch.rm_eo, 1, &endmatch,
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
				(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 &&
2505
				endmatch.rm_eo > startmatch.rm_so) {
2506
			    paintlen = actual_x(converted + index,
2507
					strnlenpt(fileptr->data,
2508
					endmatch.rm_eo) - start - x_start);
2509

2510
			    assert(0 <= x_start && x_start < COLS);
2511

2512
			    mvwaddnstr(edit, line, x_start,
2513
					converted + index, paintlen);
2514
			    if (paintlen > 0) {
2515
				fileptr->multidata[varnish->id] = CSTARTENDHERE;
2516
#ifdef DEBUG
2517
    fprintf(stderr, "  Marking for id %i  line %i as CSTARTENDHERE\n", varnish->id, line);
2518
#endif
2519
			    }
2520
2521
			}
			start_col = endmatch.rm_eo;
2522
2523
2524
			/* Skip over a zero-length match. */
			if (endmatch.rm_so == endmatch.rm_eo)
			    start_col += 1;
2525
2526
2527
2528
		    } else {
			/* There is no end on this line.  But we haven't yet
			 * looked for one on later lines. */
			end_line = fileptr->next;
2529

2530
			while (end_line != NULL &&
2531
				regexec(varnish->end, end_line->data,
2532
				0, NULL, 0) == REG_NOMATCH)
2533
			    end_line = end_line->next;
2534

2535
2536
2537
			/* If there is no end, we're done on this line. */
			if (end_line == NULL)
			    break;
2538

2539
2540
2541
2542
			assert(0 <= x_start && x_start < COLS);

			/* Paint the rest of the line. */
			mvwaddnstr(edit, line, x_start, converted + index, -1);
2543
			fileptr->multidata[varnish->id] = CENDAFTER;
2544
#ifdef DEBUG
2545
    fprintf(stderr, "  Marking for id %i  line %i as CENDAFTER\n", varnish->id, line);
2546
#endif
2547
2548
2549
			/* We've painted to the end of the line, so don't
			 * bother checking for any more starts. */
			break;
2550
		    }
2551
2552
		}
	    }
2553
  tail_of_loop:
2554
	    wattroff(edit, varnish->attributes);
2555
	}
2556
    }
2557
#endif /* !DISABLE_COLOR */
2558

2559
#ifndef NANO_TINY
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
    /* 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. */
2571
	int x_start;
2572
	    /* The starting column for mvwaddnstr().  Zero-based. */
2573
	int paintlen;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2574
2575
	    /* Number of characters to paint on this line.  There are
	     * COLS characters on a whole line. */
2576
	size_t index;
2577
	    /* Index in converted where we paint. */
2578

2579
	mark_order(&top, &top_x, &bot, &bot_x, NULL);
2580
2581
2582
2583
2584

	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
2585

2586
	/* Only paint if the marked bit of fileptr is on this page. */
2587
2588
	if (top_x < endpos && bot_x > startpos) {
	    assert(startpos <= top_x);
2589
2590
2591
2592

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

2594
2595
2596
2597
2598
	    /* 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. */
2599
2600
2601
	    if (bot_x >= endpos)
		paintlen = -1;
	    else
2602
		paintlen = strnlenpt(fileptr->data, bot_x) - (x_start + start);
2603
2604
2605
2606
2607
2608
2609
2610

	    /* 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;
	    }
2611
2612
2613

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

2614
	    index = actual_x(converted, x_start);
2615

2616
2617
2618
	    if (paintlen > 0)
		paintlen = actual_x(converted + index, paintlen);

2619
	    wattron(edit, hilite_attribute);
2620
	    mvwaddnstr(edit, line, x_start, converted + index, paintlen);
2621
	    wattroff(edit, hilite_attribute);
Chris Allegretta's avatar
Chris Allegretta committed
2622
	}
2623
    }
2624
#endif /* !NANO_TINY */
Chris Allegretta's avatar
Chris Allegretta committed
2625
2626
}

2627
/* Just update one line in the edit buffer.  This is basically a wrapper
2628
 * for edit_draw().  The line will be displayed starting with
2629
 * fileptr->data[index].  Likely arguments are current_x or zero.
2630
 * Returns: Number of additional lines consumed (needed for SOFTWRAP). */
2631
int update_line(filestruct *fileptr, size_t index)
Chris Allegretta's avatar
Chris Allegretta committed
2632
{
2633
    int line = 0;
2634
	/* The line in the edit window that we want to update. */
2635
    int extralinesused = 0;
2636
2637
2638
2639
    char *converted;
	/* fileptr->data converted to have tabs and control characters
	 * expanded. */
    size_t page_start;
Chris Allegretta's avatar
Chris Allegretta committed
2640

2641
    assert(fileptr != NULL);
2642

2643
#ifndef NANO_TINY
2644
    if (ISSET(SOFTWRAP)) {
2645
2646
	filestruct *tmp;

2647
2648
	for (tmp = openfile->edittop; tmp && tmp != fileptr; tmp = tmp->next)
	    line += (strlenpt(tmp->data) / COLS) + 1;
2649
    } else
2650
#endif
2651
2652
	line = fileptr->lineno - openfile->edittop->lineno;

2653
    if (line < 0 || line >= editwinrows)
Chris Allegretta's avatar
Chris Allegretta committed
2654
	return 1;
2655

2656
    /* First, blank out the line. */
2657
    blank_line(edit, line, 0, COLS);
2658

2659
2660
    /* Next, convert variables that index the line to their equivalent
     * positions in the expanded line. */
2661
#ifndef NANO_TINY
2662
2663
2664
    if (ISSET(SOFTWRAP))
	index = 0;
    else
2665
#endif
2666
	index = strnlenpt(fileptr->data, index);
2667
    page_start = get_page_start(index);
2668

2669
2670
    /* Expand the line, replacing tabs with spaces, and control
     * characters with their displayed forms. */
2671
2672
2673
#ifdef NANO_TINY
    converted = display_string(fileptr->data, page_start, COLS, TRUE);
#else
2674
2675
2676
    converted = display_string(fileptr->data, page_start, COLS, !ISSET(SOFTWRAP));
#ifdef DEBUG
    if (ISSET(SOFTWRAP) && strlen(converted) >= COLS - 2)
2677
	fprintf(stderr, "update_line(): converted(1) line = %s\n", converted);
2678
#endif
2679
#endif /* !NANO_TINY */
2680

2681
    /* Paint the line. */
2682
    edit_draw(fileptr, converted, line, page_start);
2683
    free(converted);
Chris Allegretta's avatar
Chris Allegretta committed
2684

2685
#ifndef NANO_TINY
2686
    if (!ISSET(SOFTWRAP)) {
2687
#endif
2688
2689
2690
2691
	if (page_start > 0)
	    mvwaddch(edit, line, 0, '$');
	if (strlenpt(fileptr->data) > page_start + COLS)
	    mvwaddch(edit, line, COLS - 1, '$');
2692
#ifndef NANO_TINY
2693
    } else {
2694
	size_t full_length = strlenpt(fileptr->data);
2695
	for (index += COLS; index <= full_length && line < editwinrows - 1; index += COLS) {
2696
2697
	    line++;
#ifdef DEBUG
2698
	    fprintf(stderr, "update_line(): softwrap code, moving to %d index %lu\n", line, (unsigned long)index);
2699
#endif
2700
	    blank_line(edit, line, 0, COLS);
2701
2702

	    /* Expand the line, replacing tabs with spaces, and control
2703
	     * characters with their displayed forms. */
2704
2705
2706
2707
2708
	    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
2709
2710
2711

	    /* Paint the line. */
	    edit_draw(fileptr, converted, line, index);
2712
	    free(converted);
2713
2714
2715
	    extralinesused++;
	}
    }
2716
#endif /* !NANO_TINY */
2717
    return extralinesused;
Chris Allegretta's avatar
Chris Allegretta committed
2718
2719
}

2720
2721
2722
/* Check whether old_column and new_column are on different "pages" (or that
 * the mark is on), which means that the relevant line needs to be redrawn. */
bool need_horizontal_scroll(const size_t old_column, const size_t new_column)
2723
{
2724
#ifndef NANO_TINY
2725
2726
2727
    if (openfile->mark_set)
	return TRUE;
    else
2728
#endif
2729
	return (get_page_start(old_column) != get_page_start(new_column));
2730
2731
}

2732
/* When edittop changes, try and figure out how many lines
2733
 * we really have to work with (i.e. set maxrows). */
2734
2735
2736
2737
2738
void compute_maxrows(void)
{
    int n;
    filestruct *foo = openfile->edittop;

2739
2740
2741
2742
2743
    if (!ISSET(SOFTWRAP)) {
	maxrows = editwinrows;
	return;
    }

2744
2745
    maxrows = 0;
    for (n = 0; n < editwinrows && foo; n++) {
2746
	maxrows++;
2747
	n += strlenpt(foo->data) / COLS;
2748
2749
2750
	foo = foo->next;
    }

2751
2752
2753
    if (n < editwinrows)
	maxrows += editwinrows - n;

2754
#ifdef DEBUG
2755
    fprintf(stderr, "compute_maxrows(): maxrows = %d\n", maxrows);
2756
2757
2758
#endif
}

2759
2760
/* 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
2761
2762
 * scrolling.  direction is the direction to scroll, either UPWARD or
 * DOWNWARD, and nlines is the number of lines to scroll.  We change
2763
2764
 * edittop, and assume that current and current_x are up to date.  We
 * also assume that scrollok(edit) is FALSE. */
2765
void edit_scroll(scroll_dir direction, ssize_t nlines)
2766
{
2767
    ssize_t i;
2768
    filestruct *foo;
2769

2770
    assert(nlines > 0);
2771

2772
2773
2774
    /* Part 1: nlines is the number of lines we're going to scroll the
     * text of the edit window. */

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

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

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

2803
2804
2805
2806
    /* Don't bother scrolling zero lines, nor more than the window can hold. */
    if (nlines == 0)
	return;
    if (nlines >= editwinrows)
2807
	refresh_needed = TRUE;
2808

2809
    if (refresh_needed == TRUE)
2810
	return;
2811
2812
2813

    /* Scroll the text of the edit window up or down nlines lines,
     * depending on the value of direction. */
2814
    scrollok(edit, TRUE);
2815
    wscrl(edit, (direction == UPWARD) ? -nlines : nlines);
2816
2817
    scrollok(edit, FALSE);

2818
2819
2820
    /* Part 2: nlines is the number of lines in the scrolled region of
     * the edit window that we need to draw. */

2821
2822
2823
2824
2825
2826
    /* 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
2827

2828
2829
    if (nlines > editwinrows)
	nlines = editwinrows;
2830
2831
2832

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

2835
2836
    /* If we scrolled down, move down to the line before the scrolled
     * region. */
2837
    if (direction == DOWNWARD) {
2838
	for (i = editwinrows - nlines; i > 0 && foo != NULL; i--)
2839
2840
2841
	    foo = foo->next;
    }

2842
2843
2844
2845
2846
2847
    /* 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--) {
2848
2849
	if ((i == nlines && direction == DOWNWARD) || (i == 1 &&
		direction == UPWARD)) {
2850
	    if (need_horizontal_scroll(openfile->placewewant, 0))
2851
2852
2853
2854
		update_line(foo, (foo == openfile->current) ?
			openfile->current_x : 0);
	} else
	    update_line(foo, (foo == openfile->current) ?
2855
		openfile->current_x : 0);
2856
	foo = foo->next;
2857
    }
2858
    compute_maxrows();
2859
2860
2861
}

/* Update any lines between old_current and current that need to be
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2862
 * updated.  Use this if we've moved without changing any text. */
2863
void edit_redraw(filestruct *old_current)
2864
{
2865
2866
2867
2868
    size_t was_pww = openfile->placewewant;

    openfile->placewewant = xplustabs();

2869
2870
    /* If the current line is offscreen, scroll until it's onscreen. */
    if (openfile->current->lineno >= openfile->edittop->lineno + maxrows ||
2871
		openfile->current->lineno < openfile->edittop->lineno) {
2872
	edit_update((focusing || !ISSET(SMOOTH_SCROLL)) ? CENTERING : FLOWING);
2873
	refresh_needed = TRUE;
2874
    }
2875

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

2881
	while (foo != openfile->current) {
2882
	    update_line(foo, 0);
2883

2884
2885
2886
	    foo = (foo->lineno > openfile->current->lineno) ?
			foo->prev : foo->next;
	}
2887
2888
2889
2890
2891
2892
    } 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);
2893
2894
2895

    /* Update current if we've changed page, or if it differs from
     * old_current and needs to be horizontally scrolled. */
2896
    if (need_horizontal_scroll(was_pww, openfile->placewewant) ||
2897
			(old_current != openfile->current &&
2898
			get_page_start(openfile->placewewant) > 0))
2899
	update_line(openfile->current, openfile->current_x);
2900
2901
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2902
2903
/* 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
2904
2905
void edit_refresh(void)
{
2906
    filestruct *foo;
2907
    int nlines;
2908

2909
    /* Figure out what maxrows should really be. */
2910
    compute_maxrows();
2911

2912
2913
    if (openfile->current->lineno < openfile->edittop->lineno ||
	openfile->current->lineno >= openfile->edittop->lineno +
2914
2915
	maxrows) {
#ifdef DEBUG
2916
2917
	fprintf(stderr, "edit_refresh(): line = %ld, edittop %ld + maxrows %d\n",
		(long)openfile->current->lineno, (long)openfile->edittop->lineno, maxrows);
2918
2919
#endif

2920
	/* Make sure the current line is on the screen. */
2921
	edit_update((focusing || !ISSET(SMOOTH_SCROLL)) ? CENTERING : STATIONARY);
2922
    }
Chris Allegretta's avatar
Chris Allegretta committed
2923

2924
2925
    foo = openfile->edittop;

2926
#ifdef DEBUG
2927
    fprintf(stderr, "edit_refresh(): edittop->lineno = %ld\n", (long)openfile->edittop->lineno);
2928
#endif
2929

2930
    for (nlines = 0; nlines < editwinrows && foo != NULL; nlines++) {
2931
	nlines += update_line(foo, (foo == openfile->current) ?
2932
		openfile->current_x : 0);
2933
2934
2935
	foo = foo->next;
    }

2936
    for (; nlines < editwinrows; nlines++)
2937
2938
2939
	blank_line(edit, nlines, 0, COLS);

    reset_cursor();
2940
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
2941
2942
}

2943
2944
2945
2946
2947
2948
/* 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
2949
{
2950
    int goal = 0;
2951

2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
    /* 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)
2962
	goal = editwinrows / 2;
2963
    else if (manner == FLOWING) {
2964
	if (openfile->current->lineno >= openfile->edittop->lineno)
2965
2966
	    goal = editwinrows - 1;
    } else {
2967
	goal = openfile->current_y;
2968

2969
	/* Limit goal to (editwinrows - 1) lines maximum. */
2970
2971
	if (goal > editwinrows - 1)
	    goal = editwinrows - 1;
Chris Allegretta's avatar
Chris Allegretta committed
2972
    }
2973

2974
2975
2976
2977
2978
    openfile->edittop = openfile->current;

    while (goal > 0 && openfile->edittop->prev != NULL) {
	openfile->edittop = openfile->edittop->prev;
	goal --;
2979
#ifndef NANO_TINY
2980
2981
	if (ISSET(SOFTWRAP))
	    goal -= strlenpt(openfile->edittop->data) / COLS;
2982
#endif
2983
    }
2984
#ifdef DEBUG
2985
    fprintf(stderr, "edit_update(): setting edittop to lineno %ld\n", (long)openfile->edittop->lineno);
2986
#endif
2987
    compute_maxrows();
Chris Allegretta's avatar
Chris Allegretta committed
2988
2989
}

2990
/* Unconditionally redraw the entire screen. */
2991
void total_redraw(void)
2992
{
2993
2994
2995
2996
2997
2998
#ifdef USE_SLANG
    /* Slang curses emulation brain damage, part 4: Slang doesn't define
     * curscr. */
    SLsmg_touch_screen();
    SLsmg_refresh();
#else
2999
    wrefresh(curscr);
3000
#endif
3001
3002
}

3003
3004
/* Unconditionally redraw the entire screen, and then refresh it using
 * the current file. */
3005
3006
void total_refresh(void)
{
3007
    total_redraw();
3008
    titlebar(NULL);
3009
    edit_refresh();
3010
    bottombars(currmenu);
3011
3012
}

3013
3014
/* Display the main shortcut list on the last two rows of the bottom
 * portion of the window. */
3015
3016
void display_main_list(void)
{
3017
#ifndef DISABLE_COLOR
3018
3019
    if (openfile->syntax &&
		(openfile->syntax->formatter || openfile->syntax->linter))
3020
	set_lint_or_format_shortcuts();
3021
3022
3023
3024
    else
	set_spell_shortcuts();
#endif

3025
    bottombars(MMAIN);
3026
3027
}

3028
/* If constant is TRUE, we display the current cursor position only if
3029
3030
 * suppress_cursorpos is FALSE.  If constant is FALSE, we display the
 * position always.  In any case we reset suppress_cursorpos to FALSE. */
3031
void do_cursorpos(bool constant)
Chris Allegretta's avatar
Chris Allegretta committed
3032
{
3033
    filestruct *f;
3034
    char c;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3035
    size_t i, cur_xpt = xplustabs() + 1;
3036
    size_t cur_lenpt = strlenpt(openfile->current->data) + 1;
3037
    int linepct, colpct, charpct;
Chris Allegretta's avatar
Chris Allegretta committed
3038

3039
    assert(openfile->fileage != NULL && openfile->current != NULL);
3040

3041
    /* Determine the size of the file up to the cursor. */
3042
    f = openfile->current->next;
3043
    c = openfile->current->data[openfile->current_x];
3044
3045

    openfile->current->next = NULL;
3046
    openfile->current->data[openfile->current_x] = '\0';
3047
3048
3049

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

3050
    openfile->current->data[openfile->current_x] = c;
3051
    openfile->current->next = f;
3052

3053
    /* If the position needs to be suppressed, don't suppress it next time. */
3054
    if (suppress_cursorpos && constant) {
3055
	suppress_cursorpos = FALSE;
3056
	return;
3057
    }
Chris Allegretta's avatar
Chris Allegretta committed
3058

3059
    /* Display the current cursor position on the statusbar. */
3060
    linepct = 100 * openfile->current->lineno / openfile->filebot->lineno;
3061
    colpct = 100 * cur_xpt / cur_lenpt;
3062
    charpct = (openfile->totsize == 0) ? 0 : 100 * i / openfile->totsize;
3063

3064
    statusline(HUSH,
3065
	_("line %ld/%ld (%d%%), col %lu/%lu (%d%%), char %lu/%lu (%d%%)"),
3066
	(long)openfile->current->lineno,
3067
	(long)openfile->filebot->lineno, linepct,
3068
	(unsigned long)cur_xpt, (unsigned long)cur_lenpt, colpct,
3069
	(unsigned long)i, (unsigned long)openfile->totsize, charpct);
3070
3071
3072

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

3075
/* Unconditionally display the current cursor position. */
3076
void do_cursorpos_void(void)
3077
{
3078
    do_cursorpos(FALSE);
3079
3080
}

3081
3082
void enable_nodelay(void)
{
3083
3084
    nodelay_mode = TRUE;
    nodelay(edit, TRUE);
3085
3086
3087
3088
}

void disable_nodelay(void)
{
3089
3090
    nodelay_mode = FALSE;
    nodelay(edit, FALSE);
3091
3092
}

3093
3094
/* Highlight the current word being replaced or spell checked.  We
 * expect word to have tabs and control characters expanded. */
3095
void spotlight(bool active, const char *word)
Chris Allegretta's avatar
Chris Allegretta committed
3096
{
3097
    size_t word_len = strlenpt(word), room;
Chris Allegretta's avatar
Chris Allegretta committed
3098

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

3102
    assert(room > 0);
3103

3104
3105
    if (word_len > room)
	room--;
3106

Chris Allegretta's avatar
Chris Allegretta committed
3107
    reset_cursor();
3108
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
3109

3110
    if (active)
3111
	wattron(edit, hilite_attribute);
Chris Allegretta's avatar
Chris Allegretta committed
3112

3113
    /* This is so we can show zero-length matches. */
3114
    if (word_len == 0)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3115
	waddch(edit, ' ');
3116
    else
3117
	waddnstr(edit, word, actual_x(word, room));
3118

3119
    if (word_len > room)
3120
	waddch(edit, '$');
Chris Allegretta's avatar
Chris Allegretta committed
3121

3122
    if (active)
3123
	wattroff(edit, hilite_attribute);
Chris Allegretta's avatar
Chris Allegretta committed
3124
3125
}

3126
#ifndef DISABLE_EXTRA
3127
3128
#define CREDIT_LEN 54
#define XLCREDIT_LEN 9
3129

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

3194
    const char *xlcredits[XLCREDIT_LEN] = {
3195
3196
3197
3198
3199
	N_("The nano text editor"),
	N_("version"),
	N_("Brought to you by:"),
	N_("Special thanks to:"),
	N_("The Free Software Foundation"),
3200
	N_("the many translators and the TP"),
3201
3202
3203
	N_("For ncurses:"),
	N_("and anyone else we forgot..."),
	N_("Thank you for using nano!")
3204
    };
3205

3206
3207
3208
3209
3210
3211
    if (!old_more_space || !old_no_help) {
	SET(MORE_SPACE);
	SET(NO_HELP);
	window_init();
    }

3212
3213
    curs_set(0);
    nodelay(edit, TRUE);
3214

3215
    blank_titlebar();
Chris Allegretta's avatar
Chris Allegretta committed
3216
    blank_edit();
3217
    blank_statusbar();
3218

3219
    wrefresh(topwin);
Chris Allegretta's avatar
Chris Allegretta committed
3220
    wrefresh(edit);
3221
    wrefresh(bottomwin);
3222
    napms(700);
3223

3224
    for (crpos = 0; crpos < CREDIT_LEN + editwinrows / 2; crpos++) {
3225
	if ((kbinput = wgetch(edit)) != ERR)
3226
	    break;
3227

3228
	if (crpos < CREDIT_LEN) {
3229
	    const char *what;
3230
3231
	    size_t start_x;

3232
	    if (credits[crpos] == NULL) {
3233
		assert(0 <= xlpos && xlpos < XLCREDIT_LEN);
3234

3235
		what = _(xlcredits[xlpos]);
3236
		xlpos++;
3237
	    } else
3238
		what = credits[crpos];
3239

3240
	    start_x = COLS / 2 - strlenpt(what) / 2 - 1;
3241
3242
	    mvwaddstr(edit, editwinrows - 1 - (editwinrows % 2),
		start_x, what);
3243
	}
3244

3245
3246
3247
3248
	wrefresh(edit);

	if ((kbinput = wgetch(edit)) != ERR)
	    break;
3249
	napms(700);
3250

3251
	scrollok(edit, TRUE);
3252
	wscrl(edit, 1);
3253
	scrollok(edit, FALSE);
3254
	wrefresh(edit);
3255

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

3260
	scrollok(edit, TRUE);
3261
	wscrl(edit, 1);
3262
	scrollok(edit, FALSE);
3263
	wrefresh(edit);
3264
3265
    }

3266
3267
3268
    if (kbinput != ERR)
	ungetch(kbinput);

3269
    if (!old_more_space)
3270
	UNSET(MORE_SPACE);
3271
    if (!old_no_help)
3272
	UNSET(NO_HELP);
3273
    window_init();
3274

3275
    nodelay(edit, FALSE);
3276

3277
    total_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
3278
}
3279
#endif /* !DISABLE_EXTRA */