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

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

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

32
33
34
35
36
37
#ifdef REVISION
#define BRANDING REVISION
#else
#define BRANDING PACKAGE_STRING
#endif

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

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

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

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

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

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

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

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

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

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

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

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

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

    while (TRUE) {
186
	input = wgetch(win);
187

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

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

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

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

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

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

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

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

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

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

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

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

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

271
272
    if (key_buffer_len == 0 && win != NULL)
	get_key_buffer(win);
273

274
275
    if (key_buffer_len == 0)
	return NULL;
276

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

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

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

    return input;
299
300
}

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

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

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

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

319
320
321
    return kbinput;
}

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

334
    meta_key = FALSE;
335

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

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

    while (kbinput == NULL)
343
	kbinput = get_input(win, 1);
344

345
346
347
    keycode = *kbinput;
    free(kbinput);

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

353
354
355
    if (keycode == ERR)
	return ERR;

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

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

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

427
428
			/* Convert the decimal code to one or two bytes. */
			byte_mb = make_mbchar((long)byte, &byte_mb_len);
429

430
			seq = (int *)nmalloc(byte_mb_len * sizeof(int));
431
432
433
434

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

435
			/* Insert the byte(s) into the input buffer. */
436
437
438
439
			unget_input(seq, byte_mb_len);

			free(byte_mb);
			free(seq);
440
441
442

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

489
490
491
    if (retval == ERR)
	return ERR;

492
493
494
495
496
497
498
499
500
501
502
#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

503
    switch (retval) {
504
#ifdef KEY_SLEFT
505
506
	/* Slang doesn't support KEY_SLEFT. */
	case KEY_SLEFT:
507
#endif
508
509
	case KEY_LEFT:
	    return sc_seq_or(do_left, keycode);
510
#ifdef KEY_SRIGHT
511
512
	/* Slang doesn't support KEY_SRIGHT. */
	case KEY_SRIGHT:
513
#endif
514
515
	case KEY_RIGHT:
	    return sc_seq_or(do_right, keycode);
516
#ifdef KEY_SUP
517
518
	/* ncurses and Slang don't support KEY_SUP. */
	case KEY_SUP:
519
#endif
520
521
	case KEY_UP:
	    return sc_seq_or(do_up_void, keycode);
522
#ifdef KEY_SDOWN
523
524
	/* ncurses and Slang don't support KEY_SDOWN. */
	case KEY_SDOWN:
525
#endif
526
527
	case KEY_DOWN:
	    return sc_seq_or(do_down_void, keycode);
528
#ifdef KEY_SHOME
529
530
	/* HP-UX 10-11 and Slang don't support KEY_SHOME. */
	case KEY_SHOME:
531
#endif
532
#ifdef KEY_HOME
533
	case KEY_HOME:
534
#endif
535
536
	case KEY_A1:	/* Home (7) on keypad with NumLock off. */
	    return sc_seq_or(do_home, keycode);
537
#ifdef KEY_SEND
538
539
	/* HP-UX 10-11 and Slang don't support KEY_SEND. */
	case KEY_SEND:
540
541
#endif
#ifdef KEY_END
542
	case KEY_END:
543
#endif
544
545
546
547
548
549
550
551
552
553
554
555
556
	case KEY_C1:	/* End (1) on keypad with NumLock off. */
	    return sc_seq_or(do_end, keycode);
	case KEY_PPAGE:
	case KEY_A3:	/* PageUp (9) on keypad with NumLock off. */
	    return sc_seq_or(do_page_up, keycode);
	case KEY_NPAGE:
	case KEY_C3:	/* PageDown (3) on keypad with NumLock off. */
	    return sc_seq_or(do_page_down, keycode);

	case KEY_ENTER:
	    return sc_seq_or(do_enter, keycode);
	case KEY_BACKSPACE:
	    return sc_seq_or(do_backspace, keycode);
557
#ifdef KEY_SDC
558
559
	/* Slang doesn't support KEY_SDC. */
	case KEY_SDC:
560
#endif
561
	case DEL_CODE:
562
563
564
565
	    if (ISSET(REBIND_DELETE))
		return sc_seq_or(do_delete, keycode);
	    else
		return sc_seq_or(do_backspace, keycode);
566
#ifdef KEY_SIC
567
568
569
	/* Slang doesn't support KEY_SIC. */
	case KEY_SIC:
	    return sc_seq_or(do_insertfile_void, keycode);
570
#endif
571
#ifdef KEY_SBEG
572
573
	/* Slang doesn't support KEY_SBEG. */
	case KEY_SBEG:
574
#endif
575
#ifdef KEY_BEG
576
577
	/* Slang doesn't support KEY_BEG. */
	case KEY_BEG:
578
#endif
579
580
	case KEY_B2:	/* Center (5) on keypad with NumLock off. */
	    return ERR;
581
#ifdef KEY_CANCEL
582
#ifdef KEY_SCANCEL
583
584
	/* Slang doesn't support KEY_SCANCEL. */
	case KEY_SCANCEL:
585
#endif
586
587
588
	/* Slang doesn't support KEY_CANCEL. */
	case KEY_CANCEL:
	    return first_sc_for(currmenu, do_cancel)->keycode;
589
#endif
590
#ifdef KEY_SUSPEND
591
#ifdef KEY_SSUSPEND
592
593
	/* Slang doesn't support KEY_SSUSPEND. */
	case KEY_SSUSPEND:
594
#endif
595
596
597
	/* Slang doesn't support KEY_SUSPEND. */
	case KEY_SUSPEND:
	    return sc_seq_or(do_suspend_void, 0);
598
599
#endif
#ifdef PDCURSES
600
601
602
603
604
605
606
	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;
607
608
#endif
#if !defined(NANO_TINY) && defined(KEY_RESIZE)
609
610
611
612
613
	/* 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;
614
#endif
615
    }
616

617
618
619
    return retval;
}

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

633
634
	switch (seq[3]) {
	    case '2':
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
		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);
		}
650
651
		break;
	    case '5':
652
653
654
655
656
657
658
659
660
661
		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;
		}
662
663
		break;
	}
664

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

838
	switch (seq[3]) {
839
	    case '2':
840
841
842
843
844
845
846
		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]);
		}
847
848
		break;
	    case '5':
849
850
851
852
853
854
855
856
857
858
		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;
		}
859
860
		break;
	}
861
862
863
864

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

1022
    return ERR;
1023
1024
}

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

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

    free(seq);

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

1076
#ifdef DEBUG
1077
1078
    fprintf(stderr, "parse_escape_sequence(): kbinput = %d, seq_len = %lu, retval = %d\n",
		kbinput, (unsigned long)seq_len, retval);
1079
1080
1081
1082
1083
#endif

    return retval;
}

1084
1085
/* Translate a byte sequence: turn a three-digit decimal number (from
 * 000 to 255) into its corresponding byte value. */
1086
int get_byte_kbinput(int kbinput)
1087
{
1088
    static int byte_digits = 0, byte = 0;
1089
    int retval = ERR;
1090

1091
1092
    /* Increment the byte digit counter. */
    byte_digits++;
1093

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

    /* If we have a result, reset the byte digit counter and the byte
     * sequence holder. */
    if (retval != ERR) {
	byte_digits = 0;
1145
	byte = 0;
1146
1147
1148
    }

#ifdef DEBUG
1149
    fprintf(stderr, "get_byte_kbinput(): kbinput = %d, byte_digits = %d, byte = %d, retval = %d\n", kbinput, byte_digits, byte, retval);
1150
1151
1152
1153
1154
#endif

    return retval;
}

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

1168
    return ERR;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1169
1170
}

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

