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

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

26
27
#include <sys/ioctl.h>

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

34
35
36
37
38
39
#ifdef REVISION
#define BRANDING REVISION
#else
#define BRANDING PACKAGE_STRING
#endif

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

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

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

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

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

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

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

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

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

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

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

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

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

    while (TRUE) {
188
	input = wgetch(win);
189

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

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

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

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

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

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

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

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

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

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

253
254
255
/* 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)
256
{
257
    unget_input(&kbinput, 1);
258

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

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

273
274
    if (key_buffer_len == 0 && win != NULL)
	get_key_buffer(win);
275

276
277
    if (key_buffer_len == 0)
	return NULL;
278

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

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

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

    return input;
301
302
}

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

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

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

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

321
322
323
    return kbinput;
}

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

336
    meta_key = FALSE;
337

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

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

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

347
348
349
    keycode = *kbinput;
    free(kbinput);

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

355
356
357
    if (keycode == ERR)
	return ERR;

358
    if (keycode == ESC_CODE) {
359
360
361
362
363
364
365
	/* 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;
366
367
    }

368
369
370
371
372
373
    switch (escapes) {
	case 0:
	    /* One non-escape: normal input mode. */
	    retval = keycode;
	    break;
	case 1:
374
375
376
	    if (keycode >= 0x80)
		retval = keycode;
	    else if ((keycode != 'O' && keycode != 'o' && keycode != '[') ||
377
			key_buffer_len == 0 || *key_buffer == ESC_CODE) {
378
379
380
381
382
383
384
385
386
		/* 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);
387
	    escapes = 0;
388
389
390
391
392
393
394
395
396
397
398
	    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;
399
#ifndef NANO_TINY
400
401
402
403
404
405
		    case 'C':
			retval = controlright;
			break;
		    case 'D':
			retval = controlleft;
			break;
406
#endif
407
408
409
		}
		double_esc = FALSE;
		escapes = 0;
410
	    } else if (key_buffer_len == 0) {
411
412
413
414
415
416
417
418
419
420
421
422
423
424
		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);

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

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

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

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

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

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

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

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

496
497
498
499
500
501
502
503
504
505
506
#ifndef NANO_TINY
    if (retval == controlleft)
	return sc_seq_or(do_prev_word_void, 0);
    else if (retval == controlright)
	return sc_seq_or(do_next_word_void, 0);
    else if (retval == controlup)
	return sc_seq_or(do_prev_block, 0);
    else if (retval == controldown)
	return sc_seq_or(do_next_block, 0);
#endif

507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
    /* When not running under X, check for the bare arrow keys whether
     * the Ctrl key is being held together with them. */
    if (console && (retval == KEY_UP || retval == KEY_DOWN ||
			retval == KEY_LEFT || retval == KEY_RIGHT)) {
	unsigned char modifiers = 6;

	if (ioctl(0, TIOCLINUX, &modifiers) >= 0 && (modifiers & 0x04)) {
	    if (retval == KEY_UP)
		return sc_seq_or(do_prev_block, 0);
	    else if (retval == KEY_DOWN)
		return sc_seq_or(do_next_block, 0);
	    else if (retval == KEY_LEFT)
		return sc_seq_or(do_prev_word_void, 0);
	    else
		return sc_seq_or(do_next_word_void, 0);
	}
    }

525
    switch (retval) {
526
#ifdef KEY_SLEFT
527
528
529
	/* Slang doesn't support KEY_SLEFT. */
	case KEY_SLEFT:
	    return sc_seq_or(do_left, keycode);
530
#endif
531
#ifdef KEY_SRIGHT
532
533
534
	/* Slang doesn't support KEY_SRIGHT. */
	case KEY_SRIGHT:
	    return sc_seq_or(do_right, keycode);
535
#endif
536
#ifdef KEY_SUP
537
538
539
	/* ncurses and Slang don't support KEY_SUP. */
	case KEY_SUP:
	    return sc_seq_or(do_up_void, keycode);
540
#endif
541
#ifdef KEY_SDOWN
542
543
544
	/* ncurses and Slang don't support KEY_SDOWN. */
	case KEY_SDOWN:
	    return sc_seq_or(do_down_void, keycode);
545
#endif
546
#ifdef KEY_SHOME
547
548
	/* HP-UX 10-11 and Slang don't support KEY_SHOME. */
	case KEY_SHOME:
549
#endif
550
551
	case KEY_A1:	/* Home (7) on keypad with NumLock off. */
	    return sc_seq_or(do_home, keycode);
552
#ifdef KEY_SEND
553
554
	/* HP-UX 10-11 and Slang don't support KEY_SEND. */
	case KEY_SEND:
555
#endif
556
557
558
559
560
561
	case KEY_C1:	/* End (1) on keypad with NumLock off. */
	    return sc_seq_or(do_end, keycode);
	case KEY_A3:	/* PageUp (9) on keypad with NumLock off. */
	    return sc_seq_or(do_page_up, keycode);
	case KEY_C3:	/* PageDown (3) on keypad with NumLock off. */
	    return sc_seq_or(do_page_down, keycode);
562
#ifdef KEY_SDC
563
564
	/* Slang doesn't support KEY_SDC. */
	case KEY_SDC:
565
#endif
566
	case DEL_CODE:
567
568
569
570
	    if (ISSET(REBIND_DELETE))
		return sc_seq_or(do_delete, keycode);
	    else
		return sc_seq_or(do_backspace, keycode);
571
#ifdef KEY_SIC
572
573
574
	/* Slang doesn't support KEY_SIC. */
	case KEY_SIC:
	    return sc_seq_or(do_insertfile_void, keycode);
575
#endif
576
#ifdef KEY_SBEG
577
578
	/* Slang doesn't support KEY_SBEG. */
	case KEY_SBEG:
579
#endif
580
#ifdef KEY_BEG
581
582
	/* Slang doesn't support KEY_BEG. */
	case KEY_BEG:
583
#endif
584
585
	case KEY_B2:	/* Center (5) on keypad with NumLock off. */
	    return ERR;
586
#ifdef KEY_CANCEL
587
#ifdef KEY_SCANCEL
588
589
	/* Slang doesn't support KEY_SCANCEL. */
	case KEY_SCANCEL:
590
#endif
591
592
593
	/* Slang doesn't support KEY_CANCEL. */
	case KEY_CANCEL:
	    return first_sc_for(currmenu, do_cancel)->keycode;
594
#endif
595
#ifdef KEY_SUSPEND
596
#ifdef KEY_SSUSPEND
597
598
	/* Slang doesn't support KEY_SSUSPEND. */
	case KEY_SSUSPEND:
599
#endif
600
601
602
	/* Slang doesn't support KEY_SUSPEND. */
	case KEY_SUSPEND:
	    return sc_seq_or(do_suspend_void, 0);
603
604
#endif
#ifdef PDCURSES
605
606
607
608
609
610
611
	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;
612
613
#endif
#if !defined(NANO_TINY) && defined(KEY_RESIZE)
614
615
616
617
618
	/* Since we don't change the default SIGWINCH handler when
	 * NANO_TINY is defined, KEY_RESIZE is never generated.
	 * Also, Slang and SunOS 5.7-5.9 don't support KEY_RESIZE. */
	case KEY_RESIZE:
	    return ERR;
619
#endif
620
    }
621

622
623
624
    return retval;
}

625
/* Translate escape sequences, most of which correspond to extended
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
626
 * keypad values, into their corresponding key values.  These sequences
627
628
 * are generated when the keypad doesn't support the needed keys.
 * Assume that Escape has already been read in. */
629
int convert_sequence(const int *seq, size_t seq_len)
630
{
631
    if (seq_len > 1) {
632
	switch (seq[0]) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
633
	    case 'O':
634
		switch (seq[1]) {
635
		    case '1':
636
637
			if (seq_len > 4  && seq[2] == ';') {

638
639
	switch (seq[3]) {
	    case '2':
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
		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. */
			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);
		}
655
656
		break;
	    case '5':
657
658
659
660
661
662
663
664
665
666
		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;
		}
667
668
		break;
	}
669

670
671
			}
			break;
672
		    case '2':
673
			if (seq_len >= 3) {
674
			    switch (seq[2]) {
675
				case 'P': /* Esc O 2 P == F13 on xterm. */
676
				    return KEY_F(13);
677
				case 'Q': /* Esc O 2 Q == F14 on xterm. */
678
				    return KEY_F(14);
679
				case 'R': /* Esc O 2 R == F15 on xterm. */
680
				    return KEY_F(15);
681
				case 'S': /* Esc O 2 S == F16 on xterm. */
682
				    return KEY_F(16);
683
684
			    }
			}
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
685
			break;
686
		    case 'A': /* Esc O A == Up on VT100/VT320/xterm. */
687
688
689
		    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. */
690
			return arrow_from_abcd(seq[1]);
691
692
		    case 'E': /* Esc O E == Center (5) on numeric keypad
			       * with NumLock off on xterm. */
693
			return KEY_B2;
694
		    case 'F': /* Esc O F == End on xterm/Terminal. */
695
			return KEY_END;
696
		    case 'H': /* Esc O H == Home on xterm/Terminal. */
697
			return KEY_HOME;
698
		    case 'M': /* Esc O M == Enter on numeric keypad with
699
			       * NumLock off on VT100/VT220/VT320/xterm/
700
			       * rxvt/Eterm. */
701
			return KEY_ENTER;
702
		    case 'P': /* Esc O P == F1 on VT100/VT220/VT320/Mach
703
			       * console. */
704
			return KEY_F(1);
705
		    case 'Q': /* Esc O Q == F2 on VT100/VT220/VT320/Mach
706
			       * console. */
707
			return KEY_F(2);
708
		    case 'R': /* Esc O R == F3 on VT100/VT220/VT320/Mach
709
			       * console. */
710
			return KEY_F(3);
711
		    case 'S': /* Esc O S == F4 on VT100/VT220/VT320/Mach
712
			       * console. */
713
			return KEY_F(4);
714
		    case 'T': /* Esc O T == F5 on Mach console. */
715
			return KEY_F(5);
716
		    case 'U': /* Esc O U == F6 on Mach console. */
717
			return KEY_F(6);
718
		    case 'V': /* Esc O V == F7 on Mach console. */
719
			return KEY_F(7);
720
		    case 'W': /* Esc O W == F8 on Mach console. */
721
			return KEY_F(8);
722
		    case 'X': /* Esc O X == F9 on Mach console. */
723
			return KEY_F(9);
724
		    case 'Y': /* Esc O Y == F10 on Mach console. */
725
			return KEY_F(10);
726
		    case 'a': /* Esc O a == Ctrl-Up on rxvt. */
727
			return CONTROL_UP;
728
		    case 'b': /* Esc O b == Ctrl-Down on rxvt. */
729
			return CONTROL_DOWN;
730
		    case 'c': /* Esc O c == Ctrl-Right on rxvt. */
731
			return CONTROL_RIGHT;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
732
		    case 'd': /* Esc O d == Ctrl-Left on rxvt. */
733
			return CONTROL_LEFT;
734
		    case 'j': /* Esc O j == '*' on numeric keypad with
735
			       * NumLock off on VT100/VT220/VT320/xterm/
736
			       * rxvt/Eterm/Terminal. */
737
			return '*';
738
		    case 'k': /* Esc O k == '+' on numeric keypad with
739
			       * NumLock off on VT100/VT220/VT320/xterm/
740
			       * rxvt/Eterm/Terminal. */
741
			return '+';
742
		    case 'l': /* Esc O l == ',' on numeric keypad with
743
			       * NumLock off on VT100/VT220/VT320/xterm/
744
			       * rxvt/Eterm/Terminal. */
745
			return ',';
746
		    case 'm': /* Esc O m == '-' on numeric keypad with
747
			       * NumLock off on VT100/VT220/VT320/xterm/
748
			       * rxvt/Eterm/Terminal. */
749
			return '-';
750
		    case 'n': /* Esc O n == Delete (.) on numeric keypad
751
			       * with NumLock off on VT100/VT220/VT320/
752
			       * xterm/rxvt/Eterm/Terminal. */
753
			return KEY_DC;
754
		    case 'o': /* Esc O o == '/' on numeric keypad with
755
			       * NumLock off on VT100/VT220/VT320/xterm/
756
			       * rxvt/Eterm/Terminal. */
757
			return '/';
758
		    case 'p': /* Esc O p == Insert (0) on numeric keypad
759
			       * with NumLock off on VT100/VT220/VT320/
760
			       * rxvt/Eterm/Terminal. */
761
			return KEY_IC;
762
		    case 'q': /* Esc O q == End (1) on numeric keypad
763
			       * with NumLock off on VT100/VT220/VT320/
764
			       * rxvt/Eterm/Terminal. */
765
			return KEY_END;
766
		    case 'r': /* Esc O r == Down (2) on numeric keypad
767
			       * with NumLock off on VT100/VT220/VT320/
768
			       * rxvt/Eterm/Terminal. */
769
			return KEY_DOWN;
770
		    case 's': /* Esc O s == PageDown (3) on numeric
771
			       * keypad with NumLock off on VT100/VT220/
772
			       * VT320/rxvt/Eterm/Terminal. */
773
			return KEY_NPAGE;
774
		    case 't': /* Esc O t == Left (4) on numeric keypad
775
			       * with NumLock off on VT100/VT220/VT320/
776
			       * rxvt/Eterm/Terminal. */
777
			return KEY_LEFT;
778
		    case 'u': /* Esc O u == Center (5) on numeric keypad
779
780
			       * with NumLock off on VT100/VT220/VT320/
			       * rxvt/Eterm. */
781
			return KEY_B2;
782
		    case 'v': /* Esc O v == Right (6) on numeric keypad
783
			       * with NumLock off on VT100/VT220/VT320/
784
			       * rxvt/Eterm/Terminal. */
785
			return KEY_RIGHT;
786
		    case 'w': /* Esc O w == Home (7) on numeric keypad
787
			       * with NumLock off on VT100/VT220/VT320/
788
			       * rxvt/Eterm/Terminal. */
789
			return KEY_HOME;
790
		    case 'x': /* Esc O x == Up (8) on numeric keypad
791
			       * with NumLock off on VT100/VT220/VT320/
792
			       * rxvt/Eterm/Terminal. */
793
			return KEY_UP;
794
		    case 'y': /* Esc O y == PageUp (9) on numeric keypad
795
			       * with NumLock off on VT100/VT220/VT320/
796
			       * rxvt/Eterm/Terminal. */
797
			return KEY_PPAGE;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
798
799
800
		}
		break;
	    case 'o':
