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

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

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

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

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

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

58
#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
    int kbinput = ERR;
309

310
    /* Extract one keystroke from the input stream. */
311
312
    while (kbinput == ERR)
	kbinput = parse_kbinput(win);
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
402
403
404
405
406
407
408
409
410
	    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;
		    case 'C':
			retval = controlright;
			break;
		    case 'D':
			retval = controlleft;
			break;
		}
		double_esc = FALSE;
		escapes = 0;
411
	    } else if (key_buffer_len == 0) {
412
413
414
415
416
417
418
419
420
421
422
423
424
425
		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);

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

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

435
			seq = (int *)nmalloc(byte_mb_len * sizeof(int));
436
437
438
439

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

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

			free(byte_mb);
			free(seq);
445
446
447

			byte_digits = 0;
			escapes = 0;
448
		    }
449
450
451
452
453
454
455
456
457
458
459
460
461
		} 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;
462
			retval = keycode;
463
		    }
464
		    escapes = 0;
465
466
467
468
469
470
471
472
473
		}
	    } 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);
474
475
		meta_key = TRUE;
		escapes = 0;
476
	    }
477
478
	    break;
	case 3:
479
	    if (key_buffer_len == 0)
480
481
482
483
484
485
486
487
488
489
		/* 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));
490
	    escapes = 0;
491
492
	    break;
    }
493

494
495
496
    if (retval == ERR)
	return ERR;

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

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

    if (console && ioctl(0, TIOCLINUX, &modifiers) >= 0) {
	/* Is Ctrl being held? */
	if (modifiers & 0x04) {
541
	    if (retval == KEY_UP)
542
		return sc_seq_or(do_prev_block, controlup);
543
	    else if (retval == KEY_DOWN)
544
		return sc_seq_or(do_next_block, controldown);
545
	    else if (retval == KEY_LEFT)
546
		return sc_seq_or(do_prev_word_void, controlleft);
547
	    else if (retval == KEY_RIGHT)
548
		return sc_seq_or(do_next_word_void, controlright);
549
	}
550

551
552
553
554
555
#ifndef NANO_TINY
	/* Is Shift being held? */
	if (modifiers & 0x01)
	    shift_held =TRUE;

556
557
558
	/* Are both Shift and Alt being held? */
	if ((modifiers & 0x09) == 0x09) {
	    if (retval == KEY_UP)
559
		return sc_seq_or(do_page_up, shiftaltup);
560
	    else if (retval == KEY_DOWN)
561
		return sc_seq_or(do_page_down, shiftaltdown);
562
	    else if (retval == KEY_LEFT)
563
		return sc_seq_or(do_home, shiftaltleft);
564
	    else if (retval == KEY_RIGHT)
565
		return sc_seq_or(do_end, shiftaltright);
566
	}
567
#endif
568
    }
569
#endif /* __linux__ */
570

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

688
689
690
    return retval;
}

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

704
705
	switch (seq[3]) {
	    case '2':
706
707
708
709
710
		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. */
711
			shift_held = TRUE;
712
713
714
715
716
717
718
719
720
721
			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);
		}
722
723
		break;
	    case '5':
724
725
726
727
728
729
730
731
732
733
		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;
		}
734
735
		break;
	}
736

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

910
	switch (seq[3]) {
911
	    case '2':
912
913
914
915
916
		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. */
917
			shift_held = TRUE;
918
919
			return arrow_from_abcd(seq[4]);
		}
920
		break;
921
922
923
924
925
926
927
928
929
930
#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. */
931
			return SHIFT_END;
932
		    case 'D': /* Esc [ 1 ; 4 D == Shift-Alt-Left on xterm. */
933
			return SHIFT_HOME;
934
935
936
		}
		break;
#endif
937
	    case '5':
938
939
940
941
942
943
944
945
946
947
		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;
		}
948
		break;
949
950
951
952
953
954
955
956
957
958
959
960
961
962
#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
963
	}
964
965
966
967

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

1126
    return ERR;
1127
1128
}

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

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

    free(seq);

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

1180
#ifdef DEBUG
1181
1182
    fprintf(stderr, "parse_escape_sequence(): kbinput = %d, seq_len = %lu, retval = %d\n",
		kbinput, (unsigned long)seq_len, retval);
1183
1184
1185
1186
1187
#endif

    return retval;
}

1188
1189
/* Translate a byte sequence: turn a three-digit decimal number (from
 * 000 to 255) into its corresponding byte value. */
1190
int get_byte_kbinput(int kbinput)
1191
{
1192
    static int byte_digits = 0, byte = 0;
1193
    int retval = ERR;
1194

1195
1196
    /* Increment the byte digit counter. */
    byte_digits++;
1197

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

    /* If we have a result, reset the byte digit counter and the byte
     * sequence holder. */
    if (retval != ERR) {
	byte_digits = 0;
1249
	byte = 0;
1250
1251
1252
    }

#ifdef DEBUG
1253
    fprintf(stderr, "get_byte_kbinput(): kbinput = %d, byte_digits = %d, byte = %d, retval = %d\n", kbinput, byte_digits, byte, retval);
1254
1255
1256
1257
1258
#endif

    return retval;
}

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

1272
    return ERR;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1273
1274
}

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

1284
    /* Increment the Unicode digit counter. */
1285
    uni_digits++;
1286

1287
    switch (uni_digits) {
1288
	case 1:
1289
1290
1291
1292
	    /* 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')
1293
		uni = (kbinput - '0') * 0x100000;
1294
1295
1296
1297
	    else
		retval = kbinput;
	    break;
	case 2:
1298
1299
1300
	    /* 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)
1301
		retval = add_unicode_digit(kbinput, 0x10000, &uni);
1302
1303
1304
1305
	    else
		retval = kbinput;
	    break;
	case 3:
1306
	    /* Later digits may be any hexadecimal value. */
1307
	    retval = add_unicode_digit(kbinput, 0x1000, &uni);
1308
	    break;
1309
	case 4:
1310
	    retval = add_unicode_digit(kbinput, 0x100, &uni);
1311
	    break;
1312
	case 5:
1313
	    retval = add_unicode_digit(kbinput, 0x10, &uni);
1314
	    break;
1315
	case 6:
1316
	    retval = add_unicode_digit(kbinput, 0x1, &uni);
1317
1318
	    /* If also the sixth digit was a valid hexadecimal value, then
	     * the Unicode sequence is complete, so return it. */
1319
	    if (retval == ERR)
1320
		retval = uni;
1321
1322
	    break;
    }
1323

1324
    /* Show feedback only when editing, not when at a prompt. */
1325
    if (retval == ERR && win == edit) {
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
	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);
    }
1336

1337
#ifdef DEBUG
1338
1339
    fprintf(stderr, "get_unicode_kbinput(): kbinput = %d, uni_digits = %d, uni = %ld, retval = %ld\n",
						kbinput, uni_digits, uni, retval);
1340
1341
#endif

1342
1343
1344
1345
    /* If we have an end result, reset the Unicode digit counter. */
    if (retval != ERR)
	uni_digits = 0;

1346
1347
    return retval;
}
1348
#endif /* ENABLE_UTF8 */
1349