1180
    /* Increment the Unicode digit counter. */
1181
    uni_digits++;
1182

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

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

1233
#ifdef DEBUG
1234
1235
    fprintf(stderr, "get_unicode_kbinput(): kbinput = %d, uni_digits = %d, uni = %ld, retval = %ld\n",
						kbinput, uni_digits, uni, retval);
1236
1237
#endif

1238
1239
1240
1241
    /* If we have an end result, reset the Unicode digit counter. */
    if (retval != ERR)
	uni_digits = 0;

1242
1243
    return retval;
}
1244
#endif /* ENABLE_UTF8 */
1245

1246
1247
1248
1249
1250
1251
/* Translate a control character sequence: turn an ASCII non-control
 * character into its corresponding control character. */
int get_control_kbinput(int kbinput)
{
    int retval;

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

1273
#ifdef DEBUG
1274
    fprintf(stderr, "get_control_kbinput(): kbinput = %d, retval = %d\n", kbinput, retval);
1275
1276
#endif

1277
1278
    return retval;
}
1279

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

1287
1288
1289
1290
    if (output_len == 0)
	return;

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

1292
1293
    for (i = 0; i < output_len; i++)
	input[i] = (int)output[i];
1294

1295
    unget_input(input, output_len);
1296

1297
    free(input);
1298
1299
}

1300
/* Read in a stream of characters verbatim, and return the length of the
1301
1302
1303
1304
 * string in kbinput_len.  Assume nodelay(win) is FALSE. */