801
		switch (seq[1]) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
802
		    case 'a': /* Esc o a == Ctrl-Up on Eterm. */
803
			return CONTROL_UP;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
804
		    case 'b': /* Esc o b == Ctrl-Down on Eterm. */
805
			return CONTROL_DOWN;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
806
		    case 'c': /* Esc o c == Ctrl-Right on Eterm. */
807
			return CONTROL_RIGHT;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
808
		    case 'd': /* Esc o d == Ctrl-Left on Eterm. */
809
			return CONTROL_LEFT;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
810
811
812
		}
		break;
	    case '[':
813
		switch (seq[1]) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
814
		    case '1':
815
			if (seq_len > 3 && seq[3] == '~') {
816
			    switch (seq[2]) {
817
				case '1': /* Esc [ 1 1 ~ == F1 on rxvt/Eterm. */
818
				    return KEY_F(1);
819
				case '2': /* Esc [ 1 2 ~ == F2 on rxvt/Eterm. */
820
				    return KEY_F(2);
821
				case '3': /* Esc [ 1 3 ~ == F3 on rxvt/Eterm. */
822
				    return KEY_F(3);
823
				case '4': /* Esc [ 1 4 ~ == F4 on rxvt/Eterm. */
824
				    return KEY_F(4);
825
826
				case '5': /* Esc [ 1 5 ~ == F5 on xterm/
					   * rxvt/Eterm. */
827
				    return KEY_F(5);
828
				case '7': /* Esc [ 1 7 ~ == F6 on
829
830
					   * VT220/VT320/Linux console/
					   * xterm/rxvt/Eterm. */
831
				    return KEY_F(6);
832
				case '8': /* Esc [ 1 8 ~ == F7 on
833
834
					   * VT220/VT320/Linux console/
					   * xterm/rxvt/Eterm. */
835
				    return KEY_F(7);
836
				case '9': /* Esc [ 1 9 ~ == F8 on
837
838
					   * VT220/VT320/Linux console/
					   * xterm/rxvt/Eterm. */
839
				    return KEY_F(8);
840
841
842
			    }
			} else if (seq_len > 4 && seq[2] == ';') {

843
	switch (seq[3]) {
844
	    case '2':
845
846
847
848
849
850
851
		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. */
			return arrow_from_abcd(seq[4]);
		}
852
853
		break;
	    case '5':
854
855
856
857
858
859
860
861
862
863
		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;
		}
864
865
		break;
	}
866
867
868
869

			} else if (seq_len > 2 && seq[2] == '~')
			    /* Esc [ 1 ~ == Home on VT320/Linux console. */
			    return KEY_HOME;
870
871
			break;
		    case '2':
872
			if (seq_len > 3 && seq[3] == '~') {
873
			    switch (seq[2]) {
874
875
				case '0': /* Esc [ 2 0 ~ == F9 on VT220/VT320/
					   * Linux console/xterm/rxvt/Eterm. */
876
				    return KEY_F(9);
877
878
				case '1': /* Esc [ 2 1 ~ == F10 on VT220/VT320/
					   * Linux console/xterm/rxvt/Eterm. */
879
				    return KEY_F(10);
880
881
				case '3': /* Esc [ 2 3 ~ == F11 on VT220/VT320/
					   * Linux console/xterm/rxvt/Eterm. */
882
				    return KEY_F(11);
883
884
				case '4': /* Esc [ 2 4 ~ == F12 on VT220/VT320/
					   * Linux console/xterm/rxvt/Eterm. */
885
				    return KEY_F(12);
886
887
				case '5': /* Esc [ 2 5 ~ == F13 on VT220/VT320/
					   * Linux console/rxvt/Eterm. */
888
				    return KEY_F(13);
889
890
				case '6': /* Esc [ 2 6 ~ == F14 on VT220/VT320/
					   * Linux console/rxvt/Eterm. */
891
				    return KEY_F(14);
892
893
				case '8': /* Esc [ 2 8 ~ == F15 on VT220/VT320/
					   * Linux console/rxvt/Eterm. */
894
				    return KEY_F(15);
895
896
				case '9': /* Esc [ 2 9 ~ == F16 on VT220/VT320/
					   * Linux console/rxvt/Eterm. */
897
				    return KEY_F(16);
898
			    }
899
900
901
902
			} 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
903
			break;
904
		    case '3': /* Esc [ 3 ~ == Delete on VT220/VT320/
905
			       * Linux console/xterm/Terminal. */
906
			return KEY_DC;
907
		    case '4': /* Esc [ 4 ~ == End on VT220/VT320/Linux
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
908
			       * console/xterm. */
909
			return KEY_END;
910
		    case '5': /* Esc [ 5 ~ == PageUp on VT220/VT320/
911
912
			       * Linux console/xterm/Terminal;
			       * Esc [ 5 ^ == PageUp on Eterm. */
913
			return KEY_PPAGE;
914
		    case '6': /* Esc [ 6 ~ == PageDown on VT220/VT320/
915
			       * Linux console/xterm/Terminal;
916
			       * Esc [ 6 ^ == PageDown on Eterm. */
917
			return KEY_NPAGE;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
918
		    case '7': /* Esc [ 7 ~ == Home on rxvt. */
919
			return KEY_HOME;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
920
		    case '8': /* Esc [ 8 ~ == End on rxvt. */
921
			return KEY_END;
922
		    case '9': /* Esc [ 9 == Delete on Mach console. */
923
			return KEY_DC;
924
		    case '@': /* Esc [ @ == Insert on Mach console. */
925
			return KEY_IC;
926
		    case 'A': /* Esc [ A == Up on ANSI/VT220/Linux
927
			       * console/FreeBSD console/Mach console/
928
			       * rxvt/Eterm/Terminal. */
929
		    case 'B': /* Esc [ B == Down on ANSI/VT220/Linux
930
			       * console/FreeBSD console/Mach console/
931
			       * rxvt/Eterm/Terminal. */
932
		    case 'C': /* Esc [ C == Right on ANSI/VT220/Linux
933
			       * console/FreeBSD console/Mach console/
934
			       * rxvt/Eterm/Terminal. */
935
		    case 'D': /* Esc [ D == Left on ANSI/VT220/Linux
936
			       * console/FreeBSD console/Mach console/
937
			       * rxvt/Eterm/Terminal. */
938
			return arrow_from_abcd(seq[1]);
939
		    case 'E': /* Esc [ E == Center (5) on numeric keypad
940
941
			       * with NumLock off on FreeBSD console/
			       * Terminal. */
942
			return KEY_B2;
943
		    case 'F': /* Esc [ F == End on FreeBSD console/Eterm. */
944
			return KEY_END;
945
		    case 'G': /* Esc [ G == PageDown on FreeBSD console. */
946
			return KEY_NPAGE;
947
		    case 'H': /* Esc [ H == Home on ANSI/VT220/FreeBSD
948
			       * console/Mach console/Eterm. */
949
			return KEY_HOME;
950
		    case 'I': /* Esc [ I == PageUp on FreeBSD console. */
951
			return KEY_PPAGE;
952
		    case 'L': /* Esc [ L == Insert on ANSI/FreeBSD console. */
953
			return KEY_IC;
954
		    case 'M': /* Esc [ M == F1 on FreeBSD console. */
955
			return KEY_F(1);
956
		    case 'N': /* Esc [ N == F2 on FreeBSD console. */
957
			return KEY_F(2);
958
		    case 'O':
959
			if (seq_len > 2) {
960
			    switch (seq[2]) {
961
				case 'P': /* Esc [ O P == F1 on xterm. */
962
				    return KEY_F(1);
963
				case 'Q': /* Esc [ O Q == F2 on xterm. */
964
				    return KEY_F(2);
965
				case 'R': /* Esc [ O R == F3 on xterm. */
966
				    return KEY_F(3);
967
				case 'S': /* Esc [ O S == F4 on xterm. */
968
				    return KEY_F(4);
969
			    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
970
			} else
971
			    /* Esc [ O == F3 on FreeBSD console. */
972
			    return KEY_F(3);
973
974
			break;
		    case 'P': /* Esc [ P == F4 on FreeBSD console. */
975
			return KEY_F(4);
976
		    case 'Q': /* Esc [ Q == F5 on FreeBSD console. */
977
			return KEY_F(5);
978
		    case 'R': /* Esc [ R == F6 on FreeBSD console. */
979
			return KEY_F(6);
980
		    case 'S': /* Esc [ S == F7 on FreeBSD console. */
981
			return KEY_F(7);
982
		    case 'T': /* Esc [ T == F8 on FreeBSD console. */
983
			return KEY_F(8);
984
		    case 'U': /* Esc [ U == PageDown on Mach console. */
985
			return KEY_NPAGE;
986
		    case 'V': /* Esc [ V == PageUp on Mach console. */
987
			return KEY_PPAGE;
988
		    case 'W': /* Esc [ W == F11 on FreeBSD console. */
989
			return KEY_F(11);
990
		    case 'X': /* Esc [ X == F12 on FreeBSD console. */
991
			return KEY_F(12);
992
		    case 'Y': /* Esc [ Y == End on Mach console. */
993
			return KEY_END;
994
		    case 'Z': /* Esc [ Z == F14 on FreeBSD console. */
995
			return KEY_F(14);
996
		    case 'a': /* Esc [ a == Shift-Up on rxvt/Eterm. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
997
		    case 'b': /* Esc [ b == Shift-Down on rxvt/Eterm. */
998
		    case 'c': /* Esc [ c == Shift-Right on rxvt/Eterm. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
999
		    case 'd': /* Esc [ d == Shift-Left on rxvt/Eterm. */
1000
			return arrow_from_abcd(seq[1]);
1001
		    case '[':
1002
			if (seq_len > 2 ) {
1003
			    switch (seq[2]) {
1004
1005
				case 'A': /* Esc [ [ A == F1 on Linux
					   * console. */
1006
				    return KEY_F(1);
1007
1008
				case 'B': /* Esc [ [ B == F2 on Linux
					   * console. */
1009
				    return KEY_F(2);
1010
1011
				case 'C': /* Esc [ [ C == F3 on Linux
					   * console. */
1012
				    return KEY_F(3);
1013
1014
				case 'D': /* Esc [ [ D == F4 on Linux
					   * console. */
1015
				    return KEY_F(4);
1016
1017
				case 'E': /* Esc [ [ E == F5 on Linux
					   * console. */
1018
				    return KEY_F(5);
1019
1020
			    }
			}
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1021
1022
1023
1024
			break;
		}
		break;
	}