1350
1351
1352
1353
1354
1355
/* Translate a control character sequence: turn an ASCII non-control
 * character into its corresponding control character. */
int get_control_kbinput(int kbinput)
{
    int retval;

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

1377
#ifdef DEBUG
1378
    fprintf(stderr, "get_control_kbinput(): kbinput = %d, retval = %d\n", kbinput, retval);
1379
1380
#endif

1381
1382
    return retval;
}
1383

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

1391
1392
1393
1394
    if (output_len == 0)
	return;

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

1396
1397
    for (i = 0; i < output_len; i++)
	input[i] = (int)output[i];
1398

1399
    unget_input(input, output_len);
1400

1401
    free(input);
1402
1403
}

1404
/* Read in a stream of characters verbatim, and return the length of the
1405
1406
1407
1408
 * string in kbinput_len.  Assume nodelay(win) is FALSE. */
int *get_verbatim_kbinput(WINDOW *win, size_t *kbinput_len)
{
    int *retval;
1409

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

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

    /* Turn flow control characters back on if necessary and turn the
1422
     * keypad back on if necessary now that we're done. */
1423
1424
    if (ISSET(PRESERVE))
	enable_flow_control();
1425
1426
1427
1428
1429
1430
    /* 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);
    }
1431

1432
    return retval;
1433
1434
}

1435
1436
1437
1438
1439
/* 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)
1440
{
1441
    int *kbinput;
1442

1443
    /* Read in the first code. */
1444
1445
    while ((kbinput = get_input(win, 1)) == NULL)
	;
1446

1447
#ifndef NANO_TINY
1448
1449
1450
    /* When the window was resized, abort and return nothing. */
    if (*kbinput == KEY_WINCH) {
	free(kbinput);
1451
	*count = 0;
1452
1453
	return NULL;
    }
1454
#endif
1455

1456
#ifdef ENABLE_UTF8
1457
    if (using_utf8()) {
1458
	/* Check whether the first code is a valid starter digit: 0 or 1. */
1459
	long uni = get_unicode_kbinput(win, *kbinput);
1460

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

	    while (uni == ERR) {
1471
		free(kbinput);
1472
1473
		while ((kbinput = get_input(win, 1)) == NULL)
		    ;
1474
		uni = get_unicode_kbinput(win, *kbinput);
1475
	    }
1476

1477
	    /* Convert the Unicode value to a multibyte sequence. */
1478
	    uni_mb = make_mbchar(uni, &uni_mb_len);
1479

1480
	    seq = (int *)nmalloc(uni_mb_len * sizeof(int));
1481

1482
1483
	    for (i = 0; i < uni_mb_len; i++)
		seq[i] = (unsigned char)uni_mb[i];
1484

1485
	    /* Insert the multibyte sequence into the input buffer. */
1486
	    unget_input(seq, uni_mb_len);
1487

1488
1489
	    free(seq);
	    free(uni_mb);
1490
	}
1491
    } else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1492
#endif /* ENABLE_UTF8 */
1493
	/* Put back the first code. */
1494
	unget_input(kbinput, 1);
1495

1496
1497
    free(kbinput);

1498
    *count = 1;
1499

1500
1501
1502
1503
1504
1505
    /* 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);
1506
1507
}

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

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

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

1536
    /* Save the screen coordinates where the mouse event took place. */
1537
    *mouse_x = mevent.x - margin;
1538
    *mouse_y = mevent.y;
1539

1540
1541
    in_bottomwin = wenclose(bottomwin, *mouse_y, *mouse_x);

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

		return 0;
	    }
1572

1573
	    /* Determine how many shortcuts are being shown. */
1574
	    number = length_of_list(currmenu);
1575

1576
1577
	    if (number > MAIN_VISIBLE)
		number = MAIN_VISIBLE;
1578

1579
1580
1581
	    /* 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. */
1582
	    if (number < 2)
1583
1584
		i = COLS / (MAIN_VISIBLE / 2);
	    else
1585
		i = COLS / ((number / 2) + (number % 2));
1586

1587
1588
	    /* Calculate the one-based index in the shortcut list. */
	    j = (*mouse_x / i) * 2 + *mouse_y;
1589

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

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

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

1635
1636
1637
1638
	if (in_bottomwin)
	    /* Translate the mouse event coordinates so that they're
	     * relative to bottomwin. */
	    wmouse_trafo(bottomwin, mouse_y, mouse_x, FALSE);
1639

1640
	if (in_edit || (in_bottomwin && *mouse_y == 0)) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1641
1642
	    int i;

1643
1644
1645
1646
1647
	    /* 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) ?
1648
				KEY_PPAGE : KEY_NPAGE, FALSE);
1649
1650
1651
1652
1653
1654
1655

	    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;
1656
1657
    }
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1658
1659
1660

    /* Ignore all other mouse events. */
    return 2;
1661
}
1662
1663
#endif /* !DISABLE_MOUSE */

1664
1665
1666
1667
/* 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. */
1668
const sc *get_shortcut(int *kbinput)
1669
{
1670
    sc *s;
1671

1672
#ifdef DEBUG
1673
1674
    fprintf(stderr, "get_shortcut(): kbinput = %d, meta_key = %s -- ",
				*kbinput, meta_key ? "TRUE" : "FALSE");
1675
1676
#endif

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

    return NULL;
}

1694
1695
1696
1697
1698
/* 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
1699

1700
1701
1702
1703
    for (; n > 0; n--)
	waddch(win, ' ');
}

1704
/* Blank the first line of the top portion of the window. */
1705
void blank_titlebar(void)
Chris Allegretta's avatar
Chris Allegretta committed
1706
{
1707
    blank_line(topwin, 0, 0, COLS);
1708
1709
}

1710
/* Blank all the lines of the middle portion of the window, i.e. the
1711
 * edit window. */
Chris Allegretta's avatar
Chris Allegretta committed
1712
1713
void blank_edit(void)
{
1714
    int i;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1715

1716
    for (i = 0; i < editwinrows; i++)
1717
	blank_line(edit, i, 0, COLS);
Chris Allegretta's avatar
Chris Allegretta committed
1718
1719
}

1720
/* Blank the first line of the bottom portion of the window. */
Chris Allegretta's avatar
Chris Allegretta committed
1721
1722
void blank_statusbar(void)
{
1723
    blank_line(bottomwin, 0, 0, COLS);
Chris Allegretta's avatar
Chris Allegretta committed
1724
1725
}

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

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

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

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

1781
1782
    /* If dollars is TRUE, make room for the "$" at the end of the
     * line. */
1783
1784
    if (dollars && span > 0 && strlenpt(buf) > start_col + span)
	span--;
1785

1786
    if (span == 0)
1787
1788
1789
1790
	return mallocstrcpy(NULL, "");

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

1792
    assert(column <= start_col);
1793

1794
1795
    /* Allocate enough space to hold the entire converted buffer. */
    converted = charalloc(strlen(buf) * (mb_cur_max() + tabsize) + 1);
1796

1797
    index = 0;
1798
#ifdef USING_OLD_NCURSES
1799
    seen_wide = FALSE;