int *get_verbatim_kbinput(WINDOW *win, size_t *kbinput_len)
{
    int *retval;
1305

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

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

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

1328
    return retval;
1329
1330
}

1331
1332
/* Read in a stream of all available characters, and return the length
 * of the string in kbinput_len.  Translate the first few characters of
1333
 * the input into the corresponding multibyte value if possible.  After
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1334
 * that, leave the input as-is. */
1335
int *parse_verbatim_kbinput(WINDOW *win, size_t *kbinput_len)
1336
{
1337
    int *kbinput, *retval;
1338

1339
    /* Read in the first code. */
1340
1341
    while ((kbinput = get_input(win, 1)) == NULL)
	;
1342

1343
#ifndef NANO_TINY
1344
1345
1346
1347
1348
1349
    /* When the window was resized, abort and return nothing. */
    if (*kbinput == KEY_WINCH) {
	*kbinput_len = 0;
	free(kbinput);
	return NULL;
    }
1350
#endif
1351

1352
#ifdef ENABLE_UTF8
1353
    if (using_utf8()) {
1354
	/* Check whether the first code is a valid starter digit: 0 or 1. */
1355
	long uni = get_unicode_kbinput(win, *kbinput);
1356

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

	    while (uni == ERR) {
1367
		free(kbinput);
1368
1369
		while ((kbinput = get_input(win, 1)) == NULL)
		    ;
1370
		uni = get_unicode_kbinput(win, *kbinput);
1371
	    }
1372

1373
	    /* Convert the Unicode value to a multibyte sequence. */
1374
	    uni_mb = make_mbchar(uni, &uni_mb_len);
1375

1376
	    seq = (int *)nmalloc(uni_mb_len * sizeof(int));
1377

1378
1379
	    for (i = 0; i < uni_mb_len; i++)
		seq[i] = (unsigned char)uni_mb[i];
1380

1381
	    /* Insert the multibyte sequence into the input buffer. */
1382
	    unget_input(seq, uni_mb_len);
1383

1384
1385
	    free(seq);
	    free(uni_mb);
1386
	}
1387
    } else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1388
1389
#endif /* ENABLE_UTF8 */

1390
	/* Put back the first code. */
1391
	unget_input(kbinput, 1);
1392

1393
1394
    free(kbinput);

1395
    /* Get the complete sequence, and save the characters in it as the
1396
     * result. */
1397
    *kbinput_len = key_buffer_len;
1398
    retval = get_input(NULL, *kbinput_len);
1399
1400
1401
1402

    return retval;
}

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

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

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

1431
1432
1433
    /* Save the screen coordinates where the mouse event took place. */
    *mouse_x = mevent.x;
    *mouse_y = mevent.y;
1434

1435
1436
    in_bottomwin = wenclose(bottomwin, *mouse_y, *mouse_x);

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

1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
	    /* 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;
	    }
1468

1469
	    /* Get the shortcut lists' length. */
1470
	    if (currmenu == MMAIN)
1471
		currslen = MAIN_VISIBLE;
1472
	    else {
1473
		currslen = length_of_list(currmenu);
1474

1475
1476
1477
1478
1479
		/* We don't show any more shortcuts than the main list
		 * does. */
		if (currslen > MAIN_VISIBLE)
		    currslen = MAIN_VISIBLE;
	    }
1480

1481
1482
1483
1484
1485
1486
1487
1488
	    /* 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));

1489
1490
	    /* Calculate the one-based index in the shortcut list. */
	    j = (*mouse_x / i) * 2 + *mouse_y;
1491

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

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

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

1537
1538
1539
1540
	if (in_bottomwin)
	    /* Translate the mouse event coordinates so that they're
	     * relative to bottomwin. */
	    wmouse_trafo(bottomwin, mouse_y, mouse_x, FALSE);
1541

1542
	if (in_edit || (in_bottomwin && *mouse_y == 0)) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1543
1544
	    int i;

1545
1546
1547
1548
1549
	    /* 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) ?
1550
				KEY_PPAGE : KEY_NPAGE, FALSE);
1551
1552
1553
1554
1555
1556
1557

	    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;
1558
1559
    }
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1560
1561
1562

    /* Ignore all other mouse events. */
    return 2;
1563
}
1564
1565
#endif /* !DISABLE_MOUSE */

1566
1567
1568
1569
/* 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. */
1570
const sc *get_shortcut(int *kbinput)
1571
{
1572
    sc *s;
1573

1574
#ifdef DEBUG
1575
1576
    fprintf(stderr, "get_shortcut(): kbinput = %d, meta_key = %s -- ",
				*kbinput, meta_key ? "TRUE" : "FALSE");
1577
1578
#endif

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

    return NULL;
}