1025
1026
    }

1027
    return ERR;
1028
1029
}

1030
1031
/* Return the equivalent arrow-key value for the first four letters
 * in the alphabet, common to many escape sequences. */
1032
int arrow_from_abcd(int kbinput)
1033
1034
1035
{
    switch (tolower(kbinput)) {
	case 'a':
1036
	    return KEY_UP;
1037
	case 'b':
1038
	    return KEY_DOWN;
1039
	case 'c':
1040
	    return KEY_RIGHT;
1041
	case 'd':
1042
	    return KEY_LEFT;
1043
1044
1045
1046
1047
	default:
	    return ERR;
    }
}

1048
/* Interpret the escape sequence in the keystroke buffer, the first
1049
1050
 * character of which is kbinput.  Assume that the keystroke buffer
 * isn't empty, and that the initial escape has already been read in. */
1051
int parse_escape_sequence(WINDOW *win, int kbinput)
1052
1053
1054
1055
1056
1057
1058
1059
{
    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);
1060
    seq_len = key_buffer_len;
1061
    seq = get_input(NULL, seq_len);
1062
    retval = convert_sequence(seq, seq_len);
1063
1064
1065

    free(seq);

1066
    /* If we got an unrecognized escape sequence, notify the user. */
1067
    if (retval == ERR) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1068
	if (win == edit) {
1069
1070
	    /* TRANSLATORS: This refers to a sequence of escape codes
	     * (from the keyboard) that nano does not know about. */
1071
	    statusline(ALERT, _("Unknown sequence"));
1072
	    suppress_cursorpos = FALSE;
1073
	    lastmessage = HUSH;
1074
1075
1076
1077
	    if (currmenu == MMAIN) {
		reset_cursor();
		curs_set(1);
	    }
1078
1079
1080
	}
    }

1081
#ifdef DEBUG
1082
1083
    fprintf(stderr, "parse_escape_sequence(): kbinput = %d, seq_len = %lu, retval = %d\n",
		kbinput, (unsigned long)seq_len, retval);
1084
1085
1086
1087
1088
#endif

    return retval;
}

1089
1090
/* Translate a byte sequence: turn a three-digit decimal number (from
 * 000 to 255) into its corresponding byte value. */
1091
int get_byte_kbinput(int kbinput)
1092
{
1093
    static int byte_digits = 0, byte = 0;
1094
    int retval = ERR;
1095

1096
1097
    /* Increment the byte digit counter. */
    byte_digits++;
1098

1099
    switch (byte_digits) {
1100
	case 1:
1101
1102
	    /* First digit: This must be from zero to two.  Put it in
	     * the 100's position of the byte sequence holder. */
1103
	    if ('0' <= kbinput && kbinput <= '2')
1104
		byte = (kbinput - '0') * 100;
1105
	    else
1106
1107
		/* This isn't the start of a byte sequence.  Return this
		 * character as the result. */
1108
1109
1110
		retval = kbinput;
	    break;
	case 2:
1111
1112
1113
1114
	    /* 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. */
1115
1116
1117
	    if (('0' <= kbinput && kbinput <= '5') || (byte < 200 &&
		'6' <= kbinput && kbinput <= '9'))
		byte += (kbinput - '0') * 10;
1118
	    else
1119
1120
		/* This isn't the second digit of a byte sequence.
		 * Return this character as the result. */
1121
1122
1123
		retval = kbinput;
	    break;
	case 3:
1124
	    /* Third digit: This must be from zero to five if the first
1125
1126
1127
	     * 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. */
1128
1129
	    if (('0' <= kbinput && kbinput <= '5') || (byte < 250 &&
		'6' <= kbinput && kbinput <= '9')) {
1130
		byte += kbinput - '0';
1131
		/* The byte sequence is complete. */
1132
		retval = byte;
1133
	    } else
1134
1135
		/* This isn't the third digit of a byte sequence.
		 * Return this character as the result. */
1136
1137
		retval = kbinput;
	    break;
1138
	default:
1139
1140
1141
	    /* If there are more than three digits, return this
	     * character as the result.  (Maybe we should produce an
	     * error instead?) */
1142
1143
1144
1145
1146
1147
1148
1149
	    retval = kbinput;
	    break;
    }

    /* If we have a result, reset the byte digit counter and the byte
     * sequence holder. */
    if (retval != ERR) {
	byte_digits = 0;
1150
	byte = 0;
1151
1152
1153
    }

#ifdef DEBUG
1154
    fprintf(stderr, "get_byte_kbinput(): kbinput = %d, byte_digits = %d, byte = %d, retval = %d\n", kbinput, byte_digits, byte, retval);
1155
1156
1157
1158
1159
#endif

    return retval;
}

1160
#ifdef ENABLE_UTF8
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1161
/* If the character in kbinput is a valid hexadecimal digit, multiply it
1162
 * by factor and add the result to uni, and return ERR to signify okay. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1163
1164
1165
1166
1167
1168
1169
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
1170
1171
	/* The character isn't hexadecimal; give it as the result. */
	return (long)kbinput;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1172

1173
    return ERR;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1174
1175
}

1176
/* Translate a Unicode sequence: turn a six-digit hexadecimal number
1177
 * (from 000000 to 10FFFF, case-insensitive) into its corresponding
1178
 * multibyte value. */
1179
long get_unicode_kbinput(WINDOW *win, int kbinput)
1180
{
1181
1182
1183
    static int uni_digits = 0;
    static long uni = 0;
    long retval = ERR;
1184

1185
    /* Increment the Unicode digit counter. */
1186
    uni_digits++;
1187

1188
    switch (uni_digits) {
1189
	case 1:
1190
1191
1192
1193
	    /* 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')
1194
		uni = (kbinput - '0') * 0x100000;
1195
1196
1197
1198
	    else
		retval = kbinput;
	    break;
	case 2:
1199
1200
1201
	    /* 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)
1202
		retval = add_unicode_digit(kbinput, 0x10000, &uni);
1203
1204
1205
1206
	    else
		retval = kbinput;
	    break;
	case 3:
1207
	    /* Later digits may be any hexadecimal value. */
1208
	    retval = add_unicode_digit(kbinput, 0x1000, &uni);
1209
	    break;
1210
	case 4:
1211
	    retval = add_unicode_digit(kbinput, 0x100, &uni);
1212
	    break;
1213
	case 5:
1214
	    retval = add_unicode_digit(kbinput, 0x10, &uni);
1215
	    break;
1216
	case 6:
1217
	    retval = add_unicode_digit(kbinput, 0x1, &uni);
1218
1219
	    /* If also the sixth digit was a valid hexadecimal value, then
	     * the Unicode sequence is complete, so return it. */
1220
	    if (retval == ERR)
1221
		retval = uni;
1222
1223
	    break;
    }
1224

1225
    /* Show feedback only when editing, not when at a prompt. */
1226
    if (retval == ERR && win == edit) {
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
	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);
    }
1237

1238
#ifdef DEBUG
1239
1240
    fprintf(stderr, "get_unicode_kbinput(): kbinput = %d, uni_digits = %d, uni = %ld, retval = %ld\n",
						kbinput, uni_digits, uni, retval);
1241
1242
#endif

1243
1244
1245
1246
    /* If we have an end result, reset the Unicode digit counter. */
    if (retval != ERR)
	uni_digits = 0;

1247
1248
    return retval;
}
1249
#endif /* ENABLE_UTF8 */
1250

1251
1252
1253
1254
1255
1256
/* Translate a control character sequence: turn an ASCII non-control
 * character into its corresponding control character. */
int get_control_kbinput(int kbinput)
{
    int retval;

1257
    /* Ctrl-Space (Ctrl-2, Ctrl-@, Ctrl-`) */
1258
    if (kbinput == ' ' || kbinput == '2')
1259
	retval = 0;
1260
1261
    /* Ctrl-/ (Ctrl-7, Ctrl-_) */
    else if (kbinput == '/')
1262
	retval = 31;
1263
    /* Ctrl-3 (Ctrl-[, Esc) to Ctrl-7 (Ctrl-/, Ctrl-_) */
1264
1265
1266
    else if ('3' <= kbinput && kbinput <= '7')
	retval = kbinput - 24;
    /* Ctrl-8 (Ctrl-?) */
1267
    else if (kbinput == '8' || kbinput == '?')
1268
	retval = DEL_CODE;
1269
1270
    /* Ctrl-@ (Ctrl-Space, Ctrl-2, Ctrl-`) to Ctrl-_ (Ctrl-/, Ctrl-7) */
    else if ('@' <= kbinput && kbinput <= '_')
1271
	retval = kbinput - '@';
1272
1273
    /* Ctrl-` (Ctrl-2, Ctrl-Space, Ctrl-@) to Ctrl-~ (Ctrl-6, Ctrl-^) */
    else if ('`' <= kbinput && kbinput <= '~')
1274
	retval = kbinput - '`';
1275
1276
1277
    else
	retval = kbinput;

1278
#ifdef DEBUG
1279
    fprintf(stderr, "get_control_kbinput(): kbinput = %d, retval = %d\n", kbinput, retval);
1280
1281
#endif

1282
1283
    return retval;
}
1284

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1285
1286
/* Put the output-formatted characters in output back into the keystroke
 * buffer, so that they can be parsed and displayed as output again. */
1287
void unparse_kbinput(char *output, size_t output_len)
1288
{
1289
1290
    int *input;
    size_t i;
1291

1292
1293
1294
1295
    if (output_len == 0)
	return;

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

1297
1298
    for (i = 0; i < output_len; i++)
	input[i] = (int)output[i];
1299

1300
    unget_input(input, output_len);
1301

1302
    free(input);
1303
1304
}

1305
/* Read in a stream of characters verbatim, and return the length of the
1306
1307
1308
1309
 * string in kbinput_len.  Assume nodelay(win) is FALSE. */