1800
#endif
1801
    buf += start_index;
1802

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

1821
	    converted[index++] = ' ';
1822
	    start_col++;
1823

1824
	    buf += parse_mbchar(buf, NULL, NULL);
1825
	}
1826
#endif
1827
1828
    }

1829
    while (*buf != '\0') {
1830
	int charlength, charwidth = 1;
1831

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

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

1867
	charlength = length_of_char(buf, &charwidth);
1868

1869
	/* If buf contains a control character, represent it. */
1870
	if (is_cntrl_mbchar(buf)) {
1871
	    converted[index++] = '^';
1872
	    converted[index++] = control_mbrep(buf);
1873
	    start_col += 2;
1874
1875
1876
	    buf += charlength;
	    continue;
	}
1877

1878
1879
1880
1881
	/* If buf contains a valid non-control character, simply copy it. */
	if (charlength > 0) {
	    for (; charlength > 0; charlength--)
		converted[index++] = *(buf++);
1882

1883
	    start_col += charwidth;
1884
#ifdef USING_OLD_NCURSES
1885
	    if (charwidth > 1)
1886
		seen_wide = TRUE;
1887
#endif
1888
	    continue;
1889
1890
	}

1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
	/* 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;
1902
1903
    }

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1904
    /* Null-terminate converted. */
1905
    converted[index] = '\0';
1906

1907
1908
    /* Make sure converted takes up no more than span columns. */
    index = actual_x(converted, span);
1909
    null_at(&converted, index);
1910

1911
    return converted;
1912
1913
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1914
1915
1916
1917
1918
1919
/* 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. */
1920
void titlebar(const char *path)
Chris Allegretta's avatar
Chris Allegretta committed
1921
{
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
    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. */
1934

1935
1936
1937
1938
    /* If the screen is too small, there is no titlebar. */
    if (topwin == NULL)
	return;

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

1941
    wattron(topwin, interface_color_pair[TITLE_BAR]);
1942

1943
    blank_titlebar();
Chris Allegretta's avatar
Chris Allegretta committed
1944

1945
1946
1947
    /* 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
1948

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

1963
1964
1965
1966
	if (openfile->modified)
	    state = _("Modified");
	else if (ISSET(VIEW_MODE))
	    state = _("View");
1967

1968
1969
	pluglen = strlenpt(_("Modified")) + 1;
    }
1970

1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
    /* 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;
1981
1982
    }

1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
    /* 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;
1995
1996
1997
	}
    }

1998
1999
2000
2001
    /* If we have side spaces left, center the path name. */
    if (verlen > 0)
	offset = verlen + (COLS - (verlen + pluglen + statelen) -
					(prefixlen + pathlen)) / 2;
2002

2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
    /* 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);
2020
    }
2021

2022
2023
2024
2025
2026
2027
    /* 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));

2028
    wattroff(topwin, interface_color_pair[TITLE_BAR]);
2029

2030
    wnoutrefresh(topwin);
Chris Allegretta's avatar
Chris Allegretta committed
2031
    reset_cursor();
2032
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
2033
2034
}

2035
2036
2037
2038
2039
2040
/* Display a normal message on the statusbar, quietly. */
void statusbar(const char *msg)
{
    statusline(HUSH, msg);
}

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

    UNSET(WHITESPACE_DISPLAY);
2054
#endif
2055
2056
2057
2058
2059

    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(). */
2060
    if (isendwin()) {
2061
2062
2063
2064
2065
	vfprintf(stderr, msg, ap);
	va_end(ap);
	return;
    }

2066
2067
2068
2069
2070
2071
2072
    /* 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)
2073
2074
	napms(1200);

2075
    if (importance == ALERT)
2076
	beep();
2077
2078

    lastmessage = importance;
2079

2080
2081
2082
    /* Turn the cursor off while fiddling in the statusbar. */
    curs_set(0);

2083
2084
    blank_statusbar();

2085
2086
2087
    /* Construct the message out of all the arguments. */
    compound = charalloc(mb_cur_max() * (COLS + 1));
    vsnprintf(compound, mb_cur_max() * (COLS + 1), msg, ap);
2088
    va_end(ap);
2089
2090
    message = display_string(compound, 0, COLS, FALSE);
    free(compound);
2091

2092
    start_x = (COLS - strlenpt(message)) / 2;
2093
    bracketed = (start_x > 1);
2094

2095
    wmove(bottomwin, 0, (bracketed ? start_x - 2 : start_x));
2096
    wattron(bottomwin, interface_color_pair[STATUS_BAR]);
2097
2098
    if (bracketed)
	waddstr(bottomwin, "[ ");
2099
2100
    waddstr(bottomwin, message);
    free(message);
2101
2102
    if (bracketed)
	waddstr(bottomwin, " ]");
2103
    wattroff(bottomwin, interface_color_pair[STATUS_BAR]);
2104

2105
    /* Push the message to the screen straightaway. */
2106
    wnoutrefresh(bottomwin);
2107
    doupdate();
2108

2109
    suppress_cursorpos = TRUE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2110

2111
#ifndef NANO_TINY
2112
2113
2114
2115
2116
    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. */
2117
2118
2119
    if (ISSET(QUICK_BLANK))
	statusblank = 1;
    else
2120
#endif
2121
	statusblank = 26;
2122
2123
}

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

2132
2133
2134
    /* Set the global variable to the given menu. */
    currmenu = menu;

2135
    if (ISSET(NO_HELP) || LINES < 5)
Chris Allegretta's avatar
Chris Allegretta committed
2136
2137
	return;

2138
    /* Determine how many shortcuts there are to show. */
2139
    number = length_of_list(menu);
2140

2141
2142
    if (number > MAIN_VISIBLE)
	number = MAIN_VISIBLE;
2143

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

2147
    /* If there is no room, don't print anything. */
2148
    if (itemwidth == 0)
2149
2150
	return;

2151
    blank_bottombars();
2152

2153
#ifdef DEBUG
2154
    fprintf(stderr, "In bottombars, number of items == \"%d\"\n", (int) number);
2155
#endif
2156

2157
    for (f = allfuncs, i = 0; i < number && f != NULL; f = f->next) {
2158
#ifdef DEBUG
2159
	fprintf(stderr, "Checking menu items....");
2160
#endif
2161
	if ((f->menus & menu) == 0)
2162
	    continue;
2163

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

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

2183
2184
    /* Defeat a VTE bug by moving the cursor and forcing a screen update. */
    wmove(bottomwin, 0, 0);
2185
    wnoutrefresh(bottomwin);
2186
2187
    doupdate();

2188
    reset_cursor();
2189
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
2190
2191
}

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

2201
    wattron(bottomwin, interface_color_pair[KEY_COMBO]);
2202
    waddnstr(bottomwin, keystroke, actual_x(keystroke, length));
2203
    wattroff(bottomwin, interface_color_pair[KEY_COMBO]);
2204

2205
    length -= strlenpt(keystroke) + 1;
2206

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

2215
2216
/* 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
2217
2218
void reset_cursor(void)
{
2219
    size_t xpt = xplustabs();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2220

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

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

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

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

2245
2246
2247
2248
2249
2250
2251
2252
/* 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. */
2253
void edit_draw(filestruct *fileptr, const char *converted, int
2254
	line, size_t start)
Chris Allegretta's avatar
Chris Allegretta committed
2255
{
2256
#if !defined(NANO_TINY) || !defined(DISABLE_COLOR)
2257
2258
2259
    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. */
2260
    size_t endpos = actual_x(fileptr->data, start + editwincols - 1) + 1;
2261
2262
2263
2264
2265
	/* 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. */
2266
2267
#endif

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

#ifdef ENABLE_LINENUMBERS
2272
2273
2274
    int needed_margin = digits(openfile->filebot->lineno) + 1;

    if (ISSET(LINE_NUMBERS) && needed_margin < COLS - 3) {
2275
	/* If the line numbers now require more room, schedule a refresh. */
2276
2277
	if (needed_margin != margin) {
	    margin = needed_margin;
2278
2279
2280
2281
2282
	    editwincols = COLS - margin;
	    refresh_needed = TRUE;
	}

	/* Show the line number only for the non-softwrapped parts. */
2283
	wattron(edit, interface_color_pair[LINE_NUMBER]);
2284
2285
2286
2287
	if (last_drawn_line != fileptr->lineno || last_line_y >= line)
	    mvwprintw(edit, line, 0, "%*i", margin - 1, fileptr->lineno);
	else
	    mvwprintw(edit, line, 0, "%*s", margin - 1, " ");
2288
	wattroff(edit, interface_color_pair[LINE_NUMBER]);
2289
2290
2291
2292
2293
    } else {
	margin = 0;
	editwincols = COLS;
    }
#endif
2294

2295
2296
    /* First simply paint the line -- then we'll add colors or the
     * marking highlight on just the pieces that need it. */
2297
    mvwaddstr(edit, line, margin, converted);
2298

2299
#ifdef USING_OLD_NCURSES
2300
2301
2302
    /* 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. */
2303
2304
    if (seen_wide)
	wredrawln(edit, line, 1);
2305
#endif
2306

2307
#ifndef DISABLE_COLOR
2308
2309
2310
    /* If color syntaxes are available and turned on, we need to display
     * them. */
    if (openfile->colorstrings != NULL && !ISSET(NO_COLOR_SYNTAX)) {
2311
	const colortype *varnish = openfile->colorstrings;
2312

2313
2314
2315
2316
	/* If there are multiline regexes, make sure there is a cache. */
	if (openfile->syntax->nmultis > 0)
	    alloc_multidata_if_needed(fileptr);

2317
	for (; varnish != NULL; varnish = varnish->next) {
2318
2319
	    int x_start;
		/* Starting column for mvwaddnstr.  Zero-based. */
2320
	    int paintlen = 0;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2321
2322
		/* Number of chars to paint on this line.  There are
		 * COLS characters on a whole line. */
2323
	    size_t index;
2324
		/* Index in converted where we paint. */
2325
2326
2327
2328
	    regmatch_t startmatch;
		/* Match position for start_regex. */
	    regmatch_t endmatch;
		/* Match position for end_regex. */
2329

2330
	    wattron(edit, varnish->attributes);
2331
2332
2333
	    /* 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. */
2334

2335
2336
	    /* First case: varnish is a single-line expression. */
	    if (varnish->end == NULL) {
2337
2338
2339
		size_t k = 0;

		/* We increment k by rm_eo, to move past the end of the
2340
		 * last match.  Even though two matches may overlap, we
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2341
2342
		 * want to ignore them, so that we can highlight e.g. C
		 * strings correctly. */
2343
2344
2345
		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
2346
2347
2348
		     * unless k is zero.  If regexec() returns
		     * REG_NOMATCH, there are no more matches in the
		     * line. */
2349
		    if (regexec(varnish->start, &fileptr->data[k], 1,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2350
2351
			&startmatch, (k == 0) ? 0 : REG_NOTBOL) ==
			REG_NOMATCH)
2352
			break;
2353
2354
		    /* Translate the match to the beginning of the
		     * line. */
2355
2356
		    startmatch.rm_so += k;
		    startmatch.rm_eo += k;
2357
2358
2359

		    /* Skip over a zero-length regex match. */
		    if (startmatch.rm_so == startmatch.rm_eo)
2360
			startmatch.rm_eo++;
2361
		    else if (startmatch.rm_so < endpos &&
2362
			startmatch.rm_eo > startpos) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2363
2364
			x_start = (startmatch.rm_so <= startpos) ? 0 :
				strnlenpt(fileptr->data,
2365
				startmatch.rm_so) - start;
2366

2367
2368
2369
			index = actual_x(converted, x_start);

			paintlen = actual_x(converted + index,
2370
2371
				strnlenpt(fileptr->data,
				startmatch.rm_eo) - start - x_start);
2372
2373
2374

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

2375
			mvwaddnstr(edit, line, x_start + margin, converted +
2376
				index, paintlen);
2377
		    }
2378
		    k = startmatch.rm_eo;
Chris Allegretta's avatar
Chris Allegretta committed
2379
		}
2380
	    } else {	/* Second case: varnish is a multiline expression. */
2381
		const filestruct *start_line = fileptr->prev;
2382
		    /* The first line before fileptr that matches 'start'. */
2383
		size_t start_col;
2384
		    /* Where the match starts in that line. */
2385
		const filestruct *end_line;
2386
		    /* The line that matches 'end'. */
2387

2388
		/* First see if the multidata was maybe already calculated. */
2389
		if (fileptr->multidata[varnish->id] == CNONE)
2390
		    goto tail_of_loop;
2391
		else if (fileptr->multidata[varnish->id] == CWHOLELINE) {
2392
		    mvwaddnstr(edit, line, margin, converted, -1);
2393
		    goto tail_of_loop;
2394
2395
		} else if (fileptr->multidata[varnish->id] == CBEGINBEFORE) {
		    regexec(varnish->end, fileptr->data, 1, &endmatch, 0);
2396
2397
		    /* If the coloured part is scrolled off, skip it. */
		    if (endmatch.rm_eo <= startpos)
2398
			goto tail_of_loop;
2399
2400
		    paintlen = actual_x(converted, strnlenpt(fileptr->data,
			endmatch.rm_eo) - start);
2401
		    mvwaddnstr(edit, line, margin, converted, paintlen);
2402
		    goto tail_of_loop;
2403
		} if (fileptr->multidata[varnish->id] == -1)
2404
		    /* Assume this until proven otherwise below. */
2405
		    fileptr->multidata[varnish->id] = CNONE;
2406

2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
		/* 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. */

2417
		while (start_line != NULL && regexec(varnish->start,
2418
2419
2420
			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. */
2421
		    if (regexec(varnish->end, start_line->data, 0, NULL, 0) == 0)
2422
2423
2424
			goto step_two;
		    start_line = start_line->prev;
		}
2425

2426
2427
2428
2429
		/* If no start was found, skip to the next step. */
		if (start_line == NULL)
		    goto step_two;

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

2437
		/* Skip over a zero-length regex match. */
2438
		if (startmatch.rm_so == startmatch.rm_eo)
2439
2440
		    goto tail_of_loop;

2441
2442
2443
2444
2445
2446
2447
		/* 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;
2448
		    if (regexec(varnish->end, start_line->data +
2449
2450
2451
				start_col + startmatch.rm_eo, 0, NULL,
				(start_col + startmatch.rm_eo == 0) ?
				0 : REG_NOTBOL) == REG_NOMATCH)
2452
2453
2454
			/* No end found after this start. */
			break;
		    start_col++;
2455
2456
		    if (regexec(varnish->start, start_line->data + start_col,
				1, &startmatch, REG_NOTBOL) == REG_NOMATCH)
2457
2458
2459
2460
2461
2462
2463
2464
2465
			/* 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;
2466
		while (end_line != NULL && regexec(varnish->end,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2467
			end_line->data, 1, &endmatch, 0) == REG_NOMATCH)
2468
		    end_line = end_line->next;
2469

2470
2471
2472
2473
		/* 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) {
2474
		    fileptr->multidata[varnish->id] = CBEGINBEFORE;
2475
2476
		    goto step_two;
		}
2477

2478
2479
2480
2481
2482
2483
2484
		/* 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;
2485
		    fileptr->multidata[varnish->id] = CWHOLELINE;
2486
#ifdef DEBUG
2487
    fprintf(stderr, "  Marking for id %i  line %i as CWHOLELINE\n", varnish->id, line);
2488
#endif
2489
2490
2491
		} else {
		    paintlen = actual_x(converted, strnlenpt(fileptr->data,
						endmatch.rm_eo) - start);
2492
		    fileptr->multidata[varnish->id] = CBEGINBEFORE;
2493
#ifdef DEBUG
2494
    fprintf(stderr, "  Marking for id %i  line %i as CBEGINBEFORE\n", varnish->id, line);
2495
#endif
2496
		}
2497
		mvwaddnstr(edit, line, margin, converted, paintlen);
2498
2499
2500
2501
		/* If the whole line has been painted, don't bother looking
		 * for any more starts. */
		if (paintlen < 0)
		    goto tail_of_loop;
2502
  step_two:
2503
2504
2505
2506
2507
		/* 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) {
2508
		    if (regexec(varnish->start, fileptr->data + start_col,
2509
2510
				1, &startmatch, (start_col == 0) ?
				0 : REG_NOTBOL) == REG_NOMATCH ||
2511
				start_col + startmatch.rm_so >= endpos)
2512
2513
			/* No more starts on this line. */
			break;
2514

2515
2516
2517
2518
2519
2520
2521
		    /* 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,
2522
				startmatch.rm_so) - start;
2523

2524
		    index = actual_x(converted, x_start);
2525

2526
		    if (regexec(varnish->end, fileptr->data +
2527
				startmatch.rm_eo, 1, &endmatch,
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
				(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 &&
2538
				endmatch.rm_eo > startmatch.rm_so) {
2539
			    paintlen = actual_x(converted + index,
2540
					strnlenpt(fileptr->data,
2541
					endmatch.rm_eo) - start - x_start);
2542

2543
			    assert(0 <= x_start && x_start < editwincols);
2544

2545
			    mvwaddnstr(edit, line, x_start + margin,
2546
					converted + index, paintlen);
2547
			    if (paintlen > 0) {
2548
				fileptr->multidata[varnish->id] = CSTARTENDHERE;
2549
#ifdef DEBUG
2550
    fprintf(stderr, "  Marking for id %i  line %i as CSTARTENDHERE\n", varnish->id, line);
2551
#endif
2552
			    }
2553
2554
			}
			start_col = endmatch.rm_eo;
2555
2556
2557
			/* Skip over a zero-length match. */
			if (endmatch.rm_so == endmatch.rm_eo)
			    start_col += 1;
2558
2559
2560
2561
		    } else {
			/* There is no end on this line.  But we haven't yet
			 * looked for one on later lines. */
			end_line = fileptr->next;
2562

2563
			while (end_line != NULL &&
2564
				regexec(varnish->end, end_line->data,
2565
				0, NULL, 0) == REG_NOMATCH)
2566
			    end_line = end_line->next;
2567

2568
2569
2570
			/* If there is no end, we're done on this line. */
			if (end_line == NULL)
			    break;
2571

2572
			assert(0 <= x_start && x_start < editwincols);
2573
2574

			/* Paint the rest of the line. */
2575
			mvwaddnstr(edit, line, x_start + margin, converted + index, -1);
2576
			fileptr->multidata[varnish->id] = CENDAFTER;
2577
#ifdef DEBUG
2578
    fprintf(stderr, "  Marking for id %i  line %i as CENDAFTER\n", varnish->id, line);
2579
#endif
2580
2581
2582
			/* We've painted to the end of the line, so don't
			 * bother checking for any more starts. */
			break;
2583
		    }
2584
2585
		}
	    }
2586
  tail_of_loop:
2587
	    wattroff(edit, varnish->attributes);
2588
	}