1596
1597
1598
1599
1600
/* 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
1601

1602
1603
1604
1605
    for (; n > 0; n--)
	waddch(win, ' ');
}

1606
/* Blank the first line of the top portion of the window. */
1607
void blank_titlebar(void)
Chris Allegretta's avatar
Chris Allegretta committed
1608
{
1609
    blank_line(topwin, 0, 0, COLS);
1610
1611
}

1612
/* Blank all the lines of the middle portion of the window, i.e. the
1613
 * edit window. */
Chris Allegretta's avatar
Chris Allegretta committed
1614
1615
void blank_edit(void)
{
1616
    int i;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1617

1618
    for (i = 0; i < editwinrows; i++)
1619
	blank_line(edit, i, 0, COLS);
Chris Allegretta's avatar
Chris Allegretta committed
1620
1621
}

1622
/* Blank the first line of the bottom portion of the window. */
Chris Allegretta's avatar
Chris Allegretta committed
1623
1624
void blank_statusbar(void)
{
1625
    blank_line(bottomwin, 0, 0, COLS);
Chris Allegretta's avatar
Chris Allegretta committed
1626
1627
}

1628
1629
/* If the NO_HELP flag isn't set, blank the last two lines of the bottom
 * portion of the window. */
1630
1631
1632
void blank_bottombars(void)
{
    if (!ISSET(NO_HELP)) {
1633
1634
	blank_line(bottomwin, 1, 0, COLS);
	blank_line(bottomwin, 2, 0, COLS);
1635
1636
1637
    }
}

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

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

1679
1680
    /* If dollars is TRUE, make room for the "$" at the end of the
     * line. */
1681
1682
    if (dollars && span > 0 && strlenpt(buf) > start_col + span)
	span--;
1683

1684
    if (span == 0)
1685
1686
1687
1688
	return mallocstrcpy(NULL, "");

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

1690
    assert(column <= start_col);
1691

1692
1693
    /* Allocate enough space to hold the entire converted buffer. */
    converted = charalloc(strlen(buf) * (mb_cur_max() + tabsize) + 1);
1694

1695
    index = 0;
1696
#ifdef USING_OLD_NCURSES
1697
    seen_wide = FALSE;
1698
#endif
1699
    buf += start_index;
1700

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

1719
	    converted[index++] = ' ';
1720
	    start_col++;
1721

1722
	    buf += parse_mbchar(buf, NULL, NULL);
1723
	}
1724
#endif
1725
1726
    }