int *get_verbatim_kbinput(WINDOW *win, size_t *kbinput_len)
{
    int *retval;
1310

1311
    /* Turn off flow control characters if necessary so that we can type
1312
1313
     * them in verbatim, and turn the keypad off if necessary so that we
     * don't get extended keypad values. */
1314
1315
    if (ISSET(PRESERVE))
	disable_flow_control();
1316
1317
    if (!ISSET(REBIND_KEYPAD))
	keypad(win, FALSE);
1318
1319
1320

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

    /* Turn flow control characters back on if necessary and turn the
1323
     * keypad back on if necessary now that we're done. */
1324
1325
    if (ISSET(PRESERVE))
	enable_flow_control();
1326
1327
1328
1329
1330
1331
    /* 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);
    }
1332

1333
    return retval;
1334
1335
}

1336
1337
1338
1339
1340
/* 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)
1341
{
1342
    int *kbinput;
1343

1344
    /* Read in the first code. */
1345
1346
    while ((kbinput = get_input(win, 1)) == NULL)
	;
1347

1348
#ifndef NANO_TINY
1349
1350
1351
    /* When the window was resized, abort and return nothing. */
    if (*kbinput == KEY_WINCH) {
	free(kbinput);
1352
	*count = 0;
1353
1354
	return NULL;
    }
1355
#endif
1356

1357
#ifdef ENABLE_UTF8
1358
    if (using_utf8()) {
1359
	/* Check whether the first code is a valid starter digit: 0 or 1. */
1360
	long uni = get_unicode_kbinput(win, *kbinput);
1361

1362
	/* If the first code isn't the digit 0 nor 1, put it back. */
1363
1364
	if (uni != ERR)
	    unget_input(kbinput, 1);
1365
1366
	/* Otherwise, continue reading in digits until we have a complete
	 * Unicode value, and put back the corresponding byte(s). */
1367
1368
1369
1370
1371
	else {
	    char *uni_mb;
	    int uni_mb_len, *seq, i;

	    while (uni == ERR) {
1372
		free(kbinput);
1373
1374
		while ((kbinput = get_input(win, 1)) == NULL)
		    ;
1375
		uni = get_unicode_kbinput(win, *kbinput);
1376
	    }
1377

1378
	    /* Convert the Unicode value to a multibyte sequence. */
1379
	    uni_mb = make_mbchar(uni, &uni_mb_len);
1380

1381
	    seq = (int *)nmalloc(uni_mb_len * sizeof(int));
1382

1383
1384
	    for (i = 0; i < uni_mb_len; i++)
		seq[i] = (unsigned char)uni_mb[i];
1385

1386
	    /* Insert the multibyte sequence into the input buffer. */
1387
	    unget_input(seq, uni_mb_len);
1388

1389
1390
	    free(seq);
	    free(uni_mb);
1391
	}
1392
    } else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1393
#endif /* ENABLE_UTF8 */
1394
	/* Put back the first code. */
1395
	unget_input(kbinput, 1);
1396

1397
1398
    free(kbinput);

1399
    *count = 1;
1400

1401
1402
1403
1404
1405
1406
    /* 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);
1407
1408
}

1409
#ifndef DISABLE_MOUSE
1410
/* Handle any mouse event that may have occurred.  We currently handle
1411
1412
1413
1414
1415
1416
1417
 * 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
1418
1419
1420
1421
1422
1423
 * 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. */
1424
int get_mouseinput(int *mouse_x, int *mouse_y, bool allow_shortcuts)
1425
1426
{
    MEVENT mevent;
1427
    bool in_bottomwin;
1428
    subnfunc *f;
1429
1430
1431
1432
1433
1434

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

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

1437
1438
1439
    /* Save the screen coordinates where the mouse event took place. */
    *mouse_x = mevent.x;
    *mouse_y = mevent.y;
1440

1441
1442
    in_bottomwin = wenclose(bottomwin, *mouse_y, *mouse_x);

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1443
    /* Handle releases/clicks of the first mouse button. */
1444
    if (mevent.bstate & (BUTTON1_RELEASED | BUTTON1_CLICKED)) {
1445
1446
	/* If we're allowing shortcuts, the current shortcut list is
	 * being displayed on the last two lines of the screen, and the
1447
1448
1449
	 * 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. */
1450
	if (allow_shortcuts && !ISSET(NO_HELP) && in_bottomwin) {
1451
1452
1453
1454
	    int i;
		/* The width of all the shortcuts, except for the last
		 * two, in the shortcut list in bottomwin. */
	    int j;
1455
		/* The calculated index number of the clicked item. */
1456
1457
1458
1459
	    size_t currslen;
		/* The number of shortcuts in the current shortcut
		 * list. */

1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
	    /* Translate the mouse event coordinates so that they're
	     * relative to bottomwin. */
	    wmouse_trafo(bottomwin, mouse_y, mouse_x, FALSE);

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

		return 0;
	    }
1474

1475
	    /* Get the shortcut lists' length. */
1476
	    if (currmenu == MMAIN)
1477
		currslen = MAIN_VISIBLE;
1478
	    else {
1479
		currslen = length_of_list(currmenu);
1480

1481
1482
1483
1484
1485
		/* We don't show any more shortcuts than the main list
		 * does. */
		if (currslen > MAIN_VISIBLE)
		    currslen = MAIN_VISIBLE;
	    }
1486

1487
1488
1489
1490
1491
1492
1493
1494
	    /* Calculate the width of all of the shortcuts in the list
	     * except for the last two, which are longer by (COLS % i)
	     * columns so as to not waste space. */
	    if (currslen < 2)
		i = COLS / (MAIN_VISIBLE / 2);
	    else
		i = COLS / ((currslen / 2) + (currslen % 2));

1495
1496
	    /* Calculate the one-based index in the shortcut list. */
	    j = (*mouse_x / i) * 2 + *mouse_y;
1497

1498
1499
	    /* Adjust the index if we hit the last two wider ones. */
	    if ((j > currslen) && (*mouse_x % i < COLS % i))
1500
		j -= 2;
1501
1502
1503
#ifdef DEBUG
	    fprintf(stderr, "Calculated %i as index in shortcut list, currmenu = %x.\n", j, currmenu);
#endif
1504
1505
	    /* Ignore releases/clicks of the first mouse button beyond
	     * the last shortcut. */
1506
	    if (j > currslen)
1507
		return 2;
1508

1509
1510
	    /* Go through the list of functions to determine which
	     * shortcut in the current menu we released/clicked on. */
1511
	    for (f = allfuncs; f != NULL; f = f->next) {
1512
		if ((f->menus & currmenu) == 0)
1513
1514
		    continue;
		if (first_sc_for(currmenu, f->scfunc) == NULL)
1515
		    continue;
1516
1517
		/* Tick off an actually shown shortcut. */
		j -= 1;
1518
1519
		if (j == 0)
		    break;
1520
	    }
1521
#ifdef DEBUG
1522
	    fprintf(stderr, "Stopped on func %ld present in menus %x\n", (long)f->scfunc, f->menus);
1523
#endif
1524

1525
	    /* And put the corresponding key into the keyboard buffer. */
1526
	    if (f != NULL) {
1527
		const sc *s = first_sc_for(currmenu, f->scfunc);
1528
		unget_kbinput(s->keycode, s->meta);
1529
	    }
1530
	    return 1;
1531
	} else
1532
1533
	    /* Handle releases/clicks of the first mouse button that
	     * aren't on the current shortcut list elsewhere. */
1534
	    return 0;
1535
    }
1536
1537
#if NCURSES_MOUSE_VERSION >= 2
    /* Handle presses of the fourth mouse button (upward rolls of the
1538
1539
1540
     * 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
1541
	bool in_edit = wenclose(edit, *mouse_y, *mouse_x);
1542

1543
1544
1545
1546
	if (in_bottomwin)
	    /* Translate the mouse event coordinates so that they're
	     * relative to bottomwin. */
	    wmouse_trafo(bottomwin, mouse_y, mouse_x, FALSE);
1547

1548
	if (in_edit || (in_bottomwin && *mouse_y == 0)) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1549
1550
	    int i;

1551
1552
1553
1554
1555
	    /* 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) ?
1556
				KEY_PPAGE : KEY_NPAGE, FALSE);
1557
1558
1559
1560
1561
1562
1563

	    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;
1564
1565
    }
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1566
1567
1568

    /* Ignore all other mouse events. */
    return 2;
1569
}
1570
1571
#endif /* !DISABLE_MOUSE */

1572
1573
1574
1575
/* 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. */
1576
const sc *get_shortcut(int *kbinput)
1577
{
1578
    sc *s;
1579

1580
#ifdef DEBUG
1581
1582
    fprintf(stderr, "get_shortcut(): kbinput = %d, meta_key = %s -- ",
				*kbinput, meta_key ? "TRUE" : "FALSE");
1583
1584
#endif

1585
    for (s = sclist; s != NULL; s = s->next) {
1586
	if ((s->menus & currmenu) && *kbinput == s->keycode &&
1587
					meta_key == s->meta) {
1588
#ifdef DEBUG
1589
1590
	    fprintf (stderr, "matched seq '%s'  (menu is %x from %x)\n",
				s->keystr, currmenu, s->menus);
1591
#endif
1592
	    return s;
1593
1594
	}
    }
1595
#ifdef DEBUG
1596
    fprintf (stderr, "matched nothing\n");
1597
#endif
1598
1599
1600
1601

    return NULL;
}

1602
1603
1604
1605
1606
/* 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
1607

1608
1609
1610
1611
    for (; n > 0; n--)
	waddch(win, ' ');
}

1612
/* Blank the first line of the top portion of the window. */
1613
void blank_titlebar(void)
Chris Allegretta's avatar
Chris Allegretta committed
1614
{
1615
    blank_line(topwin, 0, 0, COLS);
1616
1617
}

1618
/* Blank all the lines of the middle portion of the window, i.e. the
1619
 * edit window. */
Chris Allegretta's avatar
Chris Allegretta committed
1620
1621
void blank_edit(void)
{
1622
    int i;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1623

1624
    for (i = 0; i < editwinrows; i++)
1625
	blank_line(edit, i, 0, COLS);
Chris Allegretta's avatar
Chris Allegretta committed
1626
1627
}

1628
/* Blank the first line of the bottom portion of the window. */
Chris Allegretta's avatar
Chris Allegretta committed
1629
1630
void blank_statusbar(void)
{
1631
    blank_line(bottomwin, 0, 0, COLS);
Chris Allegretta's avatar
Chris Allegretta committed
1632
1633
}

1634
1635
/* If the NO_HELP flag isn't set, blank the last two lines of the bottom
 * portion of the window. */
1636
1637
1638
void blank_bottombars(void)
{
    if (!ISSET(NO_HELP)) {
1639
1640
	blank_line(bottomwin, 1, 0, COLS);
	blank_line(bottomwin, 2, 0, COLS);
1641
1642
1643
    }
}

1644
1645
/* Check if the number of keystrokes needed to blank the statusbar has
 * been pressed.  If so, blank the statusbar, unless constant cursor
1646
 * position display is on and we are in the editing screen. */
1647
void check_statusblank(void)
Chris Allegretta's avatar
Chris Allegretta committed
1648
{
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
    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
1663
1664
1665
    }
}

1666
1667
/* Convert buf into a string that can be displayed on screen.  The
 * caller wants to display buf starting with column start_col, and
1668
1669
 * extending for at most span columns.  start_col is zero-based.  span
 * is one-based, so span == 0 means you get "" returned.  The returned
1670
1671
1672
 * 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. */
1673
1674
char *display_string(const char *buf, size_t start_col, size_t span,
	bool dollars)