2589
    }
2590
#endif /* !DISABLE_COLOR */
2591

2592
#ifndef NANO_TINY
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
    /* 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. */
2604
	int x_start;
2605
	    /* The starting column for mvwaddnstr().  Zero-based. */
2606
	int paintlen;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2607
2608
	    /* Number of characters to paint on this line.  There are
	     * COLS characters on a whole line. */
2609
	size_t index;
2610
	    /* Index in converted where we paint. */
2611

2612
	mark_order(&top, &top_x, &bot, &bot_x, NULL);
2613
2614
2615
2616
2617

	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
2618

2619
	/* Only paint if the marked bit of fileptr is on this page. */
2620
2621
	if (top_x < endpos && bot_x > startpos) {
	    assert(startpos <= top_x);
2622
2623
2624
2625

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

2627
2628
2629
2630
2631
	    /* 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. */
2632
2633
2634
	    if (bot_x >= endpos)
		paintlen = -1;
	    else
2635
		paintlen = strnlenpt(fileptr->data, bot_x) - (x_start + start);
2636
2637
2638
2639
2640
2641
2642
2643

	    /* 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;
	    }
2644
2645
2646

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

2647
	    index = actual_x(converted, x_start);
2648

2649
2650
2651
	    if (paintlen > 0)
		paintlen = actual_x(converted + index, paintlen);

2652
	    wattron(edit, hilite_attribute);
2653
	    mvwaddnstr(edit, line, x_start + margin, converted + index, paintlen);
2654
	    wattroff(edit, hilite_attribute);
Chris Allegretta's avatar
Chris Allegretta committed
2655
	}
2656
    }
2657
#endif /* !NANO_TINY */
2658
2659
2660
2661
#ifdef ENABLE_LINENUMBERS
    last_drawn_line = fileptr->lineno;
    last_line_y = line;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2662
2663
}

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