1727
    while (*buf != '\0') {
1728
	int charlength, charwidth = 1;
1729

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

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

1765
	charlength = length_of_char(buf, &charwidth);
1766

1767
	/* If buf contains a control character, represent it. */
1768
	if (is_cntrl_mbchar(buf)) {
1769
	    converted[index++] = '^';
1770
	    converted[index++] = control_mbrep(buf);
1771
	    start_col += 2;
1772
1773
1774
	    buf += charlength;
	    continue;
	}
1775

1776
1777
1778
1779
	/* If buf contains a valid non-control character, simply copy it. */
	if (charlength > 0) {
	    for (; charlength > 0; charlength--)
		converted[index++] = *(buf++);
1780

1781
	    start_col += charwidth;
1782
#ifdef USING_OLD_NCURSES
1783
	    if (charwidth > 1)
1784
		seen_wide = TRUE;
1785
#endif
1786
	    continue;
1787
1788
	}

1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
	/* 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;
1800
1801
    }

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1802
    /* Null-terminate converted. */
1803
    converted[index] = '\0';
1804

1805
1806
    /* Make sure converted takes up no more than span columns. */
    index = actual_x(converted, span);
1807
    null_at(&converted, index);
1808

1809
    return converted;
1810
1811
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1812
1813
1814
1815
1816
1817
/* 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. */
1818
void titlebar(const char *path)
Chris Allegretta's avatar
Chris Allegretta committed
1819
{
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
    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. */
1832

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

1835
    wattron(topwin, interface_color_pair[TITLE_BAR]);
1836

1837
    blank_titlebar();
Chris Allegretta's avatar
Chris Allegretta committed
1838

1839
1840
1841
    /* 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
1842

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

1857
1858
1859
1860
	if (openfile->modified)
	    state = _("Modified");
	else if (ISSET(VIEW_MODE))
	    state = _("View");
1861

1862
1863
	pluglen = strlenpt(_("Modified")) + 1;
    }
1864

1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
    /* 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;
1875
1876
    }

1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
    /* 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;
1889
1890
1891
	}
    }

1892
1893
1894
1895
    /* If we have side spaces left, center the path name. */
    if (verlen > 0)
	offset = verlen + (COLS - (verlen + pluglen + statelen) -
					(prefixlen + pathlen)) / 2;
1896

1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
    /* 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);
1914
    }
1915

1916
1917
1918
1919
1920
1921
    /* 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));

1922
    wattroff(topwin, interface_color_pair[TITLE_BAR]);
1923

1924
    wnoutrefresh(topwin);
Chris Allegretta's avatar
Chris Allegretta committed
1925
    reset_cursor();
1926
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
1927
1928
}

1929
1930
1931
1932
1933
1934
/* Display a normal message on the statusbar, quietly. */
void statusbar(const char *msg)
{
    statusline(HUSH, msg);
}

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

    UNSET(WHITESPACE_DISPLAY);
1947
#endif
1948
1949
1950
1951
1952

    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(). */
1953
    if (isendwin()) {
1954
1955
1956
1957
1958
	vfprintf(stderr, msg, ap);
	va_end(ap);
	return;
    }

1959
1960
1961
1962
1963
1964
1965
    /* 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)
1966
1967
	napms(1200);

1968
    if (importance == ALERT)
1969
	beep();
1970
1971

    lastmessage = importance;
1972

1973
1974
1975
    /* Turn the cursor off while fiddling in the statusbar. */
    curs_set(0);

1976
1977
    blank_statusbar();

1978
1979
1980
1981
    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
1982
    free(bar);
1983
1984

#ifndef NANO_TINY
1985
1986
    if (old_whitespace)
	SET(WHITESPACE_DISPLAY);
1987
#endif
Benno Schulenberg's avatar
Benno Schulenberg committed
1988
    start_x = (COLS - strlenpt(foo) - 4) / 2;
1989

1990
    wmove(bottomwin, 0, start_x);
1991
    wattron(bottomwin, interface_color_pair[STATUS_BAR]);
1992
1993
1994
1995
    waddstr(bottomwin, "[ ");
    waddstr(bottomwin, foo);
    free(foo);
    waddstr(bottomwin, " ]");
1996
    wattroff(bottomwin, interface_color_pair[STATUS_BAR]);
1997

1998
    /* Push the message to the screen straightaway. */
1999
    wnoutrefresh(bottomwin);
2000
    doupdate();
2001

2002
    suppress_cursorpos = TRUE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2003

2004
    /* If we're doing quick statusbar blanking, blank it after just one
2005
2006
     * keystroke.  Otherwise, blank it after twenty-six keystrokes, as
     * Pico does. */
2007
#ifndef NANO_TINY
2008
2009
2010
    if (ISSET(QUICK_BLANK))
	statusblank = 1;
    else
2011
#endif
2012
	statusblank = 26;
2013
2014
}

2015
2016
/* Display the shortcut list corresponding to menu on the last two rows
 * of the bottom portion of the window. */
2017
void bottombars(int menu)
Chris Allegretta's avatar
Chris Allegretta committed
2018
{
2019
    size_t i, colwidth, slen;
2020
2021
    subnfunc *f;
    const sc *s;
2022

2023
2024
2025
    /* Set the global variable to the given menu. */
    currmenu = menu;

Chris Allegretta's avatar
Chris Allegretta committed
2026
2027
2028
    if (ISSET(NO_HELP))
	return;

2029
    if (menu == MMAIN) {
2030
	slen = MAIN_VISIBLE;
2031

2032
	assert(slen <= length_of_list(menu));
2033
    } else {
2034
	slen = length_of_list(menu);
2035

2036
	/* Don't show any more shortcuts than the main list does. */
2037
2038
2039
2040
	if (slen > MAIN_VISIBLE)
	    slen = MAIN_VISIBLE;
    }

2041
2042
2043
2044
    /* 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. */
2045
    colwidth = COLS / ((slen / 2) + (slen % 2));
Chris Allegretta's avatar
Chris Allegretta committed
2046

2047
    blank_bottombars();
2048

2049
2050
2051
#ifdef DEBUG
    fprintf(stderr, "In bottombars, and slen == \"%d\"\n", (int) slen);
#endif
2052

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

2055
#ifdef DEBUG
2056
	fprintf(stderr, "Checking menu items....");
2057
#endif
2058
	if ((f->menus & menu) == 0)
2059
	    continue;
2060

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

2079
2080
    wnoutrefresh(bottomwin);
    reset_cursor();
2081
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
2082
2083
}

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

2093
    wattron(bottomwin, interface_color_pair[KEY_COMBO]);
2094
    waddnstr(bottomwin, keystroke, actual_x(keystroke, length));
2095
    wattroff(bottomwin, interface_color_pair[KEY_COMBO]);
2096

2097
    length -= strlenpt(keystroke) + 1;
2098

2099
    if (length > 0) {
2100
	waddch(bottomwin, ' ');
2101
	wattron(bottomwin, interface_color_pair[FUNCTION_TAG]);
2102
	waddnstr(bottomwin, desc, actual_x(desc, length));
2103
	wattroff(bottomwin, interface_color_pair[FUNCTION_TAG]);
Chris Allegretta's avatar
Chris Allegretta committed
2104
2105
2106
    }
}