1675
1676
{
    size_t start_index;
1677
	/* Index in buf of the first character shown. */
1678
    size_t column;
1679
	/* Screen column that start_index corresponds to. */
1680
1681
1682
1683
    char *converted;
	/* The string we return. */
    size_t index;
	/* Current position in converted. */
1684

1685
1686
    /* If dollars is TRUE, make room for the "$" at the end of the
     * line. */
1687
1688
    if (dollars && span > 0 && strlenpt(buf) > start_col + span)
	span--;
1689

1690
    if (span == 0)
1691
1692
1693
1694
	return mallocstrcpy(NULL, "");

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

1696
    assert(column <= start_col);
1697

1698
1699
    /* Allocate enough space to hold the entire converted buffer. */
    converted = charalloc(strlen(buf) * (mb_cur_max() + tabsize) + 1);
1700

1701
    index = 0;
1702
#ifdef USING_OLD_NCURSES
1703
    seen_wide = FALSE;
1704
#endif
1705
    buf += start_index;
1706

1707
    if (*buf != '\0' && *buf != '\t' &&
1708
	(column < start_col || (dollars && column > 0))) {
1709
	/* We don't display the complete first character as it starts to
1710
	 * the left of the screen. */
1711
	if (is_cntrl_mbchar(buf)) {
1712
	    if (column < start_col) {
1713
		converted[index++] = control_mbrep(buf);
1714
		start_col++;
1715
		buf += parse_mbchar(buf, NULL, NULL);
1716
	    }
1717
	}
1718
#ifdef ENABLE_UTF8
1719
	else if (using_utf8() && mbwidth(buf) == 2) {
1720
1721
1722
1723
1724
	    if (column >= start_col) {
		converted[index++] = ' ';
		start_col++;
	    }

1725
	    converted[index++] = ' ';
1726
	    start_col++;
1727

1728
	    buf += parse_mbchar(buf, NULL, NULL);
1729
	}
1730
#endif
1731
1732
    }

1733
    while (*buf != '\0') {
1734
	int charlength, charwidth = 1;
1735

1736
	if (*buf == ' ') {
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
	    /* 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++;
1748
1749
	    buf++;
	    continue;
1750
	} else if (*buf == '\t') {
1751
	    /* Show a tab as a visible character, or as as a space. */
1752
#ifndef NANO_TINY
1753
	    if (ISSET(WHITESPACE_DISPLAY)) {
1754
		int i = 0;
1755

1756
1757
		while (i < whitespace_len[0])
		    converted[index++] = whitespace[i++];
1758
	    } else
1759
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1760
		converted[index++] = ' ';
1761
	    start_col++;
1762
	    /* Fill the tab up with the required number of spaces. */
1763
	    while (start_col % tabsize != 0) {
1764
		converted[index++] = ' ';
1765
1766
		start_col++;
	    }
1767
1768
1769
1770
	    buf++;
	    continue;
	}

1771
	charlength = length_of_char(buf, &charwidth);
1772

1773
	/* If buf contains a control character, represent it. */
1774
	if (is_cntrl_mbchar(buf)) {
1775
	    converted[index++] = '^';
1776
	    converted[index++] = control_mbrep(buf);
1777
	    start_col += 2;
1778
1779
1780
	    buf += charlength;
	    continue;
	}
1781

1782
1783
1784
1785
	/* If buf contains a valid non-control character, simply copy it. */
	if (charlength > 0) {
	    for (; charlength > 0; charlength--)
		converted[index++] = *(buf++);
1786

1787
	    start_col += charwidth;
1788
#ifdef USING_OLD_NCURSES
1789
	    if (charwidth > 1)
1790
		seen_wide = TRUE;
1791
#endif
1792
	    continue;
1793
1794
	}

1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
	/* 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;
1806
1807
    }

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1808
    /* Null-terminate converted. */
1809
    converted[index] = '\0';
1810

1811
1812
    /* Make sure converted takes up no more than span columns. */
    index = actual_x(converted, span);
1813
    null_at(&converted, index);
1814

1815
    return converted;
1816
1817
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1818
1819
1820
1821
1822
1823
/* 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. */
1824
void titlebar(const char *path)
Chris Allegretta's avatar
Chris Allegretta committed
1825
{
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
    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. */
1838

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

1841
    wattron(topwin, interface_color_pair[TITLE_BAR]);
1842

1843
    blank_titlebar();
Chris Allegretta's avatar
Chris Allegretta committed
1844

1845
1846
1847
    /* 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
1848

1849
    /* Figure out the path, prefix and state strings. */
1850
#ifndef DISABLE_BROWSER
1851
1852
1853
1854
    if (path != NULL)
	prefix = _("DIR:");
    else
#endif
1855
1856
1857
1858
1859
1860
1861
    {
	if (openfile->filename[0] == '\0')
	    path = _("New Buffer");
	else {
	    path = openfile->filename;
	    prefix = _("File:");
	}
1862

1863
1864
1865
1866
	if (openfile->modified)
	    state = _("Modified");
	else if (ISSET(VIEW_MODE))
	    state = _("View");
1867

1868
1869
	pluglen = strlenpt(_("Modified")) + 1;
    }
1870

1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
    /* 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;
1881
1882
    }

1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
    /* 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;
1895
1896
1897
	}
    }

1898
1899
1900
1901
    /* If we have side spaces left, center the path name. */
    if (verlen > 0)
	offset = verlen + (COLS - (verlen + pluglen + statelen) -
					(prefixlen + pathlen)) / 2;
1902

1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
    /* 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);
1920
    }
1921

1922
1923
1924
1925
1926
1927
    /* 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));

1928
    wattroff(topwin, interface_color_pair[TITLE_BAR]);
1929

1930
    wnoutrefresh(topwin);
Chris Allegretta's avatar
Chris Allegretta committed
1931
    reset_cursor();
1932
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
1933
1934
}

1935
1936
1937
1938
1939
1940
/* Display a normal message on the statusbar, quietly. */
void statusbar(const char *msg)
{
    statusline(HUSH, msg);
}

1941
/* Display a message on the statusbar, and set suppress_cursorpos to
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1942
1943
 * TRUE, so that the message won't be immediately overwritten if
 * constant cursor position display is on. */
1944
void statusline(message_type importance, const char *msg, ...)
1945
1946
{
    va_list ap;
1947
    char *bar, *foo;
Benno Schulenberg's avatar
Benno Schulenberg committed
1948
    size_t start_x;
1949
1950
1951
1952
#ifndef NANO_TINY
    bool old_whitespace = ISSET(WHITESPACE_DISPLAY);

    UNSET(WHITESPACE_DISPLAY);
1953
#endif
1954
1955
1956
1957
1958

    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(). */
1959
    if (isendwin()) {
1960
1961
1962
1963
1964
	vfprintf(stderr, msg, ap);
	va_end(ap);
	return;
    }

1965
1966
1967
1968
1969
1970
1971
    /* 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)
1972
1973
	napms(1200);

1974
    if (importance == ALERT)
1975
	beep();
1976
1977

    lastmessage = importance;
1978

1979
1980
1981
    /* Turn the cursor off while fiddling in the statusbar. */
    curs_set(0);

1982
1983
    blank_statusbar();

1984
1985
1986
1987
    bar = charalloc(mb_cur_max() * (COLS - 3));
    vsnprintf(bar, mb_cur_max() * (COLS - 3), msg, ap);
    va_end(ap);
    foo = display_string(bar, 0, COLS - 4, FALSE);
Benno Schulenberg's avatar
Benno Schulenberg committed
1988
    free(bar);
1989
1990

#ifndef NANO_TINY
1991
1992
    if (old_whitespace)
	SET(WHITESPACE_DISPLAY);
1993
#endif
Benno Schulenberg's avatar
Benno Schulenberg committed
1994
    start_x = (COLS - strlenpt(foo) - 4) / 2;
1995

1996
    wmove(bottomwin, 0, start_x);
1997
    wattron(bottomwin, interface_color_pair[STATUS_BAR]);
1998
1999
2000
2001
    waddstr(bottomwin, "[ ");
    waddstr(bottomwin, foo);
    free(foo);
    waddstr(bottomwin, " ]");
2002
    wattroff(bottomwin, interface_color_pair[STATUS_BAR]);
2003

2004
    /* Push the message to the screen straightaway. */
2005
    wnoutrefresh(bottomwin);
2006
    doupdate();
2007

2008
    suppress_cursorpos = TRUE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2009

2010
    /* If we're doing quick statusbar blanking, blank it after just one
2011
2012
     * keystroke.  Otherwise, blank it after twenty-six keystrokes, as
     * Pico does. */
2013
#ifndef NANO_TINY
2014
2015
2016
    if (ISSET(QUICK_BLANK))
	statusblank = 1;
    else
2017
#endif
2018
	statusblank = 26;
2019
2020
}

2021
2022
/* Display the shortcut list corresponding to menu on the last two rows
 * of the bottom portion of the window. */
2023
void bottombars(int menu)
Chris Allegretta's avatar
Chris Allegretta committed
2024
{
2025
    size_t i, colwidth, slen;
2026
2027
    subnfunc *f;
    const sc *s;
2028

2029
2030
2031
    /* Set the global variable to the given menu. */
    currmenu = menu;

Chris Allegretta's avatar
Chris Allegretta committed
2032
2033
2034
    if (ISSET(NO_HELP))
	return;

2035
    if (menu == MMAIN) {
2036
	slen = MAIN_VISIBLE;
2037

2038
	assert(slen <= length_of_list(menu));
2039
    } else {
2040
	slen = length_of_list(menu);
2041

2042
	/* Don't show any more shortcuts than the main list does. */
2043
2044
2045
2046
	if (slen > MAIN_VISIBLE)
	    slen = MAIN_VISIBLE;
    }

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

2053
    blank_bottombars();
2054

2055
2056
2057
#ifdef DEBUG
    fprintf(stderr, "In bottombars, and slen == \"%d\"\n", (int) slen);
#endif
2058

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

2061
#ifdef DEBUG
2062
	fprintf(stderr, "Checking menu items....");
2063
#endif
2064
	if ((f->menus & menu) == 0)
2065
	    continue;
2066

2067
#ifdef DEBUG
2068
	fprintf(stderr, "found one! f->menus = %x, desc = \"%s\"\n", f->menus, f->desc);
2069
#endif
2070
2071
	s = first_sc_for(menu, f->scfunc);
	if (s == NULL) {
2072
2073
2074
#ifdef DEBUG
	    fprintf(stderr, "Whoops, guess not, no shortcut key found for func!\n");
#endif
2075
2076
	    continue;
	}
2077
	wmove(bottomwin, 1 + i % 2, (i / 2) * colwidth);
2078
#ifdef DEBUG
2079
	fprintf(stderr, "Calling onekey with keystr \"%s\" and desc \"%s\"\n", s->keystr, f->desc);
2080
#endif
2081
	onekey(s->keystr, _(f->desc), colwidth + (COLS % colwidth));
2082
	i++;
Chris Allegretta's avatar
Chris Allegretta committed
2083
    }
2084

2085
2086
    wnoutrefresh(bottomwin);
    reset_cursor();
2087
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
2088
2089
}

2090
2091
/* 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
2092
 * to write at most length characters, even if length is very small and
2093
2094
 * keystroke and desc are long.  Note that waddnstr(,,(size_t)-1) adds
 * the whole string!  We do not bother padding the entry with blanks. */
2095
void onekey(const char *keystroke, const char *desc, int length)
Chris Allegretta's avatar
Chris Allegretta committed
2096
{
2097
2098
    assert(keystroke != NULL && desc != NULL);

2099
    wattron(bottomwin, interface_color_pair[KEY_COMBO]);
2100
    waddnstr(bottomwin, keystroke, actual_x(keystroke, length));
2101
    wattroff(bottomwin, interface_color_pair[KEY_COMBO]);
2102

2103
    length -= strlenpt(keystroke) + 1;
2104

2105
    if (length > 0) {
2106
	waddch(bottomwin, ' ');
2107
	wattron(bottomwin, interface_color_pair[FUNCTION_TAG]);
2108
	waddnstr(bottomwin, desc, actual_x(desc, length));
2109
	wattroff(bottomwin, interface_color_pair[FUNCTION_TAG]);
Chris Allegretta's avatar
Chris Allegretta committed
2110
2111
2112
    }
}

2113
2114
/* 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
2115
2116
void reset_cursor(void)
{
2117
    size_t xpt = xplustabs();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2118

2119
#ifndef NANO_TINY
2120
    if (ISSET(SOFTWRAP)) {
2121
	filestruct *line = openfile->edittop;
2122
2123
	openfile->current_y = 0;

2124
2125
2126
2127
	while (line != NULL && line != openfile->current) {
	    openfile->current_y += strlenpt(line->data) / COLS + 1;
	    line = line->next;
	}
2128
	openfile->current_y += xpt / COLS;
2129

2130
	if (openfile->current_y < editwinrows)
2131
	    wmove(edit, openfile->current_y, xpt % COLS);
2132
2133
2134
    } else
#endif
    {
2135
	openfile->current_y = openfile->current->lineno -
2136
				openfile->edittop->lineno;
2137
2138
2139
2140

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

2143
2144
2145
2146
2147
2148
2149
2150
/* 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. */
2151
void edit_draw(filestruct *fileptr, const char *converted, int
2152
	line, size_t start)
Chris Allegretta's avatar
Chris Allegretta committed
2153
{
2154
#if !defined(NANO_TINY) || !defined(DISABLE_COLOR)
2155
2156
2157
2158
2159
2160
2161
2162
2163
    size_t startpos = actual_x(fileptr->data, start);
	/* The position in fileptr->data of the leftmost character
	 * that displays at least partially on the window. */
    size_t endpos = actual_x(fileptr->data, start + COLS - 1) + 1;
	/* The position in fileptr->data of the first character that is
	 * completely off the window to the right.
	 *
	 * Note that endpos might be beyond the null terminator of the
	 * string. */
2164
2165
#endif

2166
    assert(openfile != NULL && fileptr != NULL && converted != NULL);
2167
    assert(strlenpt(converted) <= COLS);
2168

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

2173
#ifdef USING_OLD_NCURSES
2174
2175
2176
    /* 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. */
2177
2178
    if (seen_wide)
	wredrawln(edit, line, 1);
2179
#endif
2180

2181
#ifndef DISABLE_COLOR
2182
2183
2184
    /* If color syntaxes are available and turned on, we need to display
     * them. */
    if (openfile->colorstrings != NULL && !ISSET(NO_COLOR_SYNTAX)) {
2185
	const colortype *varnish = openfile->colorstrings;
2186

2187
2188
2189
2190
	/* If there are multiline regexes, make sure there is a cache. */
	if (openfile->syntax->nmultis > 0)
	    alloc_multidata_if_needed(fileptr);

2191
	for (; varnish != NULL; varnish = varnish->next) {
2192
2193
	    int x_start;
		/* Starting column for mvwaddnstr.  Zero-based. */
2194
	    int paintlen = 0;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2195
2196
		/* Number of chars to paint on this line.  There are
		 * COLS characters on a whole line. */
2197
	    size_t index;
2198
		/* Index in converted where we paint. */
2199
2200
2201
2202
	    regmatch_t startmatch;
		/* Match position for start_regex. */
	    regmatch_t endmatch;
		/* Match position for end_regex. */
2203

2204
	    wattron(edit, varnish->attributes);
2205
2206
2207
	    /* 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. */
2208

2209
2210
	    /* First case: varnish is a single-line expression. */
	    if (varnish->end == NULL) {
2211
2212
2213
		size_t k = 0;

		/* We increment k by rm_eo, to move past the end of the
2214
		 * last match.  Even though two matches may overlap, we
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2215
2216
		 * want to ignore them, so that we can highlight e.g. C
		 * strings correctly. */
2217
2218
2219
		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
2220
2221
2222
		     * unless k is zero.  If regexec() returns
		     * REG_NOMATCH, there are no more matches in the
		     * line. */
2223
		    if (regexec(varnish->start, &fileptr->data[k], 1,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2224
2225
			&startmatch, (k == 0) ? 0 : REG_NOTBOL) ==
			REG_NOMATCH)
2226
			break;
2227
2228
		    /* Translate the match to the beginning of the
		     * line. */
2229
2230
		    startmatch.rm_so += k;
		    startmatch.rm_eo += k;
2231
2232
2233

		    /* Skip over a zero-length regex match. */
		    if (startmatch.rm_so == startmatch.rm_eo)
2234
			startmatch.rm_eo++;
2235
		    else if (startmatch.rm_so < endpos &&
2236
			startmatch.rm_eo > startpos) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2237
2238
			x_start = (startmatch.rm_so <= startpos) ? 0 :
				strnlenpt(fileptr->data,
2239
				startmatch.rm_so) - start;
2240

2241
2242
2243
			index = actual_x(converted, x_start);

			paintlen = actual_x(converted + index,
2244
2245
				strnlenpt(fileptr->data,
				startmatch.rm_eo) - start - x_start);
2246
2247
2248

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

2249
			mvwaddnstr(edit, line, x_start, converted +
2250
				index, paintlen);
2251
		    }
2252
		    k = startmatch.rm_eo;
Chris Allegretta's avatar
Chris Allegretta committed
2253
		}
2254
	    } else {	/* Second case: varnish is a multiline expression. */
2255
		const filestruct *start_line = fileptr->prev;
2256
		    /* The first line before fileptr that matches 'start'. */
2257
		size_t start_col;
2258
		    /* Where the match starts in that line. */
2259
		const filestruct *end_line;
2260
		    /* The line that matches 'end'. */
2261

2262
		/* First see if the multidata was maybe already calculated. */
2263
		if (fileptr->multidata[varnish->id] == CNONE)
2264
		    goto tail_of_loop;
2265
		else if (fileptr->multidata[varnish->id] == CWHOLELINE) {
2266
		    mvwaddnstr(edit, line, 0, converted, -1);
2267
		    goto tail_of_loop;
2268
2269
		} else if (fileptr->multidata[varnish->id] == CBEGINBEFORE) {
		    regexec(varnish->end, fileptr->data, 1, &endmatch, 0);
2270
2271
		    /* If the coloured part is scrolled off, skip it. */
		    if (endmatch.rm_eo <= startpos)
2272
			goto tail_of_loop;
2273
2274
2275
		    paintlen = actual_x(converted, strnlenpt(fileptr->data,
			endmatch.rm_eo) - start);
		    mvwaddnstr(edit, line, 0, converted, paintlen);
2276
		    goto tail_of_loop;
2277
		} if (fileptr->multidata[varnish->id] == -1)
2278
		    /* Assume this until proven otherwise below. */
2279
		    fileptr->multidata[varnish->id] = CNONE;
2280

2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
		/* 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. */

2291
		while (start_line != NULL && regexec(varnish->start,
2292
2293
2294
			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. */
2295
		    if (regexec(varnish->end, start_line->data, 0, NULL, 0) == 0)
2296
2297
2298
			goto step_two;
		    start_line = start_line->prev;
		}
2299

2300
2301
2302
2303
		/* If no start was found, skip to the next step. */
		if (start_line == NULL)
		    goto step_two;

2304
		/* If a found start has been qualified as an end earlier,
2305
		 * believe it and skip to the next step. */
2306
		if (start_line->multidata != NULL &&
2307
2308
			(start_line->multidata[varnish->id] == CBEGINBEFORE ||
			start_line->multidata[varnish->id] == CSTARTENDHERE))
2309
2310
		    goto step_two;

2311
		/* Skip over a zero-length regex match. */
2312
		if (startmatch.rm_so == startmatch.rm_eo)
2313
2314
		    goto tail_of_loop;

2315
2316
2317
2318
2319
2320
2321
		/* 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;
2322
		    if (regexec(varnish->end, start_line->data +
2323
2324
2325
				start_col + startmatch.rm_eo, 0, NULL,
				(start_col + startmatch.rm_eo == 0) ?
				0 : REG_NOTBOL) == REG_NOMATCH)
2326
2327
2328
			/* No end found after this start. */
			break;
		    start_col++;
2329
2330
		    if (regexec(varnish->start, start_line->data + start_col,
				1, &startmatch, REG_NOTBOL) == REG_NOMATCH)
2331
2332
2333
2334
2335
2336
2337
2338
2339
			/* 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;
2340
		while (end_line != NULL && regexec(varnish->end,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2341
			end_line->data, 1, &endmatch, 0) == REG_NOMATCH)
2342
		    end_line = end_line->next;
2343

2344
2345
2346
2347
		/* 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) {
2348
		    fileptr->multidata[varnish->id] = CBEGINBEFORE;
2349
2350
		    goto step_two;
		}
2351

2352
2353
2354
2355
2356
2357
2358
		/* 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;
2359
		    fileptr->multidata[varnish->id] = CWHOLELINE;
2360
#ifdef DEBUG
2361
    fprintf(stderr, "  Marking for id %i  line %i as CWHOLELINE\n", varnish->id, line);
2362
#endif
2363
2364
2365
		} else {
		    paintlen = actual_x(converted, strnlenpt(fileptr->data,
						endmatch.rm_eo) - start);
2366
		    fileptr->multidata[varnish->id] = CBEGINBEFORE;
2367
#ifdef DEBUG
2368
    fprintf(stderr, "  Marking for id %i  line %i as CBEGINBEFORE\n", varnish->id, line);
2369
#endif
2370
2371
2372
2373
2374
2375
		}
		mvwaddnstr(edit, line, 0, converted, paintlen);
		/* If the whole line has been painted, don't bother looking
		 * for any more starts. */
		if (paintlen < 0)
		    goto tail_of_loop;
2376
  step_two:
2377
2378
2379
2380
2381
		/* 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) {
2382
		    if (regexec(varnish->start, fileptr->data + start_col,
2383
2384
				1, &startmatch, (start_col == 0) ?
				0 : REG_NOTBOL) == REG_NOMATCH ||
2385
				start_col + startmatch.rm_so >= endpos)
2386
2387
			/* No more starts on this line. */
			break;
2388

2389
2390
2391
2392
2393
2394
2395
		    /* 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,
2396
				startmatch.rm_so) - start;
2397

2398
		    index = actual_x(converted, x_start);
2399

2400
		    if (regexec(varnish->end, fileptr->data +
2401
				startmatch.rm_eo, 1, &endmatch,
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
				(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 &&
2412
				endmatch.rm_eo > startmatch.rm_so) {
2413
			    paintlen = actual_x(converted + index,
2414
					strnlenpt(fileptr->data,
2415
					endmatch.rm_eo) - start - x_start);
2416

2417
			    assert(0 <= x_start && x_start < COLS);
2418

2419
			    mvwaddnstr(edit, line, x_start,
2420
					converted + index, paintlen);
2421
			    if (paintlen > 0) {
2422
				fileptr->multidata[varnish->id] = CSTARTENDHERE;
2423
#ifdef DEBUG
2424
    fprintf(stderr, "  Marking for id %i  line %i as CSTARTENDHERE\n", varnish->id, line);
2425
#endif
2426
			    }
2427
2428
			}
			start_col = endmatch.rm_eo;
2429
2430
2431
			/* Skip over a zero-length match. */
			if (endmatch.rm_so == endmatch.rm_eo)
			    start_col += 1;
2432
2433
2434
2435
		    } else {
			/* There is no end on this line.  But we haven't yet
			 * looked for one on later lines. */
			end_line = fileptr->next;
2436

2437
			while (end_line != NULL &&
2438
				regexec(varnish->end, end_line->data,
2439
				0, NULL, 0) == REG_NOMATCH)
2440
			    end_line = end_line->next;
2441

2442
2443
2444
			/* If there is no end, we're done on this line. */
			if (end_line == NULL)
			    break;
2445

2446
2447
2448
2449
			assert(0 <= x_start && x_start < COLS);

			/* Paint the rest of the line. */
			mvwaddnstr(edit, line, x_start, converted + index, -1);
2450
			fileptr->multidata[varnish->id] = CENDAFTER;
2451
#ifdef DEBUG
2452
    fprintf(stderr, "  Marking for id %i  line %i as CENDAFTER\n", varnish->id, line);
2453
#endif
2454
2455
2456
			/* We've painted to the end of the line, so don't
			 * bother checking for any more starts. */
			break;
2457
		    }
2458
2459
		}
	    }
2460
  tail_of_loop:
2461
	    wattroff(edit, varnish->attributes);
2462
	}
2463
    }