2678
    assert(fileptr != NULL);
2679

2680
#ifndef NANO_TINY
2681
    if (ISSET(SOFTWRAP)) {
2682
2683
	filestruct *tmp;

2684
	for (tmp = openfile->edittop; tmp && tmp != fileptr; tmp = tmp->next)
2685
	    line += (strlenpt(tmp->data) / editwincols) + 1;
2686
    } else
2687
#endif
2688
2689
	line = fileptr->lineno - openfile->edittop->lineno;

2690
    if (line < 0 || line >= editwinrows)
Chris Allegretta's avatar
Chris Allegretta committed
2691
	return 1;
2692

2693
    /* First, blank out the line. */
2694
    blank_line(edit, line, 0, COLS);
2695

2696
2697
    /* Next, convert variables that index the line to their equivalent
     * positions in the expanded line. */
2698
#ifndef NANO_TINY
2699
2700
2701
    if (ISSET(SOFTWRAP))
	index = 0;
    else
2702
#endif
2703
	index = strnlenpt(fileptr->data, index);
2704
    page_start = get_page_start(index);
2705

2706
2707
    /* Expand the line, replacing tabs with spaces, and control
     * characters with their displayed forms. */
2708
#ifdef NANO_TINY
2709
    converted = display_string(fileptr->data, page_start, editwincols, TRUE);