2107
2108
/* 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
2109
2110
void reset_cursor(void)
{
2111
    size_t xpt = xplustabs();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2112

2113
#ifndef NANO_TINY
2114
    if (ISSET(SOFTWRAP)) {
2115
	filestruct *line = openfile->edittop;
2116
2117
	openfile->current_y = 0;

2118
2119
2120
2121
	while (line != NULL && line != openfile->current) {
	    openfile->current_y += strlenpt(line->data) / COLS + 1;
	    line = line->next;
	}
2122
	openfile->current_y += xpt / COLS;
2123

2124
	if (openfile->current_y < editwinrows)
2125
	    wmove(edit, openfile->current_y, xpt % COLS);
2126
2127
2128
    } else
#endif
    {
2129
	openfile->current_y = openfile->current->lineno -
2130
				openfile->edittop->lineno;
2131
2132
2133
2134

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

2137
2138
2139
2140
2141
2142
2143
2144
/* 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. */
2145
void edit_draw(filestruct *fileptr, const char *converted, int
2146
	line, size_t start)
Chris Allegretta's avatar
Chris Allegretta committed
2147
{
2148
#if !defined(NANO_TINY) || !defined(DISABLE_COLOR)
2149
2150
2151
2152
2153
2154
2155
2156
2157
    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. */
2158
2159
#endif

2160
    assert(openfile != NULL && fileptr != NULL && converted != NULL);
2161
    assert(strlenpt(converted) <= COLS);
2162

2163
2164
2165
    /* 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);
2166

2167
#ifdef USING_OLD_NCURSES
2168
2169
2170
    /* 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. */
2171
2172
    if (seen_wide)
	wredrawln(edit, line, 1);
2173
#endif
2174

2175
#ifndef DISABLE_COLOR
2176
2177
2178
    /* If color syntaxes are available and turned on, we need to display
     * them. */
    if (openfile->colorstrings != NULL && !ISSET(NO_COLOR_SYNTAX)) {
2179
	const colortype *varnish = openfile->colorstrings;
2180

2181
2182
2183
2184
	/* If there are multiline regexes, make sure there is a cache. */
	if (openfile->syntax->nmultis > 0)
	    alloc_multidata_if_needed(fileptr);

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

2198
	    wattron(edit, varnish->attributes);
2199
2200
2201
	    /* 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. */
2202

2203
2204
	    /* First case: varnish is a single-line expression. */
	    if (varnish->end == NULL) {
2205
2206
2207
		size_t k = 0;

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

		    /* Skip over a zero-length regex match. */
		    if (startmatch.rm_so == startmatch.rm_eo)
2228
			startmatch.rm_eo++;
2229
		    else if (startmatch.rm_so < endpos &&
2230
			startmatch.rm_eo > startpos) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2231
2232
			x_start = (startmatch.rm_so <= startpos) ? 0 :
				strnlenpt(fileptr->data,
2233
				startmatch.rm_so) - start;
2234

2235
2236
2237
			index = actual_x(converted, x_start);

			paintlen = actual_x(converted + index,
2238
2239
				strnlenpt(fileptr->data,
				startmatch.rm_eo) - start - x_start);
2240
2241
2242

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

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

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

2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
		/* 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. */

2285
		while (start_line != NULL && regexec(varnish->start,
2286
2287
2288
			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. */
2289
		    if (regexec(varnish->end, start_line->data, 0, NULL, 0) == 0)
2290
2291
2292
			goto step_two;
		    start_line = start_line->prev;
		}
2293

2294
2295
2296
2297
		/* If no start was found, skip to the next step. */
		if (start_line == NULL)
		    goto step_two;

2298
		/* If a found start has been qualified as an end earlier,
2299
		 * believe it and skip to the next step. */
2300
		if (start_line->multidata != NULL &&
2301
2302
			(start_line->multidata[varnish->id] == CBEGINBEFORE ||
			start_line->multidata[varnish->id] == CSTARTENDHERE))
2303
2304
		    goto step_two;

2305
		/* Skip over a zero-length regex match. */
2306
		if (startmatch.rm_so == startmatch.rm_eo)
2307
2308
		    goto tail_of_loop;

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

2338
2339
2340
2341
		/* 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) {
2342
		    fileptr->multidata[varnish->id] = CBEGINBEFORE;
2343
2344
		    goto step_two;
		}
2345

2346
2347
2348
2349
2350
2351
2352
		/* 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;
2353
		    fileptr->multidata[varnish->id] = CWHOLELINE;
2354
#ifdef DEBUG
2355
    fprintf(stderr, "  Marking for id %i  line %i as CWHOLELINE\n", varnish->id, line);
2356
#endif
2357
2358
2359
		} else {
		    paintlen = actual_x(converted, strnlenpt(fileptr->data,
						endmatch.rm_eo) - start);
2360
		    fileptr->multidata[varnish->id] = CBEGINBEFORE;
2361
#ifdef DEBUG
2362
    fprintf(stderr, "  Marking for id %i  line %i as CBEGINBEFORE\n", varnish->id, line);
2363
#endif
2364
2365
2366
2367
2368
2369
		}
		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;
2370
  step_two:
2371
2372
2373
2374
2375
		/* 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) {
2376
		    if (regexec(varnish->start, fileptr->data + start_col,
2377
2378
				1, &startmatch, (start_col == 0) ?
				0 : REG_NOTBOL) == REG_NOMATCH ||
2379
				start_col + startmatch.rm_so >= endpos)
2380
2381
			/* No more starts on this line. */
			break;
2382

2383
2384
2385
2386
2387
2388
2389
		    /* 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,
2390
				startmatch.rm_so) - start;
2391

2392
		    index = actual_x(converted, x_start);
2393

2394
		    if (regexec(varnish->end, fileptr->data +
2395
				startmatch.rm_eo, 1, &endmatch,
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
				(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 &&
2406
				endmatch.rm_eo > startmatch.rm_so) {
2407
			    paintlen = actual_x(converted + index,
2408
					strnlenpt(fileptr->data,
2409
					endmatch.rm_eo) - start - x_start);
2410

2411
			    assert(0 <= x_start && x_start < COLS);
2412

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

2431
			while (end_line != NULL &&
2432
				regexec(varnish->end, end_line->data,
2433
				0, NULL, 0) == REG_NOMATCH)
2434
			    end_line = end_line->next;
2435

2436
2437
2438
			/* If there is no end, we're done on this line. */
			if (end_line == NULL)
			    break;
2439

2440
2441
2442
2443
			assert(0 <= x_start && x_start < COLS);

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

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

2480
	mark_order(&top, &top_x, &bot, &bot_x, NULL);
2481
2482
2483
2484
2485

	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
2486

2487
	/* Only paint if the marked bit of fileptr is on this page. */
2488
2489
	if (top_x < endpos && bot_x > startpos) {
	    assert(startpos <= top_x);
2490
2491
2492
2493

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

2495
2496
2497
2498
2499
	    /* 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. */
2500
2501
2502
	    if (bot_x >= endpos)
		paintlen = -1;
	    else
2503
		paintlen = strnlenpt(fileptr->data, bot_x) - (x_start + start);
2504
2505
2506
2507
2508
2509
2510
2511

	    /* 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;
	    }
2512
2513
2514

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

2515
	    index = actual_x(converted, x_start);
2516

2517
2518
2519
	    if (paintlen > 0)
		paintlen = actual_x(converted + index, paintlen);

2520
	    wattron(edit, hilite_attribute);
2521
	    mvwaddnstr(edit, line, x_start, converted + index, paintlen);
2522
	    wattroff(edit, hilite_attribute);
Chris Allegretta's avatar
Chris Allegretta committed
2523
	}
2524
    }
2525
#endif /* !NANO_TINY */
Chris Allegretta's avatar
Chris Allegretta committed
2526
2527
}

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

2542
    assert(fileptr != NULL);
2543

2544
#ifndef NANO_TINY
2545
    if (ISSET(SOFTWRAP)) {
2546
2547
	filestruct *tmp;

2548
2549
	for (tmp = openfile->edittop; tmp && tmp != fileptr; tmp = tmp->next)
	    line += (strlenpt(tmp->data) / COLS) + 1;
2550
    } else
2551
#endif
2552
2553
	line = fileptr->lineno - openfile->edittop->lineno;

2554
    if (line < 0 || line >= editwinrows)
Chris Allegretta's avatar
Chris Allegretta committed
2555
	return 1;
2556

2557
    /* First, blank out the line. */
2558
    blank_line(edit, line, 0, COLS);
2559

2560
2561
    /* Next, convert variables that index the line to their equivalent
     * positions in the expanded line. */
2562
#ifndef NANO_TINY
2563
2564
2565
    if (ISSET(SOFTWRAP))
	index = 0;
    else
2566
#endif
2567
	index = strnlenpt(fileptr->data, index);
2568
    page_start = get_page_start(index);
2569

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

2582
    /* Paint the line. */
2583
    edit_draw(fileptr, converted, line, page_start);
2584
    free(converted);
Chris Allegretta's avatar
Chris Allegretta committed
2585

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

	    /* Expand the line, replacing tabs with spaces, and control
2604
	     * characters with their displayed forms. */
2605
2606
2607
2608
2609
	    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
2610
2611
2612

	    /* Paint the line. */
	    edit_draw(fileptr, converted, line, index);
2613
	    free(converted);
2614
2615
2616
	    extralinesused++;
	}
    }
2617
#endif /* !NANO_TINY */
2618
    return extralinesused;
Chris Allegretta's avatar
Chris Allegretta committed
2619
2620
}

2621
2622
2623
2624
/* Return TRUE if we need an update after moving the cursor, and
 * FALSE otherwise.  We need an update if the mark is on, or if
 * pww_save and placewewant are on different pages. */
bool need_screen_update(size_t pww_save)
2625
2626
{
    return
2627
#ifndef NANO_TINY
2628
	openfile->mark_set ||
2629
#endif
2630
	get_page_start(pww_save) != get_page_start(openfile->placewewant);
2631
2632
}

2633
/* When edittop changes, try and figure out how many lines
2634
 * we really have to work with (i.e. set maxrows). */
2635
2636
2637
2638
2639
void compute_maxrows(void)
{
    int n;
    filestruct *foo = openfile->edittop;

2640
2641
2642
2643
2644
    if (!ISSET(SOFTWRAP)) {
	maxrows = editwinrows;
	return;
    }

2645
2646
    maxrows = 0;
    for (n = 0; n < editwinrows && foo; n++) {
2647
	maxrows++;
2648
	n += strlenpt(foo->data) / COLS;
2649
2650
2651
	foo = foo->next;
    }

2652
2653
2654
    if (n < editwinrows)
	maxrows += editwinrows - n;

2655
#ifdef DEBUG
2656
    fprintf(stderr, "compute_maxrows(): maxrows = %d\n", maxrows);
2657
2658
2659
#endif
}

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

2671
    assert(nlines > 0);
2672

2673
2674
2675
    /* Part 1: nlines is the number of lines we're going to scroll the
     * text of the edit window. */

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

#ifndef NANO_TINY
2691
	/* Don't over-scroll on long lines. */
2692
	if (ISSET(SOFTWRAP) && direction == UPWARD) {
2693
	    ssize_t len = strlenpt(openfile->edittop->data) / COLS;
2694
	    i -= len;
2695
	    if (len > 0)
2696
		refresh_needed = TRUE;
2697
	}
2698
#endif
2699
2700
    }

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