2464
#endif /* !DISABLE_COLOR */
2465

2466
#ifndef NANO_TINY
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
    /* 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. */
2478
	int x_start;
2479
	    /* The starting column for mvwaddnstr().  Zero-based. */
2480
	int paintlen;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2481
2482
	    /* Number of characters to paint on this line.  There are
	     * COLS characters on a whole line. */
2483
	size_t index;
2484
	    /* Index in converted where we paint. */
2485

2486
	mark_order(&top, &top_x, &bot, &bot_x, NULL);
2487
2488
2489
2490
2491

	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
2492

2493
	/* Only paint if the marked bit of fileptr is on this page. */
2494
2495
	if (top_x < endpos && bot_x > startpos) {
	    assert(startpos <= top_x);
2496
2497
2498
2499

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

2501
2502
2503
2504
2505
	    /* 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. */
2506
2507
2508
	    if (bot_x >= endpos)
		paintlen = -1;
	    else
2509
		paintlen = strnlenpt(fileptr->data, bot_x) - (x_start + start);
2510
2511
2512
2513
2514
2515
2516
2517

	    /* 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;
	    }
2518
2519
2520

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

2521
	    index = actual_x(converted, x_start);
2522

2523
2524
2525
	    if (paintlen > 0)
		paintlen = actual_x(converted + index, paintlen);

2526
	    wattron(edit, hilite_attribute);
2527
	    mvwaddnstr(edit, line, x_start, converted + index, paintlen);
2528
	    wattroff(edit, hilite_attribute);
Chris Allegretta's avatar
Chris Allegretta committed
2529
	}
2530
    }
2531
#endif /* !NANO_TINY */
Chris Allegretta's avatar
Chris Allegretta committed
2532
2533
}