2710
#else
2711
    converted = display_string(fileptr->data, page_start, editwincols, !ISSET(SOFTWRAP));
2712
#ifdef DEBUG
2713
    if (ISSET(SOFTWRAP) && strlen(converted) >= editwincols - 2)
2714
	fprintf(stderr, "update_line(): converted(1) line = %s\n", converted);
2715
#endif
2716
#endif /* !NANO_TINY */
2717

2718
    /* Paint the line. */
2719
    edit_draw(fileptr, converted, line, page_start);
2720
    free(converted);
Chris Allegretta's avatar
Chris Allegretta committed
2721

2722
#ifndef NANO_TINY
2723
    if (!ISSET(SOFTWRAP)) {
2724
#endif
2725
	if (page_start > 0)
2726
2727
	    mvwaddch(edit, line, margin, '$');
	if (strlenpt(fileptr->data) > page_start + editwincols)
2728
	    mvwaddch(edit, line, COLS - 1, '$');
2729
#ifndef NANO_TINY
2730
    } else {
2731
	size_t full_length = strlenpt(fileptr->data);
2732
	for (index += editwincols; index <= full_length && line < editwinrows - 1; index += editwincols) {
2733
2734
	    line++;
#ifdef DEBUG
2735
	    fprintf(stderr, "update_line(): softwrap code, moving to %d index %lu\n", line, (unsigned long)index);
2736
#endif
2737
	    blank_line(edit, line, 0, COLS);
2738
2739

	    /* Expand the line, replacing tabs with spaces, and control
2740
	     * characters with their displayed forms. */
2741
	    converted = display_string(fileptr->data, index, editwincols, !ISSET(SOFTWRAP));
2742
#ifdef DEBUG
2743
	    if (ISSET(SOFTWRAP) && strlen(converted) >= editwincols - 2)
2744
2745
		fprintf(stderr, "update_line(): converted(2) line = %s\n", converted);
#endif
2746
2747
2748

	    /* Paint the line. */
	    edit_draw(fileptr, converted, line, index);
2749
	    free(converted);
2750
2751
2752
	    extralinesused++;
	}
    }
2753
#endif /* !NANO_TINY */
2754
    return extralinesused;
Chris Allegretta's avatar
Chris Allegretta committed
2755
2756
}

2757
2758
2759
/* 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)
2760
{
2761
#ifndef NANO_TINY
2762
2763
2764
    if (openfile->mark_set)
	return TRUE;
    else
2765
#endif
2766
	return (get_page_start(old_column) != get_page_start(new_column));
2767
2768
}

2769
/* When edittop changes, try and figure out how many lines
2770
 * we really have to work with (i.e. set maxrows). */
2771
2772
2773
2774
2775
void compute_maxrows(void)
{
    int n;
    filestruct *foo = openfile->edittop;

2776
2777
2778
2779
2780
    if (!ISSET(SOFTWRAP)) {
	maxrows = editwinrows;
	return;
    }

2781
2782
    maxrows = 0;
    for (n = 0; n < editwinrows && foo; n++) {
2783
	maxrows++;
2784
	n += strlenpt(foo->data) / editwincols;
2785
2786
2787
	foo = foo->next;
    }

2788
2789
2790
    if (n < editwinrows)
	maxrows += editwinrows - n;

2791
#ifdef DEBUG
2792
    fprintf(stderr, "compute_maxrows(): maxrows = %d\n", maxrows);
2793
2794
2795
#endif
}

2796
2797
/* 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
2798
2799
 * scrolling.  direction is the direction to scroll, either UPWARD or
 * DOWNWARD, and nlines is the number of lines to scroll.  We change
2800
2801
 * edittop, and assume that current and current_x are up to date.  We
 * also assume that scrollok(edit) is FALSE. */