2704
2705
2706
2707
    /* Don't bother scrolling zero lines, nor more than the window can hold. */
    if (nlines == 0)
	return;
    if (nlines >= editwinrows)
2708
	refresh_needed = TRUE;
2709

2710
    if (refresh_needed == TRUE)
2711
	return;
2712
2713
2714

    /* Scroll the text of the edit window up or down nlines lines,
     * depending on the value of direction. */
2715
    scrollok(edit, TRUE);
2716
    wscrl(edit, (direction == UPWARD) ? -nlines : nlines);
2717
2718
    scrollok(edit, FALSE);

2719
2720
2721
    /* Part 2: nlines is the number of lines in the scrolled region of
     * the edit window that we need to draw. */

2722
2723
    /* If the top or bottom line of the file is now visible in the edit
     * window, we need to draw the entire edit window. */
2724
2725
    if ((direction == UPWARD && openfile->edittop ==
	openfile->fileage) || (direction == DOWNWARD &&
2726
2727
	openfile->edittop->lineno + editwinrows - 1 >=
	openfile->filebot->lineno))
2728
	nlines = editwinrows;
2729

2730
2731
2732
2733
2734
2735
    /* 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
2736

2737
2738
    if (nlines > editwinrows)
	nlines = editwinrows;
2739
2740
2741

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

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

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

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

    openfile->placewewant = xplustabs();

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

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

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

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

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

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

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

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

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

2832
2833
    foo = openfile->edittop;

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

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

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

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

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

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

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

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

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

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

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

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

2933
    bottombars(MMAIN);
2934
2935
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3010
    assert(room > 0);
3011

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3153
3154
3155
3156
	wrefresh(edit);

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

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

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

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

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

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

3183
    nodelay(edit, FALSE);
3184

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