2534
/* Just update one line in the edit buffer.  This is basically a wrapper
2535
 * for edit_draw().  The line will be displayed starting with
2536
 * fileptr->data[index].  Likely arguments are current_x or zero.
2537
 * Returns: Number of additional lines consumed (needed for SOFTWRAP). */
2538
int update_line(filestruct *fileptr, size_t index)
Chris Allegretta's avatar
Chris Allegretta committed
2539
{
2540
    int line = 0;
2541
	/* The line in the edit window that we want to update. */
2542
    int extralinesused = 0;
2543
2544
2545
2546
    char *converted;
	/* fileptr->data converted to have tabs and control characters
	 * expanded. */
    size_t page_start;
Chris Allegretta's avatar
Chris Allegretta committed
2547

2548
    assert(fileptr != NULL);
2549

2550
#ifndef NANO_TINY
2551
    if (ISSET(SOFTWRAP)) {
2552
2553
	filestruct *tmp;

2554
2555
	for (tmp = openfile->edittop; tmp && tmp != fileptr; tmp = tmp->next)
	    line += (strlenpt(tmp->data) / COLS) + 1;
2556
    } else
2557
#endif
2558
2559
	line = fileptr->lineno - openfile->edittop->lineno;

2560
    if (line < 0 || line >= editwinrows)
Chris Allegretta's avatar
Chris Allegretta committed
2561
	return 1;
2562

2563
    /* First, blank out the line. */
2564
    blank_line(edit, line, 0, COLS);
2565

2566
2567
    /* Next, convert variables that index the line to their equivalent
     * positions in the expanded line. */
2568
#ifndef NANO_TINY
2569
2570
2571
    if (ISSET(SOFTWRAP))
	index = 0;
    else
2572
#endif
2573
	index = strnlenpt(fileptr->data, index);
2574
    page_start = get_page_start(index);
2575

2576
2577
    /* Expand the line, replacing tabs with spaces, and control
     * characters with their displayed forms. */
2578
2579
2580
#ifdef NANO_TINY
    converted = display_string(fileptr->data, page_start, COLS, TRUE);
#else
2581
2582
2583
    converted = display_string(fileptr->data, page_start, COLS, !ISSET(SOFTWRAP));
#ifdef DEBUG
    if (ISSET(SOFTWRAP) && strlen(converted) >= COLS - 2)
2584
	fprintf(stderr, "update_line(): converted(1) line = %s\n", converted);
2585
#endif
2586
#endif /* !NANO_TINY */
2587

2588
    /* Paint the line. */
2589
    edit_draw(fileptr, converted, line, page_start);
2590
    free(converted);
Chris Allegretta's avatar
Chris Allegretta committed
2591

2592
#ifndef NANO_TINY
2593
    if (!ISSET(SOFTWRAP)) {
2594
#endif
2595
2596
2597
2598
	if (page_start > 0)
	    mvwaddch(edit, line, 0, '$');
	if (strlenpt(fileptr->data) > page_start + COLS)
	    mvwaddch(edit, line, COLS - 1, '$');
2599
#ifndef NANO_TINY
2600
    } else {
2601
	size_t full_length = strlenpt(fileptr->data);
2602
	for (index += COLS; index <= full_length && line < editwinrows - 1; index += COLS) {
2603
2604
	    line++;
#ifdef DEBUG
2605
	    fprintf(stderr, "update_line(): softwrap code, moving to %d index %lu\n", line, (unsigned long)index);
2606
#endif
2607
	    blank_line(edit, line, 0, COLS);
2608
2609

	    /* Expand the line, replacing tabs with spaces, and control
2610
	     * characters with their displayed forms. */
2611
2612
2613
2614
2615
	    converted = display_string(fileptr->data, index, COLS, !ISSET(SOFTWRAP));
#ifdef DEBUG
	    if (ISSET(SOFTWRAP) && strlen(converted) >= COLS - 2)
		fprintf(stderr, "update_line(): converted(2) line = %s\n", converted);
#endif
2616
2617
2618

	    /* Paint the line. */
	    edit_draw(fileptr, converted, line, index);
2619
	    free(converted);
2620
2621
2622
	    extralinesused++;
	}
    }
2623
#endif /* !NANO_TINY */
2624
    return extralinesused;
Chris Allegretta's avatar
Chris Allegretta committed
2625
2626
}

2627
2628
2629
/* 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)
2630
{
2631
#ifndef NANO_TINY
2632
2633
2634
    if (openfile->mark_set)
	return TRUE;
    else
2635
#endif
2636
	return (get_page_start(old_column) != get_page_start(new_column));
2637
2638
}

2639
/* When edittop changes, try and figure out how many lines
2640
 * we really have to work with (i.e. set maxrows). */
2641
2642
2643
2644
2645
void compute_maxrows(void)
{
    int n;
    filestruct *foo = openfile->edittop;

2646
2647
2648
2649
2650
    if (!ISSET(SOFTWRAP)) {
	maxrows = editwinrows;
	return;
    }

2651
2652
    maxrows = 0;
    for (n = 0; n < editwinrows && foo; n++) {
2653
	maxrows++;
2654
	n += strlenpt(foo->data) / COLS;
2655
2656
2657
	foo = foo->next;
    }

2658
2659
2660
    if (n < editwinrows)
	maxrows += editwinrows - n;

2661
#ifdef DEBUG
2662
    fprintf(stderr, "compute_maxrows(): maxrows = %d\n", maxrows);
2663
2664
2665
#endif
}

2666
2667
/* 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
2668
2669
 * scrolling.  direction is the direction to scroll, either UPWARD or
 * DOWNWARD, and nlines is the number of lines to scroll.  We change
2670
2671
 * edittop, and assume that current and current_x are up to date.  We
 * also assume that scrollok(edit) is FALSE. */
2672
void edit_scroll(scroll_dir direction, ssize_t nlines)
2673
{
2674
    ssize_t i;
2675
    filestruct *foo;
2676

2677
    assert(nlines > 0);
2678

2679
2680
2681
    /* Part 1: nlines is the number of lines we're going to scroll the
     * text of the edit window. */

2682
    /* Move the top line of the edit window up or down (depending on the
2683
2684
     * value of direction) nlines lines, or as many lines as we can if
     * there are fewer than nlines lines available. */
2685
    for (i = nlines; i > 0; i--) {
2686
	if (direction == UPWARD) {
2687
	    if (openfile->edittop == openfile->fileage)
2688
		break;
2689
	    openfile->edittop = openfile->edittop->prev;
2690
	} else {
2691
	    if (openfile->edittop == openfile->filebot)
2692
		break;
2693
	    openfile->edittop = openfile->edittop->next;
2694
	}
2695
2696

#ifndef NANO_TINY
2697
	/* Don't over-scroll on long lines. */
2698
	if (ISSET(SOFTWRAP) && direction == UPWARD) {
2699
	    ssize_t len = strlenpt(openfile->edittop->data) / COLS;
2700
	    i -= len;
2701
	    if (len > 0)
2702
		refresh_needed = TRUE;
2703
	}
2704
#endif
2705
2706
    }

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

2710
2711
2712
2713
    /* Don't bother scrolling zero lines, nor more than the window can hold. */
    if (nlines == 0)
	return;
    if (nlines >= editwinrows)
2714
	refresh_needed = TRUE;
2715

2716
    if (refresh_needed == TRUE)
2717
	return;
2718
2719
2720

    /* Scroll the text of the edit window up or down nlines lines,
     * depending on the value of direction. */
2721
    scrollok(edit, TRUE);
2722
    wscrl(edit, (direction == UPWARD) ? -nlines : nlines);
2723
2724
    scrollok(edit, FALSE);

2725
2726
2727
    /* Part 2: nlines is the number of lines in the scrolled region of
     * the edit window that we need to draw. */

2728
2729
2730
2731
2732
2733
    /* 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
2734

2735
2736
    if (nlines > editwinrows)
	nlines = editwinrows;
2737
2738
2739

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

2742
2743
    /* If we scrolled down, move down to the line before the scrolled
     * region. */
2744
    if (direction == DOWNWARD) {
2745
	for (i = editwinrows - nlines; i > 0 && foo != NULL; i--)
2746
2747
2748
	    foo = foo->next;
    }

2749
2750
2751
2752
2753
2754
    /* 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--) {
2755
2756
	if ((i == nlines && direction == DOWNWARD) || (i == 1 &&
		direction == UPWARD)) {
2757
	    if (need_horizontal_scroll(openfile->placewewant, 0))
2758
2759
2760
2761
		update_line(foo, (foo == openfile->current) ?
			openfile->current_x : 0);
	} else
	    update_line(foo, (foo == openfile->current) ?
2762
		openfile->current_x : 0);
2763
	foo = foo->next;
2764
    }
2765
    compute_maxrows();
2766
2767
2768
}

/* Update any lines between old_current and current that need to be
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2769
 * updated.  Use this if we've moved without changing any text. */
2770
void edit_redraw(filestruct *old_current)
2771
{
2772
2773
2774
2775
    size_t was_pww = openfile->placewewant;

    openfile->placewewant = xplustabs();

2776
2777
    /* If the current line is offscreen, scroll until it's onscreen. */
    if (openfile->current->lineno >= openfile->edittop->lineno + maxrows ||
2778
		openfile->current->lineno < openfile->edittop->lineno) {
2779
	edit_update((focusing || !ISSET(SMOOTH_SCROLL)) ? CENTERING : FLOWING);
2780
	refresh_needed = TRUE;
2781
    }
2782

2783
#ifndef NANO_TINY
2784
2785
2786
    /* If the mark is on, update all lines between old_current and current. */
    if (openfile->mark_set) {
	filestruct *foo = old_current;
2787

2788
	while (foo != openfile->current) {
2789
	    update_line(foo, 0);
2790

2791
2792
2793
	    foo = (foo->lineno > openfile->current->lineno) ?
			foo->prev : foo->next;
	}
2794
2795
2796
2797
2798
2799
    } 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);
2800
2801
2802

    /* Update current if we've changed page, or if it differs from
     * old_current and needs to be horizontally scrolled. */
2803
    if (need_horizontal_scroll(was_pww, openfile->placewewant) ||
2804
			(old_current != openfile->current &&
2805
			get_page_start(openfile->placewewant) > 0))
2806
	update_line(openfile->current, openfile->current_x);
2807
2808
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2809
2810
/* 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
2811
2812
void edit_refresh(void)
{
2813
    filestruct *foo;
2814
    int nlines;
2815

2816
    /* Figure out what maxrows should really be. */
2817
    compute_maxrows();
2818

2819
2820
    if (openfile->current->lineno < openfile->edittop->lineno ||
	openfile->current->lineno >= openfile->edittop->lineno +
2821
2822
	maxrows) {
#ifdef DEBUG
2823
2824
	fprintf(stderr, "edit_refresh(): line = %ld, edittop %ld + maxrows %d\n",
		(long)openfile->current->lineno, (long)openfile->edittop->lineno, maxrows);
2825
2826
#endif

2827
	/* Make sure the current line is on the screen. */
2828
	edit_update((focusing || !ISSET(SMOOTH_SCROLL)) ? CENTERING : STATIONARY);
2829
    }
Chris Allegretta's avatar
Chris Allegretta committed
2830

2831
2832
    foo = openfile->edittop;

2833
#ifdef DEBUG
2834
    fprintf(stderr, "edit_refresh(): edittop->lineno = %ld\n", (long)openfile->edittop->lineno);
2835
#endif
2836

2837
    for (nlines = 0; nlines < editwinrows && foo != NULL; nlines++) {
2838
	nlines += update_line(foo, (foo == openfile->current) ?
2839
		openfile->current_x : 0);
2840
2841
2842
	foo = foo->next;
    }

2843
    for (; nlines < editwinrows; nlines++)
2844
2845
2846
	blank_line(edit, nlines, 0, COLS);

    reset_cursor();
2847
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
2848
2849
}