2802
void edit_scroll(scroll_dir direction, ssize_t nlines)
2803
{
2804
    ssize_t i;
2805
    filestruct *foo;
2806

2807
    assert(nlines > 0);
2808

2809
2810
2811
    /* Part 1: nlines is the number of lines we're going to scroll the
     * text of the edit window. */

2812
    /* Move the top line of the edit window up or down (depending on the
2813
2814
     * value of direction) nlines lines, or as many lines as we can if
     * there are fewer than nlines lines available. */
2815
    for (i = nlines; i > 0; i--) {
2816
	if (direction == UPWARD) {
2817
	    if (openfile->edittop == openfile->fileage)
2818
		break;
2819
	    openfile->edittop = openfile->edittop->prev;
2820
	} else {
2821
	    if (openfile->edittop == openfile->filebot)
2822
		break;
2823
	    openfile->edittop = openfile->edittop->next;
2824
	}
2825
2826

#ifndef NANO_TINY
2827
	/* Don't over-scroll on long lines. */
2828
	if (ISSET(SOFTWRAP) && direction == UPWARD) {
2829
	    ssize_t len = strlenpt(openfile->edittop->data) / editwincols;
2830
	    i -= len;
2831
	    if (len > 0)
2832
		refresh_needed = TRUE;
2833
	}
2834
#endif
2835
2836
    }

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

2840
2841
2842
2843
    /* Don't bother scrolling zero lines, nor more than the window can hold. */
    if (nlines == 0)
	return;
    if (nlines >= editwinrows)
2844
	refresh_needed = TRUE;
2845

2846
    if (refresh_needed == TRUE)
2847
	return;
2848
2849
2850

    /* Scroll the text of the edit window up or down nlines lines,
     * depending on the value of direction. */
2851
    scrollok(edit, TRUE);
2852
    wscrl(edit, (direction == UPWARD) ? -nlines : nlines);
2853
2854
    scrollok(edit, FALSE);

2855
2856
2857
    /* Part 2: nlines is the number of lines in the scrolled region of
     * the edit window that we need to draw. */

2858
2859
2860
2861
2862
2863
    /* 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
2864

2865
2866
    if (nlines > editwinrows)
	nlines = editwinrows;
2867
2868
2869

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

2872
2873
    /* If we scrolled down, move down to the line before the scrolled
     * region. */
2874
    if (direction == DOWNWARD) {
2875
	for (i = editwinrows - nlines; i > 0 && foo != NULL; i--)
2876
2877
2878
	    foo = foo->next;
    }

2879
2880
2881
2882
2883
2884
    /* 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--) {
2885
2886
	if ((i == nlines && direction == DOWNWARD) || (i == 1 &&
		direction == UPWARD)) {
2887
	    if (need_horizontal_scroll(openfile->placewewant, 0))
2888
2889
2890
2891
		update_line(foo, (foo == openfile->current) ?
			openfile->current_x : 0);
	} else
	    update_line(foo, (foo == openfile->current) ?
2892
		openfile->current_x : 0);
2893
	foo = foo->next;
2894
    }
2895
    compute_maxrows();
2896
2897
2898
}

/* Update any lines between old_current and current that need to be
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2899
 * updated.  Use this if we've moved without changing any text. */
2900
void edit_redraw(filestruct *old_current)
2901
{
2902
2903
2904
2905
    size_t was_pww = openfile->placewewant;

    openfile->placewewant = xplustabs();

2906
2907
    /* If the current line is offscreen, scroll until it's onscreen. */
    if (openfile->current->lineno >= openfile->edittop->lineno + maxrows ||
2908
2909
#ifndef NANO_TINY
		(openfile->current->lineno == openfile->edittop->lineno + maxrows - 1 &&
2910
		ISSET(SOFTWRAP) && strlenpt(openfile->current->data) >= editwincols) ||
2911
#endif
2912
		openfile->current->lineno < openfile->edittop->lineno) {
2913
	edit_update((focusing || !ISSET(SMOOTH_SCROLL)) ? CENTERING : FLOWING);
2914
	refresh_needed = TRUE;
2915
    }
2916

2917
#ifndef NANO_TINY
2918
2919
2920
    /* If the mark is on, update all lines between old_current and current. */
    if (openfile->mark_set) {
	filestruct *foo = old_current;
2921

2922
	while (foo != openfile->current) {
2923
	    update_line(foo, 0);
2924

2925
2926
2927
	    foo = (foo->lineno > openfile->current->lineno) ?
			foo->prev : foo->next;
	}
2928
2929
2930
2931
2932
2933
    } 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);
2934
2935
2936

    /* Update current if we've changed page, or if it differs from
     * old_current and needs to be horizontally scrolled. */
2937
    if (need_horizontal_scroll(was_pww, openfile->placewewant) ||
2938
			(old_current != openfile->current &&
2939
			get_page_start(openfile->placewewant) > 0))
2940
	update_line(openfile->current, openfile->current_x);
2941
2942
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2943
2944
/* 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
2945
2946
void edit_refresh(void)
{
2947
    filestruct *foo;
2948
    int nlines;
2949

2950
    /* Figure out what maxrows should really be. */
2951
    compute_maxrows();
2952

2953
2954
    if (openfile->current->lineno < openfile->edittop->lineno ||
	openfile->current->lineno >= openfile->edittop->lineno +
2955
2956
	maxrows) {
#ifdef DEBUG
2957
2958
	fprintf(stderr, "edit_refresh(): line = %ld, edittop %ld + maxrows %d\n",
		(long)openfile->current->lineno, (long)openfile->edittop->lineno, maxrows);
2959
2960
#endif

2961
	/* Make sure the current line is on the screen. */
2962
	edit_update((focusing || !ISSET(SMOOTH_SCROLL)) ? CENTERING : STATIONARY);
2963
    }
Chris Allegretta's avatar
Chris Allegretta committed
2964

2965
2966
    foo = openfile->edittop;

2967
#ifdef DEBUG
2968
    fprintf(stderr, "edit_refresh(): edittop->lineno = %ld\n", (long)openfile->edittop->lineno);
2969
#endif
2970

2971
    for (nlines = 0; nlines < editwinrows && foo != NULL; nlines++) {
2972
	nlines += update_line(foo, (foo == openfile->current) ?
2973
		openfile->current_x : 0);
2974
2975
2976
	foo = foo->next;
    }

2977
    for (; nlines < editwinrows; nlines++)
2978
2979
2980
	blank_line(edit, nlines, 0, COLS);

    reset_cursor();
2981
    curs_set(1);
2982
    wnoutrefresh(edit);
2983
2984

    refresh_needed = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
2985
2986
}

2987
2988
2989
2990
2991
2992
/* 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
2993
{
2994
    int goal = 0;
2995

2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
    /* 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)
3006
	goal = editwinrows / 2;
3007
    else if (manner == FLOWING) {
3008
	if (openfile->current->lineno >= openfile->edittop->lineno) {
3009
	    goal = editwinrows - 1;
3010
3011
#ifndef NANO_TINY
	    if (ISSET(SOFTWRAP))
3012
		goal -= strlenpt(openfile->current->data) / editwincols;
3013
3014
#endif
	}
3015
    } else {
3016
	goal = openfile->current_y;
3017

3018
	/* Limit goal to (editwinrows - 1) lines maximum. */
3019
3020
	if (goal > editwinrows - 1)
	    goal = editwinrows - 1;
Chris Allegretta's avatar
Chris Allegretta committed
3021
    }
3022

3023
3024
3025
3026
3027
    openfile->edittop = openfile->current;

    while (goal > 0 && openfile->edittop->prev != NULL) {
	openfile->edittop = openfile->edittop->prev;
	goal --;
3028
#ifndef NANO_TINY
3029
	if (ISSET(SOFTWRAP)) {
3030
	    goal -= strlenpt(openfile->edittop->data) / editwincols;
3031
3032
3033
	    if (goal < 0)
		openfile->edittop = openfile->edittop->next;
	}
3034
#endif
3035
    }
3036
#ifdef DEBUG
3037
    fprintf(stderr, "edit_update(): setting edittop to lineno %ld\n", (long)openfile->edittop->lineno);
3038
#endif
3039
    compute_maxrows();
Chris Allegretta's avatar
Chris Allegretta committed
3040
3041
}

3042
/* Unconditionally redraw the entire screen. */
3043
void total_redraw(void)
3044
{
3045
3046
3047
3048
3049
3050
#ifdef USE_SLANG
    /* Slang curses emulation brain damage, part 4: Slang doesn't define
     * curscr. */
    SLsmg_touch_screen();
    SLsmg_refresh();
#else
3051
    wrefresh(curscr);
3052
#endif
3053
3054
}

3055
3056
/* Unconditionally redraw the entire screen, and then refresh it using
 * the current file. */
3057
3058
void total_refresh(void)
{
3059
    total_redraw();
3060
    titlebar(NULL);
3061
    edit_refresh();
3062
    bottombars(currmenu);
3063
3064
}

3065
3066
/* Display the main shortcut list on the last two rows of the bottom
 * portion of the window. */
3067
3068
void display_main_list(void)
{
3069
#ifndef DISABLE_COLOR
3070
3071
    if (openfile->syntax &&
		(openfile->syntax->formatter || openfile->syntax->linter))
3072
	set_lint_or_format_shortcuts();
3073
3074
3075
3076
    else
	set_spell_shortcuts();
#endif

3077
    bottombars(MMAIN);
3078
3079
}

3080
/* If constant is TRUE, we display the current cursor position only if
3081
3082
 * suppress_cursorpos is FALSE.  If constant is FALSE, we display the
 * position always.  In any case we reset suppress_cursorpos to FALSE. */
3083
void do_cursorpos(bool constant)
Chris Allegretta's avatar
Chris Allegretta committed
3084
{
3085
    filestruct *f;
3086
    char c;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3087
    size_t i, cur_xpt = xplustabs() + 1;
3088
    size_t cur_lenpt = strlenpt(openfile->current->data) + 1;
3089
    int linepct, colpct, charpct;
Chris Allegretta's avatar
Chris Allegretta committed
3090

3091
    assert(openfile->fileage != NULL && openfile->current != NULL);
3092

3093
    /* Determine the size of the file up to the cursor. */
3094
    f = openfile->current->next;
3095
    c = openfile->current->data[openfile->current_x];
3096
3097

    openfile->current->next = NULL;
3098
    openfile->current->data[openfile->current_x] = '\0';
3099
3100
3101

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

3102
    openfile->current->data[openfile->current_x] = c;
3103
    openfile->current->next = f;
3104

3105
    /* If the position needs to be suppressed, don't suppress it next time. */
3106
    if (suppress_cursorpos && constant) {
3107
	suppress_cursorpos = FALSE;
3108
	return;
3109
    }
Chris Allegretta's avatar
Chris Allegretta committed
3110

3111
    /* Display the current cursor position on the statusbar. */
3112
    linepct = 100 * openfile->current->lineno / openfile->filebot->lineno;
3113
    colpct = 100 * cur_xpt / cur_lenpt;
3114
    charpct = (openfile->totsize == 0) ? 0 : 100 * i / openfile->totsize;
3115

3116
    statusline(HUSH,
3117
	_("line %ld/%ld (%d%%), col %lu/%lu (%d%%), char %lu/%lu (%d%%)"),
3118
	(long)openfile->current->lineno,
3119
	(long)openfile->filebot->lineno, linepct,
3120
	(unsigned long)cur_xpt, (unsigned long)cur_lenpt, colpct,
3121
	(unsigned long)i, (unsigned long)openfile->totsize, charpct);
3122
3123
3124

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

3127
/* Unconditionally display the current cursor position. */
3128
void do_cursorpos_void(void)
3129
{
3130
    do_cursorpos(FALSE);
3131
3132
}

3133
3134
void enable_nodelay(void)
{
3135
3136
    nodelay_mode = TRUE;
    nodelay(edit, TRUE);
3137
3138
3139
3140
}

void disable_nodelay(void)
{
3141
3142
    nodelay_mode = FALSE;
    nodelay(edit, FALSE);
3143
3144
}

3145
3146
/* Highlight the current word being replaced or spell checked.  We
 * expect word to have tabs and control characters expanded. */
3147
void spotlight(bool active, const char *word)
Chris Allegretta's avatar
Chris Allegretta committed
3148
{
3149
    size_t word_len = strlenpt(word), room;
Chris Allegretta's avatar
Chris Allegretta committed
3150

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

3154
    assert(room > 0);
3155

3156
3157
    if (word_len > room)
	room--;
3158

Chris Allegretta's avatar
Chris Allegretta committed
3159
    reset_cursor();
3160
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
3161

3162
    if (active)
3163
	wattron(edit, hilite_attribute);
Chris Allegretta's avatar
Chris Allegretta committed
3164

3165
    /* This is so we can show zero-length matches. */
3166
    if (word_len == 0)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3167
	waddch(edit, ' ');
3168
    else
3169
	waddnstr(edit, word, actual_x(word, room));
3170

3171
    if (word_len > room)
3172
	waddch(edit, '$');
Chris Allegretta's avatar
Chris Allegretta committed
3173

3174
    if (active)
3175
	wattroff(edit, hilite_attribute);
Chris Allegretta's avatar
Chris Allegretta committed
3176
3177
}

3178
#ifndef DISABLE_EXTRA
3179
3180
#define CREDIT_LEN 54
#define XLCREDIT_LEN 9
3181

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

3246
    const char *xlcredits[XLCREDIT_LEN] = {
3247
3248
3249
3250
3251
	N_("The nano text editor"),
	N_("version"),
	N_("Brought to you by:"),
	N_("Special thanks to:"),
	N_("The Free Software Foundation"),
3252
	N_("the many translators and the TP"),
3253
3254
3255
	N_("For ncurses:"),
	N_("and anyone else we forgot..."),
	N_("Thank you for using nano!")
3256
    };
3257

3258
3259
3260
3261
3262
3263
    if (!old_more_space || !old_no_help) {
	SET(MORE_SPACE);
	SET(NO_HELP);
	window_init();
    }

3264
3265
    curs_set(0);
    nodelay(edit, TRUE);
3266

3267
    blank_titlebar();
Chris Allegretta's avatar
Chris Allegretta committed
3268
    blank_edit();
3269
    blank_statusbar();
3270

3271
    wrefresh(topwin);
Chris Allegretta's avatar
Chris Allegretta committed
3272
    wrefresh(edit);
3273
    wrefresh(bottomwin);
3274
    napms(700);
3275

3276
    for (crpos = 0; crpos < CREDIT_LEN + editwinrows / 2; crpos++) {
3277
	if ((kbinput = wgetch(edit)) != ERR)
3278
	    break;
3279

3280
	if (crpos < CREDIT_LEN) {
3281
	    const char *what;
3282
3283
	    size_t start_x;

3284
	    if (credits[crpos] == NULL) {
3285
		assert(0 <= xlpos && xlpos < XLCREDIT_LEN);
3286

3287
		what = _(xlcredits[xlpos]);
3288
		xlpos++;
3289
	    } else
3290
		what = credits[crpos];
3291

3292
	    start_x = COLS / 2 - strlenpt(what) / 2 - 1;
3293
3294
	    mvwaddstr(edit, editwinrows - 1 - (editwinrows % 2),
		start_x, what);
3295
	}
3296

3297
3298
3299
3300
	wrefresh(edit);

	if ((kbinput = wgetch(edit)) != ERR)
	    break;
3301
	napms(700);
3302

3303
	scrollok(edit, TRUE);
3304
	wscrl(edit, 1);
3305
	scrollok(edit, FALSE);
3306
	wrefresh(edit);
3307

3308
	if ((kbinput = wgetch(edit)) != ERR)
3309
	    break;
3310
	napms(700);
3311

3312
	scrollok(edit, TRUE);
3313
	wscrl(edit, 1);
3314
	scrollok(edit, FALSE);
3315
	wrefresh(edit);
3316
3317
    }

3318
3319
3320
    if (kbinput != ERR)
	ungetch(kbinput);

3321
    if (!old_more_space)
3322
	UNSET(MORE_SPACE);
3323
    if (!old_no_help)
3324
	UNSET(NO_HELP);
3325
    window_init();
3326

3327
    nodelay(edit, FALSE);
3328

3329
    total_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
3330
}
3331
#endif /* !DISABLE_EXTRA */