2850
2851
2852
2853
2854
2855
/* 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
2856
{
2857
    int goal = 0;
2858

2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
    /* 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)
2869
	goal = editwinrows / 2;
2870
    else if (manner == FLOWING) {
2871
	if (openfile->current->lineno >= openfile->edittop->lineno)
2872
2873
	    goal = editwinrows - 1;
    } else {
2874
	goal = openfile->current_y;
2875

2876
	/* Limit goal to (editwinrows - 1) lines maximum. */
2877
2878
	if (goal > editwinrows - 1)
	    goal = editwinrows - 1;
Chris Allegretta's avatar
Chris Allegretta committed
2879
    }
2880

2881
2882
2883
2884
2885
    openfile->edittop = openfile->current;

    while (goal > 0 && openfile->edittop->prev != NULL) {
	openfile->edittop = openfile->edittop->prev;
	goal --;
2886
#ifndef NANO_TINY
2887
2888
	if (ISSET(SOFTWRAP))
	    goal -= strlenpt(openfile->edittop->data) / COLS;
2889
#endif
2890
    }
2891
#ifdef DEBUG
2892
    fprintf(stderr, "edit_update(): setting edittop to lineno %ld\n", (long)openfile->edittop->lineno);
2893
#endif
2894
    compute_maxrows();
Chris Allegretta's avatar
Chris Allegretta committed
2895
2896
}

2897
/* Unconditionally redraw the entire screen. */
2898
void total_redraw(void)
2899
{
2900
2901
2902
2903
2904
2905
#ifdef USE_SLANG
    /* Slang curses emulation brain damage, part 4: Slang doesn't define
     * curscr. */
    SLsmg_touch_screen();
    SLsmg_refresh();
#else
2906
    wrefresh(curscr);
2907
#endif
2908
2909
}

2910
2911
/* Unconditionally redraw the entire screen, and then refresh it using
 * the current file. */
2912
2913
void total_refresh(void)
{
2914
    total_redraw();
2915
    titlebar(NULL);
2916
    edit_refresh();
2917
    bottombars(currmenu);
2918
2919
}

2920
2921
/* Display the main shortcut list on the last two rows of the bottom
 * portion of the window. */
2922
2923
void display_main_list(void)
{
2924
#ifndef DISABLE_COLOR
2925
2926
    if (openfile->syntax &&
		(openfile->syntax->formatter || openfile->syntax->linter))
2927
	set_lint_or_format_shortcuts();
2928
2929
2930
2931
    else
	set_spell_shortcuts();
#endif

2932
    bottombars(MMAIN);
2933
2934
}

2935
/* If constant is TRUE, we display the current cursor position only if
2936
2937
 * suppress_cursorpos is FALSE.  If constant is FALSE, we display the
 * position always.  In any case we reset suppress_cursorpos to FALSE. */
2938
void do_cursorpos(bool constant)
Chris Allegretta's avatar
Chris Allegretta committed
2939
{
2940
    filestruct *f;
2941
    char c;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2942
    size_t i, cur_xpt = xplustabs() + 1;
2943
    size_t cur_lenpt = strlenpt(openfile->current->data) + 1;
2944
    int linepct, colpct, charpct;
Chris Allegretta's avatar
Chris Allegretta committed
2945

2946
    assert(openfile->fileage != NULL && openfile->current != NULL);
2947

2948
    /* Determine the size of the file up to the cursor. */
2949
    f = openfile->current->next;
2950
    c = openfile->current->data[openfile->current_x];
2951
2952

    openfile->current->next = NULL;
2953
    openfile->current->data[openfile->current_x] = '\0';
2954
2955
2956

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

2957
    openfile->current->data[openfile->current_x] = c;
2958
    openfile->current->next = f;
2959

2960
    /* If the position needs to be suppressed, don't suppress it next time. */
2961
    if (suppress_cursorpos && constant) {
2962
	suppress_cursorpos = FALSE;
2963
	return;
2964
    }
Chris Allegretta's avatar
Chris Allegretta committed
2965

2966
    /* Display the current cursor position on the statusbar. */
2967
    linepct = 100 * openfile->current->lineno / openfile->filebot->lineno;
2968
    colpct = 100 * cur_xpt / cur_lenpt;
2969
    charpct = (openfile->totsize == 0) ? 0 : 100 * i / openfile->totsize;
2970

2971
    statusline(HUSH,
2972
	_("line %ld/%ld (%d%%), col %lu/%lu (%d%%), char %lu/%lu (%d%%)"),
2973
	(long)openfile->current->lineno,
2974
	(long)openfile->filebot->lineno, linepct,
2975
	(unsigned long)cur_xpt, (unsigned long)cur_lenpt, colpct,
2976
	(unsigned long)i, (unsigned long)openfile->totsize, charpct);
2977
2978
2979

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

2982
/* Unconditionally display the current cursor position. */
2983
void do_cursorpos_void(void)
2984
{
2985
    do_cursorpos(FALSE);
2986
2987
}

2988
2989
void enable_nodelay(void)
{
2990
2991
    nodelay_mode = TRUE;
    nodelay(edit, TRUE);
2992
2993
2994
2995
}

void disable_nodelay(void)
{
2996
2997
    nodelay_mode = FALSE;
    nodelay(edit, FALSE);
2998
2999
}

3000
3001
/* Highlight the current word being replaced or spell checked.  We
 * expect word to have tabs and control characters expanded. */
3002
void spotlight(bool active, const char *word)
Chris Allegretta's avatar
Chris Allegretta committed
3003
{
3004
    size_t word_len = strlenpt(word), room;
Chris Allegretta's avatar
Chris Allegretta committed
3005

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

3009
    assert(room > 0);
3010

3011
3012
    if (word_len > room)
	room--;
3013

Chris Allegretta's avatar
Chris Allegretta committed
3014
    reset_cursor();
3015
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
3016

3017
    if (active)
3018
	wattron(edit, hilite_attribute);
Chris Allegretta's avatar
Chris Allegretta committed
3019

3020
    /* This is so we can show zero-length matches. */
3021
    if (word_len == 0)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3022
	waddch(edit, ' ');
3023
    else
3024
	waddnstr(edit, word, actual_x(word, room));
3025

3026
    if (word_len > room)
3027
	waddch(edit, '$');
Chris Allegretta's avatar
Chris Allegretta committed
3028

3029
    if (active)
3030
	wattroff(edit, hilite_attribute);
Chris Allegretta's avatar
Chris Allegretta committed
3031
3032
}

3033
#ifndef DISABLE_EXTRA
3034
3035
#define CREDIT_LEN 54
#define XLCREDIT_LEN 9
3036

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3037
3038
/* Easter egg: Display credits.  Assume nodelay(edit) and scrollok(edit)
 * are FALSE. */
3039
3040
void do_credits(void)
{
3041
3042
    bool old_more_space = ISSET(MORE_SPACE);
    bool old_no_help = ISSET(NO_HELP);
3043
    int kbinput = ERR, crpos = 0, xlpos = 0;
3044
3045
3046
    const char *credits[CREDIT_LEN] = {
	NULL,				/* "The nano text editor" */
	NULL,				/* "version" */
Chris Allegretta's avatar
Chris Allegretta committed
3047
3048
	VERSION,
	"",
3049
	NULL,				/* "Brought to you by:" */
Chris Allegretta's avatar
Chris Allegretta committed
3050
3051
3052
3053
3054
	"Chris Allegretta",
	"Jordi Mallach",
	"Adam Rogoyski",
	"Rob Siemborski",
	"Rocco Corsi",
3055
	"David Lawrence Ramsey",
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3056
	"David Benbennick",
Benno Schulenberg's avatar
Benno Schulenberg committed
3057
	"Mark Majeres",
3058
	"Mike Frysinger",
3059
	"Benno Schulenberg",
Chris Allegretta's avatar
Chris Allegretta committed
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
	"Ken Tyler",
	"Sven Guckes",
	"Bill Soudan",
	"Christian Weisgerber",
	"Erik Andersen",
	"Big Gaute",
	"Joshua Jensen",
	"Ryan Krebs",
	"Albert Chin",
	"",
3070
	NULL,				/* "Special thanks to:" */
3071
	"Monique, Brielle & Joseph",
Chris Allegretta's avatar
Chris Allegretta committed
3072
3073
3074
3075
3076
3077
	"Plattsburgh State University",
	"Benet Laboratories",
	"Amy Allegretta",
	"Linda Young",
	"Jeremy Robichaud",
	"Richard Kolb II",
3078
	NULL,				/* "The Free Software Foundation" */
Chris Allegretta's avatar
Chris Allegretta committed
3079
	"Linus Torvalds",
3080
	NULL,				/* "the many translators and the TP" */
3081
	NULL,				/* "For ncurses:" */
3082
3083
3084
3085
	"Thomas Dickey",
	"Pavel Curtis",
	"Zeyd Ben-Halim",
	"Eric S. Raymond",
3086
3087
3088
3089
3090
3091
	NULL,				/* "and anyone else we forgot..." */
	NULL,				/* "Thank you for using nano!" */
	"",
	"",
	"",
	"",
3092
	"(C) 1999 - 2016",
3093
	"Free Software Foundation, Inc.",
3094
3095
3096
3097
	"",
	"",
	"",
	"",
3098
	"https://nano-editor.org/"
3099
3100
    };

3101
    const char *xlcredits[XLCREDIT_LEN] = {
3102
3103
3104
3105
3106
	N_("The nano text editor"),
	N_("version"),
	N_("Brought to you by:"),
	N_("Special thanks to:"),
	N_("The Free Software Foundation"),
3107
	N_("the many translators and the TP"),
3108
3109
3110
	N_("For ncurses:"),
	N_("and anyone else we forgot..."),
	N_("Thank you for using nano!")
3111
    };
3112

3113
3114
3115
3116
3117
3118
    if (!old_more_space || !old_no_help) {
	SET(MORE_SPACE);
	SET(NO_HELP);
	window_init();
    }

3119
3120
    curs_set(0);
    nodelay(edit, TRUE);
3121

3122
    blank_titlebar();
Chris Allegretta's avatar
Chris Allegretta committed
3123
    blank_edit();
3124
    blank_statusbar();
3125

3126
    wrefresh(topwin);
Chris Allegretta's avatar
Chris Allegretta committed
3127
    wrefresh(edit);
3128
    wrefresh(bottomwin);
3129
    napms(700);
3130

3131
    for (crpos = 0; crpos < CREDIT_LEN + editwinrows / 2; crpos++) {
3132
	if ((kbinput = wgetch(edit)) != ERR)
3133
	    break;
3134

3135
	if (crpos < CREDIT_LEN) {
3136
	    const char *what;
3137
3138
	    size_t start_x;

3139
	    if (credits[crpos] == NULL) {
3140
		assert(0 <= xlpos && xlpos < XLCREDIT_LEN);
3141

3142
		what = _(xlcredits[xlpos]);
3143
		xlpos++;
3144
	    } else
3145
		what = credits[crpos];
3146

3147
	    start_x = COLS / 2 - strlenpt(what) / 2 - 1;
3148
3149
	    mvwaddstr(edit, editwinrows - 1 - (editwinrows % 2),
		start_x, what);
3150
	}
3151

3152
3153
3154
3155
	wrefresh(edit);

	if ((kbinput = wgetch(edit)) != ERR)
	    break;
3156
	napms(700);
3157

3158
	scrollok(edit, TRUE);
3159
	wscrl(edit, 1);
3160
	scrollok(edit, FALSE);
3161
	wrefresh(edit);
3162

3163
	if ((kbinput = wgetch(edit)) != ERR)
3164
	    break;
3165
	napms(700);
3166

3167
	scrollok(edit, TRUE);
3168
	wscrl(edit, 1);
3169
	scrollok(edit, FALSE);
3170
	wrefresh(edit);
3171
3172
    }

3173
3174
3175
    if (kbinput != ERR)
	ungetch(kbinput);

3176
    if (!old_more_space)
3177
	UNSET(MORE_SPACE);
3178
    if (!old_no_help)
3179
	UNSET(NO_HELP);
3180
    window_init();
3181

3182
    nodelay(edit, FALSE);
3183

3184
    total_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
3185
}
3186
#endif /* !DISABLE_EXTRA */