winio.c 95.9 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
507
	/* Slang doesn't support KEY_SLEFT. */
	case KEY_SLEFT:
	    return sc_seq_or(do_left, keycode);
508
#endif
509
#ifdef KEY_SRIGHT
510
511
512
	/* Slang doesn't support KEY_SRIGHT. */
	case KEY_SRIGHT:
	    return sc_seq_or(do_right, keycode);
513
#endif
514
#ifdef KEY_SUP
515
516
517
	/* ncurses and Slang don't support KEY_SUP. */
	case KEY_SUP:
	    return sc_seq_or(do_up_void, keycode);
518
#endif
519
#ifdef KEY_SDOWN
520
521
522
	/* ncurses and Slang don't support KEY_SDOWN. */
	case KEY_SDOWN:
	    return sc_seq_or(do_down_void, keycode);
523
#endif
524
#ifdef KEY_SHOME
525
526
	/* HP-UX 10-11 and Slang don't support KEY_SHOME. */
	case KEY_SHOME:
527
#endif
528
529
	case KEY_A1:	/* Home (7) on keypad with NumLock off. */
	    return sc_seq_or(do_home, keycode);
530
#ifdef KEY_SEND
531
532
	/* HP-UX 10-11 and Slang don't support KEY_SEND. */
	case KEY_SEND:
533
#endif
534
535
536
537
538
539
	case KEY_C1:	/* End (1) on keypad with NumLock off. */
	    return sc_seq_or(do_end, keycode);
	case KEY_A3:	/* PageUp (9) on keypad with NumLock off. */
	    return sc_seq_or(do_page_up, keycode);
	case KEY_C3:	/* PageDown (3) on keypad with NumLock off. */
	    return sc_seq_or(do_page_down, keycode);
540
#ifdef KEY_SDC
541
542
	/* Slang doesn't support KEY_SDC. */
	case KEY_SDC:
543
#endif
544
	case DEL_CODE:
545
546
547
548
	    if (ISSET(REBIND_DELETE))
		return sc_seq_or(do_delete, keycode);
	    else
		return sc_seq_or(do_backspace, keycode);
549
#ifdef KEY_SIC
550
551
552
	/* Slang doesn't support KEY_SIC. */
	case KEY_SIC:
	    return sc_seq_or(do_insertfile_void, keycode);
553
#endif
554
#ifdef KEY_SBEG
555
556
	/* Slang doesn't support KEY_SBEG. */
	case KEY_SBEG:
557
#endif
558
#ifdef KEY_BEG
559
560
	/* Slang doesn't support KEY_BEG. */
	case KEY_BEG:
561
#endif
562
563
	case KEY_B2:	/* Center (5) on keypad with NumLock off. */
	    return ERR;
564
#ifdef KEY_CANCEL
565
#ifdef KEY_SCANCEL
566
567
	/* Slang doesn't support KEY_SCANCEL. */
	case KEY_SCANCEL:
568
#endif
569
570
571
	/* Slang doesn't support KEY_CANCEL. */
	case KEY_CANCEL:
	    return first_sc_for(currmenu, do_cancel)->keycode;
572
#endif
573
#ifdef KEY_SUSPEND
574
#ifdef KEY_SSUSPEND
575
576
	/* Slang doesn't support KEY_SSUSPEND. */
	case KEY_SSUSPEND:
577
#endif
578
579
580
	/* Slang doesn't support KEY_SUSPEND. */
	case KEY_SUSPEND:
	    return sc_seq_or(do_suspend_void, 0);
581
582
#endif
#ifdef PDCURSES
583
584
585
586
587
588
589
	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;
590
591
#endif
#if !defined(NANO_TINY) && defined(KEY_RESIZE)
592
593
594
595
596
	/* 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;
597
#endif
598
    }
599

600
601
602
    return retval;
}

603
/* Translate escape sequences, most of which correspond to extended
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
604
 * keypad values, into their corresponding key values.  These sequences
605
606
 * are generated when the keypad doesn't support the needed keys.
 * Assume that Escape has already been read in. */
607
int convert_sequence(const int *seq, size_t seq_len)
608
{
609
    if (seq_len > 1) {
610
	switch (seq[0]) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
611
	    case 'O':
612
		switch (seq[1]) {
613
		    case '1':
614
615
			if (seq_len > 4  && seq[2] == ';') {

616
617
	switch (seq[3]) {
	    case '2':
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
		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);
		}
633
634
		break;
	    case '5':
635
636
637
638
639
640
641
642
643
644
		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;
		}
645
646
		break;
	}
647

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

821
	switch (seq[3]) {
822
	    case '2':
823
824
825
826
827
828
829
		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]);
		}
830
831
		break;
	    case '5':
832
833
834
835
836
837
838
839
840
841
		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;
		}
842
843
		break;
	}
844
845
846
847

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

1005
    return ERR;
1006
1007
}

1008
1009
/* Return the equivalent arrow-key value for the first four letters
 * in the alphabet, common to many escape sequences. */
1010
int arrow_from_abcd(int kbinput)
1011
1012
1013
{
    switch (tolower(kbinput)) {
	case 'a':
1014
	    return KEY_UP;
1015
	case 'b':
1016
	    return KEY_DOWN;
1017
	case 'c':
1018
	    return KEY_RIGHT;
1019
	case 'd':
1020
	    return KEY_LEFT;
1021
1022
1023
1024
1025
	default:
	    return ERR;
    }
}

1026
/* Interpret the escape sequence in the keystroke buffer, the first
1027
1028
 * character of which is kbinput.  Assume that the keystroke buffer
 * isn't empty, and that the initial escape has already been read in. */
1029
int parse_escape_sequence(WINDOW *win, int kbinput)
1030
1031
1032
1033
1034
1035
1036
1037
{
    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);
1038
    seq_len = key_buffer_len;
1039
    seq = get_input(NULL, seq_len);
1040
    retval = convert_sequence(seq, seq_len);
1041
1042
1043

    free(seq);

1044
    /* If we got an unrecognized escape sequence, notify the user. */
1045
    if (retval == ERR) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1046
	if (win == edit) {
1047
1048
	    /* TRANSLATORS: This refers to a sequence of escape codes
	     * (from the keyboard) that nano does not know about. */
1049
	    statusline(ALERT, _("Unknown sequence"));
1050
	    suppress_cursorpos = FALSE;
1051
	    lastmessage = HUSH;
1052
1053
1054
1055
	    if (currmenu == MMAIN) {
		reset_cursor();
		curs_set(1);
	    }
1056
1057
1058
	}
    }

1059
#ifdef DEBUG
1060
1061
    fprintf(stderr, "parse_escape_sequence(): kbinput = %d, seq_len = %lu, retval = %d\n",
		kbinput, (unsigned long)seq_len, retval);
1062
1063
1064
1065
1066
#endif

    return retval;
}

1067
1068
/* Translate a byte sequence: turn a three-digit decimal number (from
 * 000 to 255) into its corresponding byte value. */
1069
int get_byte_kbinput(int kbinput)
1070
{
1071
    static int byte_digits = 0, byte = 0;
1072
    int retval = ERR;
1073

1074
1075
    /* Increment the byte digit counter. */
    byte_digits++;
1076

1077
    switch (byte_digits) {
1078
	case 1:
1079
1080
	    /* First digit: This must be from zero to two.  Put it in
	     * the 100's position of the byte sequence holder. */
1081
	    if ('0' <= kbinput && kbinput <= '2')
1082
		byte = (kbinput - '0') * 100;
1083
	    else
1084
1085
		/* This isn't the start of a byte sequence.  Return this
		 * character as the result. */
1086
1087
1088
		retval = kbinput;
	    break;
	case 2:
1089
1090
1091
1092
	    /* 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. */
1093
1094
1095
	    if (('0' <= kbinput && kbinput <= '5') || (byte < 200 &&
		'6' <= kbinput && kbinput <= '9'))
		byte += (kbinput - '0') * 10;
1096
	    else
1097
1098
		/* This isn't the second digit of a byte sequence.
		 * Return this character as the result. */
1099
1100
1101
		retval = kbinput;
	    break;
	case 3:
1102
	    /* Third digit: This must be from zero to five if the first
1103
1104
1105
	     * 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. */
1106
1107
	    if (('0' <= kbinput && kbinput <= '5') || (byte < 250 &&
		'6' <= kbinput && kbinput <= '9')) {
1108
		byte += kbinput - '0';
1109
		/* The byte sequence is complete. */
1110
		retval = byte;
1111
	    } else
1112
1113
		/* This isn't the third digit of a byte sequence.
		 * Return this character as the result. */
1114
1115
		retval = kbinput;
	    break;
1116
	default:
1117
1118
1119
	    /* If there are more than three digits, return this
	     * character as the result.  (Maybe we should produce an
	     * error instead?) */
1120
1121
1122
1123
1124
1125
1126
1127
	    retval = kbinput;
	    break;
    }

    /* If we have a result, reset the byte digit counter and the byte
     * sequence holder. */
    if (retval != ERR) {
	byte_digits = 0;
1128
	byte = 0;
1129
1130
1131
    }

#ifdef DEBUG
1132
    fprintf(stderr, "get_byte_kbinput(): kbinput = %d, byte_digits = %d, byte = %d, retval = %d\n", kbinput, byte_digits, byte, retval);
1133
1134
1135
1136
1137
#endif

    return retval;
}

1138
#ifdef ENABLE_UTF8
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1139
/* If the character in kbinput is a valid hexadecimal digit, multiply it
1140
 * by factor and add the result to uni, and return ERR to signify okay. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1141
1142
1143
1144
1145
1146
1147
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
1148
1149
	/* The character isn't hexadecimal; give it as the result. */
	return (long)kbinput;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1150

1151
    return ERR;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1152
1153
}

1154
/* Translate a Unicode sequence: turn a six-digit hexadecimal number
1155
 * (from 000000 to 10FFFF, case-insensitive) into its corresponding
1156
 * multibyte value. */
1157
long get_unicode_kbinput(WINDOW *win, int kbinput)
1158
{
1159
1160
1161
    static int uni_digits = 0;
    static long uni = 0;
    long retval = ERR;
1162

1163
    /* Increment the Unicode digit counter. */
1164
    uni_digits++;
1165

1166
    switch (uni_digits) {
1167
	case 1:
1168
1169
1170
1171
	    /* 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')
1172
		uni = (kbinput - '0') * 0x100000;
1173
1174
1175
1176
	    else
		retval = kbinput;
	    break;
	case 2:
1177
1178
1179
	    /* 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)
1180
		retval = add_unicode_digit(kbinput, 0x10000, &uni);
1181
1182
1183
1184
	    else
		retval = kbinput;
	    break;
	case 3:
1185
	    /* Later digits may be any hexadecimal value. */
1186
	    retval = add_unicode_digit(kbinput, 0x1000, &uni);
1187
	    break;
1188
	case 4:
1189
	    retval = add_unicode_digit(kbinput, 0x100, &uni);
1190
	    break;
1191
	case 5:
1192
	    retval = add_unicode_digit(kbinput, 0x10, &uni);
1193
	    break;
1194
	case 6:
1195
	    retval = add_unicode_digit(kbinput, 0x1, &uni);
1196
1197
	    /* If also the sixth digit was a valid hexadecimal value, then
	     * the Unicode sequence is complete, so return it. */
1198
	    if (retval == ERR)
1199
		retval = uni;
1200
1201
	    break;
    }
1202

1203
    /* Show feedback only when editing, not when at a prompt. */
1204
    if (retval == ERR && win == edit) {
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
	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);
    }
1215

1216
#ifdef DEBUG
1217
1218
    fprintf(stderr, "get_unicode_kbinput(): kbinput = %d, uni_digits = %d, uni = %ld, retval = %ld\n",
						kbinput, uni_digits, uni, retval);
1219
1220
#endif

1221
1222
1223
1224
    /* If we have an end result, reset the Unicode digit counter. */
    if (retval != ERR)
	uni_digits = 0;

1225
1226
    return retval;
}
1227
#endif /* ENABLE_UTF8 */
1228

1229
1230
1231
1232
1233
1234
/* Translate a control character sequence: turn an ASCII non-control
 * character into its corresponding control character. */
int get_control_kbinput(int kbinput)
{
    int retval;

1235
    /* Ctrl-Space (Ctrl-2, Ctrl-@, Ctrl-`) */
1236
    if (kbinput == ' ' || kbinput == '2')
1237
	retval = 0;
1238
1239
    /* Ctrl-/ (Ctrl-7, Ctrl-_) */
    else if (kbinput == '/')
1240
	retval = 31;
1241
    /* Ctrl-3 (Ctrl-[, Esc) to Ctrl-7 (Ctrl-/, Ctrl-_) */
1242
1243
1244
    else if ('3' <= kbinput && kbinput <= '7')
	retval = kbinput - 24;
    /* Ctrl-8 (Ctrl-?) */
1245
    else if (kbinput == '8' || kbinput == '?')
1246
	retval = DEL_CODE;
1247
1248
    /* Ctrl-@ (Ctrl-Space, Ctrl-2, Ctrl-`) to Ctrl-_ (Ctrl-/, Ctrl-7) */
    else if ('@' <= kbinput && kbinput <= '_')
1249
	retval = kbinput - '@';
1250
1251
    /* Ctrl-` (Ctrl-2, Ctrl-Space, Ctrl-@) to Ctrl-~ (Ctrl-6, Ctrl-^) */
    else if ('`' <= kbinput && kbinput <= '~')
1252
	retval = kbinput - '`';
1253
1254
1255
    else
	retval = kbinput;

1256
#ifdef DEBUG
1257
    fprintf(stderr, "get_control_kbinput(): kbinput = %d, retval = %d\n", kbinput, retval);
1258
1259
#endif

1260
1261
    return retval;
}
1262

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1263
1264
/* Put the output-formatted characters in output back into the keystroke
 * buffer, so that they can be parsed and displayed as output again. */
1265
void unparse_kbinput(char *output, size_t output_len)
1266
{
1267
1268
    int *input;
    size_t i;
1269

1270
1271
1272
1273
    if (output_len == 0)
	return;

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

1275
1276
    for (i = 0; i < output_len; i++)
	input[i] = (int)output[i];
1277

1278
    unget_input(input, output_len);
1279

1280
    free(input);
1281
1282
}

1283
/* Read in a stream of characters verbatim, and return the length of the
1284
1285
1286
1287
 * string in kbinput_len.  Assume nodelay(win) is FALSE. */
int *get_verbatim_kbinput(WINDOW *win, size_t *kbinput_len)
{
    int *retval;
1288

1289
    /* Turn off flow control characters if necessary so that we can type
1290
1291
     * them in verbatim, and turn the keypad off if necessary so that we
     * don't get extended keypad values. */
1292
1293
    if (ISSET(PRESERVE))
	disable_flow_control();
1294
1295
    if (!ISSET(REBIND_KEYPAD))
	keypad(win, FALSE);
1296
1297
1298

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

    /* Turn flow control characters back on if necessary and turn the
1301
     * keypad back on if necessary now that we're done. */
1302
1303
    if (ISSET(PRESERVE))
	enable_flow_control();
1304
1305
1306
1307
1308
1309
    /* 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);
    }
1310

1311
    return retval;
1312
1313
}

1314
1315
1316
1317
1318
/* Read in one control character (or an iTerm double Escape), or convert a
 * series of six digits into a Unicode codepoint.  Return in count either 1
 * (for a control character or the first byte of a multibyte sequence), or 2
 * (for an iTerm double Escape). */
int *parse_verbatim_kbinput(WINDOW *win, size_t *count)
1319
{
1320
    int *kbinput;
1321

1322
    /* Read in the first code. */
1323
1324
    while ((kbinput = get_input(win, 1)) == NULL)
	;
1325

1326
#ifndef NANO_TINY
1327
1328
1329
    /* When the window was resized, abort and return nothing. */
    if (*kbinput == KEY_WINCH) {
	free(kbinput);
1330
	*count = 0;
1331
1332
	return NULL;
    }
1333
#endif
1334

1335
#ifdef ENABLE_UTF8
1336
    if (using_utf8()) {
1337
	/* Check whether the first code is a valid starter digit: 0 or 1. */
1338
	long uni = get_unicode_kbinput(win, *kbinput);
1339

1340
	/* If the first code isn't the digit 0 nor 1, put it back. */
1341
1342
	if (uni != ERR)
	    unget_input(kbinput, 1);
1343
1344
	/* Otherwise, continue reading in digits until we have a complete
	 * Unicode value, and put back the corresponding byte(s). */
1345
1346
1347
1348
1349
	else {
	    char *uni_mb;
	    int uni_mb_len, *seq, i;

	    while (uni == ERR) {
1350
		free(kbinput);
1351
1352
		while ((kbinput = get_input(win, 1)) == NULL)
		    ;
1353
		uni = get_unicode_kbinput(win, *kbinput);
1354
	    }
1355

1356
	    /* Convert the Unicode value to a multibyte sequence. */
1357
	    uni_mb = make_mbchar(uni, &uni_mb_len);
1358

1359
	    seq = (int *)nmalloc(uni_mb_len * sizeof(int));
1360

1361
1362
	    for (i = 0; i < uni_mb_len; i++)
		seq[i] = (unsigned char)uni_mb[i];
1363

1364
	    /* Insert the multibyte sequence into the input buffer. */
1365
	    unget_input(seq, uni_mb_len);
1366

1367
1368
	    free(seq);
	    free(uni_mb);
1369
	}
1370
    } else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1371
#endif /* ENABLE_UTF8 */
1372
	/* Put back the first code. */
1373
	unget_input(kbinput, 1);
1374

1375
1376
    free(kbinput);

1377
    *count = 1;
1378

1379
1380
1381
1382
1383
1384
    /* If this is an iTerm double escape, take both Escapes. */
    if (key_buffer_len > 3 && *key_buffer == ESC_CODE &&
		key_buffer[1] == ESC_CODE && key_buffer[2] == '[')
	*count = 2;

    return get_input(NULL, *count);
1385
1386
}

1387
#ifndef DISABLE_MOUSE
1388
/* Handle any mouse event that may have occurred.  We currently handle
1389
1390
1391
1392
1393
1394
1395
 * 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
1396
1397
1398
1399
1400
1401
 * 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. */
1402
int get_mouseinput(int *mouse_x, int *mouse_y, bool allow_shortcuts)
1403
1404
{
    MEVENT mevent;
1405
    bool in_bottomwin;
1406
    subnfunc *f;
1407
1408
1409
1410
1411
1412

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

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

1415
1416
1417
    /* Save the screen coordinates where the mouse event took place. */
    *mouse_x = mevent.x;
    *mouse_y = mevent.y;
1418

1419
1420
    in_bottomwin = wenclose(bottomwin, *mouse_y, *mouse_x);

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1421
    /* Handle releases/clicks of the first mouse button. */
1422
    if (mevent.bstate & (BUTTON1_RELEASED | BUTTON1_CLICKED)) {
1423
1424
	/* If we're allowing shortcuts, the current shortcut list is
	 * being displayed on the last two lines of the screen, and the
1425
1426
1427
	 * 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. */
1428
	if (allow_shortcuts && !ISSET(NO_HELP) && in_bottomwin) {
1429
1430
1431
1432
	    int i;
		/* The width of all the shortcuts, except for the last
		 * two, in the shortcut list in bottomwin. */
	    int j;
1433
		/* The calculated index number of the clicked item. */
1434
1435
1436
1437
	    size_t currslen;
		/* The number of shortcuts in the current shortcut
		 * list. */

1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
	    /* 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;
	    }
1452

1453
	    /* Get the shortcut lists' length. */
1454
	    if (currmenu == MMAIN)
1455
		currslen = MAIN_VISIBLE;
1456
	    else {
1457
		currslen = length_of_list(currmenu);
1458

1459
1460
1461
1462
1463
		/* We don't show any more shortcuts than the main list
		 * does. */
		if (currslen > MAIN_VISIBLE)
		    currslen = MAIN_VISIBLE;
	    }
1464

1465
1466
1467
1468
1469
1470
1471
1472
	    /* 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));

1473
1474
	    /* Calculate the one-based index in the shortcut list. */
	    j = (*mouse_x / i) * 2 + *mouse_y;
1475

1476
1477
	    /* Adjust the index if we hit the last two wider ones. */
	    if ((j > currslen) && (*mouse_x % i < COLS % i))
1478
		j -= 2;
1479
1480
1481
#ifdef DEBUG
	    fprintf(stderr, "Calculated %i as index in shortcut list, currmenu = %x.\n", j, currmenu);
#endif
1482
1483
	    /* Ignore releases/clicks of the first mouse button beyond
	     * the last shortcut. */
1484
	    if (j > currslen)
1485
		return 2;
1486

1487
1488
	    /* Go through the list of functions to determine which
	     * shortcut in the current menu we released/clicked on. */
1489
	    for (f = allfuncs; f != NULL; f = f->next) {
1490
		if ((f->menus & currmenu) == 0)
1491
1492
		    continue;
		if (first_sc_for(currmenu, f->scfunc) == NULL)
1493
		    continue;
1494
1495
		/* Tick off an actually shown shortcut. */
		j -= 1;
1496
1497
		if (j == 0)
		    break;
1498
	    }
1499
#ifdef DEBUG
1500
	    fprintf(stderr, "Stopped on func %ld present in menus %x\n", (long)f->scfunc, f->menus);
1501
#endif
1502

1503
	    /* And put the corresponding key into the keyboard buffer. */
1504
	    if (f != NULL) {
1505
		const sc *s = first_sc_for(currmenu, f->scfunc);
1506
		unget_kbinput(s->keycode, s->meta);
1507
	    }
1508
	    return 1;
1509
	} else
1510
1511
	    /* Handle releases/clicks of the first mouse button that
	     * aren't on the current shortcut list elsewhere. */
1512
	    return 0;
1513
    }
1514
1515
#if NCURSES_MOUSE_VERSION >= 2
    /* Handle presses of the fourth mouse button (upward rolls of the
1516
1517
1518
     * 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
1519
	bool in_edit = wenclose(edit, *mouse_y, *mouse_x);
1520

1521
1522
1523
1524
	if (in_bottomwin)
	    /* Translate the mouse event coordinates so that they're
	     * relative to bottomwin. */
	    wmouse_trafo(bottomwin, mouse_y, mouse_x, FALSE);
1525

1526
	if (in_edit || (in_bottomwin && *mouse_y == 0)) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1527
1528
	    int i;

1529
1530
1531
1532
1533
	    /* 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) ?
1534
				KEY_PPAGE : KEY_NPAGE, FALSE);
1535
1536
1537
1538
1539
1540
1541

	    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;
1542
1543
    }
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1544
1545
1546

    /* Ignore all other mouse events. */
    return 2;
1547
}
1548
1549
#endif /* !DISABLE_MOUSE */

1550
1551
1552
1553
/* 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. */
1554
const sc *get_shortcut(int *kbinput)
1555
{
1556
    sc *s;
1557

1558
#ifdef DEBUG
1559
1560
    fprintf(stderr, "get_shortcut(): kbinput = %d, meta_key = %s -- ",
				*kbinput, meta_key ? "TRUE" : "FALSE");
1561
1562
#endif

1563
    for (s = sclist; s != NULL; s = s->next) {
1564
	if ((s->menus & currmenu) && *kbinput == s->keycode &&
1565
					meta_key == s->meta) {
1566
#ifdef DEBUG
1567
1568
	    fprintf (stderr, "matched seq '%s'  (menu is %x from %x)\n",
				s->keystr, currmenu, s->menus);
1569
#endif
1570
	    return s;
1571
1572
	}
    }
1573
#ifdef DEBUG
1574
    fprintf (stderr, "matched nothing\n");
1575
#endif
1576
1577
1578
1579

    return NULL;
}

1580
1581
1582
1583
1584
/* 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
1585

1586
1587
1588
1589
    for (; n > 0; n--)
	waddch(win, ' ');
}

1590
/* Blank the first line of the top portion of the window. */
1591
void blank_titlebar(void)
Chris Allegretta's avatar
Chris Allegretta committed
1592
{
1593
    blank_line(topwin, 0, 0, COLS);
1594
1595
}

1596
/* Blank all the lines of the middle portion of the window, i.e. the
1597
 * edit window. */
Chris Allegretta's avatar
Chris Allegretta committed
1598
1599
void blank_edit(void)
{
1600
    int i;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1601

1602
    for (i = 0; i < editwinrows; i++)
1603
	blank_line(edit, i, 0, COLS);
Chris Allegretta's avatar
Chris Allegretta committed
1604
1605
}

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

1612
1613
/* If the NO_HELP flag isn't set, blank the last two lines of the bottom
 * portion of the window. */
1614
1615
1616
void blank_bottombars(void)
{
    if (!ISSET(NO_HELP)) {
1617
1618
	blank_line(bottomwin, 1, 0, COLS);
	blank_line(bottomwin, 2, 0, COLS);
1619
1620
1621
    }
}

1622
1623
/* Check if the number of keystrokes needed to blank the statusbar has
 * been pressed.  If so, blank the statusbar, unless constant cursor
1624
 * position display is on and we are in the editing screen. */
1625
void check_statusblank(void)
Chris Allegretta's avatar
Chris Allegretta committed
1626
{
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
    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
1641
1642
1643
    }
}

1644
1645
/* Convert buf into a string that can be displayed on screen.  The
 * caller wants to display buf starting with column start_col, and
1646
1647
 * extending for at most span columns.  start_col is zero-based.  span
 * is one-based, so span == 0 means you get "" returned.  The returned
1648
1649
1650
 * 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. */
1651
1652
char *display_string(const char *buf, size_t start_col, size_t span,
	bool dollars)
1653
1654
{
    size_t start_index;
1655
	/* Index in buf of the first character shown. */
1656
    size_t column;
1657
	/* Screen column that start_index corresponds to. */
1658
1659
1660
1661
    char *converted;
	/* The string we return. */
    size_t index;
	/* Current position in converted. */
1662

1663
1664
    /* If dollars is TRUE, make room for the "$" at the end of the
     * line. */
1665
1666
    if (dollars && span > 0 && strlenpt(buf) > start_col + span)
	span--;
1667

1668
    if (span == 0)
1669
1670
1671
1672
	return mallocstrcpy(NULL, "");

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

1674
    assert(column <= start_col);
1675

1676
1677
    /* Allocate enough space to hold the entire converted buffer. */
    converted = charalloc(strlen(buf) * (mb_cur_max() + tabsize) + 1);
1678

1679
    index = 0;
1680
#ifdef USING_OLD_NCURSES
1681
    seen_wide = FALSE;
1682
#endif
1683
    buf += start_index;
1684

1685
    if (*buf != '\0' && *buf != '\t' &&
1686
	(column < start_col || (dollars && column > 0))) {
1687
	/* We don't display the complete first character as it starts to
1688
	 * the left of the screen. */
1689
	if (is_cntrl_mbchar(buf)) {
1690
	    if (column < start_col) {
1691
		converted[index++] = control_mbrep(buf);
1692
		start_col++;
1693
		buf += parse_mbchar(buf, NULL, NULL);
1694
	    }
1695
	}
1696
#ifdef ENABLE_UTF8
1697
	else if (using_utf8() && mbwidth(buf) == 2) {
1698
1699
1700
1701
1702
	    if (column >= start_col) {
		converted[index++] = ' ';
		start_col++;
	    }

1703
	    converted[index++] = ' ';
1704
	    start_col++;
1705

1706
	    buf += parse_mbchar(buf, NULL, NULL);
1707
	}
1708
#endif
1709
1710
    }

1711
    while (*buf != '\0') {
1712
	int charlength, charwidth = 1;
1713

1714
	if (*buf == ' ') {
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
	    /* 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++;
1726
1727
	    buf++;
	    continue;
1728
	} else if (*buf == '\t') {
1729
	    /* Show a tab as a visible character, or as as a space. */
1730
#ifndef NANO_TINY
1731
	    if (ISSET(WHITESPACE_DISPLAY)) {
1732
		int i = 0;
1733

1734
1735
		while (i < whitespace_len[0])
		    converted[index++] = whitespace[i++];
1736
	    } else
1737
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1738
		converted[index++] = ' ';
1739
	    start_col++;
1740
	    /* Fill the tab up with the required number of spaces. */
1741
	    while (start_col % tabsize != 0) {
1742
		converted[index++] = ' ';
1743
1744
		start_col++;
	    }
1745
1746
1747
1748
	    buf++;
	    continue;
	}

1749
	charlength = length_of_char(buf, &charwidth);
1750

1751
	/* If buf contains a control character, represent it. */
1752
	if (is_cntrl_mbchar(buf)) {
1753
	    converted[index++] = '^';
1754
	    converted[index++] = control_mbrep(buf);
1755
	    start_col += 2;
1756
1757
1758
	    buf += charlength;
	    continue;
	}
1759

1760
1761
1762
1763
	/* If buf contains a valid non-control character, simply copy it. */
	if (charlength > 0) {
	    for (; charlength > 0; charlength--)
		converted[index++] = *(buf++);
1764

1765
	    start_col += charwidth;
1766
#ifdef USING_OLD_NCURSES
1767
	    if (charwidth > 1)
1768
		seen_wide = TRUE;
1769
#endif
1770
	    continue;
1771
1772
	}

1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
	/* 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;
1784
1785
    }

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1786
    /* Null-terminate converted. */
1787
    converted[index] = '\0';
1788

1789
1790
    /* Make sure converted takes up no more than span columns. */
    index = actual_x(converted, span);
1791
    null_at(&converted, index);
1792

1793
    return converted;
1794
1795
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1796
1797
1798
1799
1800
1801
/* 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. */
1802
void titlebar(const char *path)
Chris Allegretta's avatar
Chris Allegretta committed
1803
{
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
    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. */
1816

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

1819
    wattron(topwin, interface_color_pair[TITLE_BAR]);
1820

1821
    blank_titlebar();
Chris Allegretta's avatar
Chris Allegretta committed
1822

1823
1824
1825
    /* 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
1826

1827
    /* Figure out the path, prefix and state strings. */
1828
#ifndef DISABLE_BROWSER
1829
1830
1831
1832
    if (path != NULL)
	prefix = _("DIR:");
    else
#endif
1833
1834
1835
1836
1837
1838
1839
    {
	if (openfile->filename[0] == '\0')
	    path = _("New Buffer");
	else {
	    path = openfile->filename;
	    prefix = _("File:");
	}
1840

1841
1842
1843
1844
	if (openfile->modified)
	    state = _("Modified");
	else if (ISSET(VIEW_MODE))
	    state = _("View");
1845

1846
1847
	pluglen = strlenpt(_("Modified")) + 1;
    }
1848

1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
    /* 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;
1859
1860
    }

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

1876
1877
1878
1879
    /* If we have side spaces left, center the path name. */
    if (verlen > 0)
	offset = verlen + (COLS - (verlen + pluglen + statelen) -
					(prefixlen + pathlen)) / 2;
1880

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

1900
1901
1902
1903
1904
1905
    /* 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));

1906
    wattroff(topwin, interface_color_pair[TITLE_BAR]);
1907

1908
    wnoutrefresh(topwin);
Chris Allegretta's avatar
Chris Allegretta committed
1909
    reset_cursor();
1910
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
1911
1912
}

1913
1914
1915
1916
1917
1918
/* Display a normal message on the statusbar, quietly. */
void statusbar(const char *msg)
{
    statusline(HUSH, msg);
}

1919
/* Display a message on the statusbar, and set suppress_cursorpos to
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1920
1921
 * TRUE, so that the message won't be immediately overwritten if
 * constant cursor position display is on. */
1922
void statusline(message_type importance, const char *msg, ...)
1923
1924
{
    va_list ap;
1925
    char *bar, *foo;
Benno Schulenberg's avatar
Benno Schulenberg committed
1926
    size_t start_x;
1927
1928
1929
1930
#ifndef NANO_TINY
    bool old_whitespace = ISSET(WHITESPACE_DISPLAY);

    UNSET(WHITESPACE_DISPLAY);
1931
#endif
1932
1933
1934
1935
1936

    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(). */
1937
    if (isendwin()) {
1938
1939
1940
1941
1942
	vfprintf(stderr, msg, ap);
	va_end(ap);
	return;
    }

1943
1944
1945
1946
1947
1948
1949
    /* 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)
1950
1951
	napms(1200);

1952
    if (importance == ALERT)
1953
	beep();
1954
1955

    lastmessage = importance;
1956

1957
1958
1959
    /* Turn the cursor off while fiddling in the statusbar. */
    curs_set(0);

1960
1961
    blank_statusbar();

1962
1963
1964
1965
    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
1966
    free(bar);
1967
1968

#ifndef NANO_TINY
1969
1970
    if (old_whitespace)
	SET(WHITESPACE_DISPLAY);
1971
#endif
Benno Schulenberg's avatar
Benno Schulenberg committed
1972
    start_x = (COLS - strlenpt(foo) - 4) / 2;
1973

1974
    wmove(bottomwin, 0, start_x);
1975
    wattron(bottomwin, interface_color_pair[STATUS_BAR]);
1976
1977
1978
1979
    waddstr(bottomwin, "[ ");
    waddstr(bottomwin, foo);
    free(foo);
    waddstr(bottomwin, " ]");
1980
    wattroff(bottomwin, interface_color_pair[STATUS_BAR]);
1981

1982
    /* Push the message to the screen straightaway. */
1983
    wnoutrefresh(bottomwin);
1984
    doupdate();
1985

1986
    suppress_cursorpos = TRUE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1987

1988
    /* If we're doing quick statusbar blanking, blank it after just one
1989
1990
     * keystroke.  Otherwise, blank it after twenty-six keystrokes, as
     * Pico does. */
1991
#ifndef NANO_TINY
1992
1993
1994
    if (ISSET(QUICK_BLANK))
	statusblank = 1;
    else
1995
#endif
1996
	statusblank = 26;
1997
1998
}

1999
2000
/* Display the shortcut list corresponding to menu on the last two rows
 * of the bottom portion of the window. */
2001
void bottombars(int menu)
Chris Allegretta's avatar
Chris Allegretta committed
2002
{
2003
    size_t i, colwidth, slen;
2004
2005
    subnfunc *f;
    const sc *s;
2006

2007
2008
2009
    /* Set the global variable to the given menu. */
    currmenu = menu;

Chris Allegretta's avatar
Chris Allegretta committed
2010
2011
2012
    if (ISSET(NO_HELP))
	return;

2013
    if (menu == MMAIN) {
2014
	slen = MAIN_VISIBLE;
2015

2016
	assert(slen <= length_of_list(menu));
2017
    } else {
2018
	slen = length_of_list(menu);
2019

2020
	/* Don't show any more shortcuts than the main list does. */
2021
2022
2023
2024
	if (slen > MAIN_VISIBLE)
	    slen = MAIN_VISIBLE;
    }

2025
2026
2027
2028
    /* 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. */
2029
    colwidth = COLS / ((slen / 2) + (slen % 2));
Chris Allegretta's avatar
Chris Allegretta committed
2030

2031
    blank_bottombars();
2032

2033
2034
2035
#ifdef DEBUG
    fprintf(stderr, "In bottombars, and slen == \"%d\"\n", (int) slen);
#endif
2036

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

2039
#ifdef DEBUG
2040
	fprintf(stderr, "Checking menu items....");
2041
#endif
2042
	if ((f->menus & menu) == 0)
2043
	    continue;
2044

2045
#ifdef DEBUG
2046
	fprintf(stderr, "found one! f->menus = %x, desc = \"%s\"\n", f->menus, f->desc);
2047
#endif
2048
2049
	s = first_sc_for(menu, f->scfunc);
	if (s == NULL) {
2050
2051
2052
#ifdef DEBUG
	    fprintf(stderr, "Whoops, guess not, no shortcut key found for func!\n");
#endif
2053
2054
	    continue;
	}
2055
	wmove(bottomwin, 1 + i % 2, (i / 2) * colwidth);
2056
#ifdef DEBUG
2057
	fprintf(stderr, "Calling onekey with keystr \"%s\" and desc \"%s\"\n", s->keystr, f->desc);
2058
#endif
2059
	onekey(s->keystr, _(f->desc), colwidth + (COLS % colwidth));
2060
	i++;
Chris Allegretta's avatar
Chris Allegretta committed
2061
    }
2062

2063
2064
    wnoutrefresh(bottomwin);
    reset_cursor();
2065
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
2066
2067
}

2068
2069
/* 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
2070
 * to write at most length characters, even if length is very small and
2071
2072
 * keystroke and desc are long.  Note that waddnstr(,,(size_t)-1) adds
 * the whole string!  We do not bother padding the entry with blanks. */
2073
void onekey(const char *keystroke, const char *desc, int length)
Chris Allegretta's avatar
Chris Allegretta committed
2074
{
2075
2076
    assert(keystroke != NULL && desc != NULL);

2077
    wattron(bottomwin, interface_color_pair[KEY_COMBO]);
2078
    waddnstr(bottomwin, keystroke, actual_x(keystroke, length));
2079
    wattroff(bottomwin, interface_color_pair[KEY_COMBO]);
2080

2081
    length -= strlenpt(keystroke) + 1;
2082

2083
    if (length > 0) {
2084
	waddch(bottomwin, ' ');
2085
	wattron(bottomwin, interface_color_pair[FUNCTION_TAG]);
2086
	waddnstr(bottomwin, desc, actual_x(desc, length));
2087
	wattroff(bottomwin, interface_color_pair[FUNCTION_TAG]);
Chris Allegretta's avatar
Chris Allegretta committed
2088
2089
2090
    }
}

2091
2092
/* 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
2093
2094
void reset_cursor(void)
{
2095
    size_t xpt = xplustabs();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2096

2097
#ifndef NANO_TINY
2098
    if (ISSET(SOFTWRAP)) {
2099
	filestruct *line = openfile->edittop;
2100
2101
	openfile->current_y = 0;

2102
2103
2104
2105
	while (line != NULL && line != openfile->current) {
	    openfile->current_y += strlenpt(line->data) / COLS + 1;
	    line = line->next;
	}
2106
	openfile->current_y += xpt / COLS;
2107

2108
	if (openfile->current_y < editwinrows)
2109
	    wmove(edit, openfile->current_y, xpt % COLS);
2110
2111
2112
    } else
#endif
    {
2113
	openfile->current_y = openfile->current->lineno -
2114
				openfile->edittop->lineno;
2115
2116
2117
2118

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

2121
2122
2123
2124
2125
2126
2127
2128
/* 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. */
2129
void edit_draw(filestruct *fileptr, const char *converted, int
2130
	line, size_t start)
Chris Allegretta's avatar
Chris Allegretta committed
2131
{
2132
#if !defined(NANO_TINY) || !defined(DISABLE_COLOR)
2133
2134
2135
2136
2137
2138
2139
2140
2141
    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. */
2142
2143
#endif

2144
    assert(openfile != NULL && fileptr != NULL && converted != NULL);
2145
    assert(strlenpt(converted) <= COLS);
2146

2147
2148
2149
    /* 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);
2150

2151
#ifdef USING_OLD_NCURSES
2152
2153
2154
    /* 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. */
2155
2156
    if (seen_wide)
	wredrawln(edit, line, 1);
2157
#endif
2158

2159
#ifndef DISABLE_COLOR
2160
2161
2162
    /* If color syntaxes are available and turned on, we need to display
     * them. */
    if (openfile->colorstrings != NULL && !ISSET(NO_COLOR_SYNTAX)) {
2163
	const colortype *varnish = openfile->colorstrings;
2164

2165
2166
2167
2168
	/* If there are multiline regexes, make sure there is a cache. */
	if (openfile->syntax->nmultis > 0)
	    alloc_multidata_if_needed(fileptr);

2169
	for (; varnish != NULL; varnish = varnish->next) {
2170
2171
	    int x_start;
		/* Starting column for mvwaddnstr.  Zero-based. */
2172
	    int paintlen = 0;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2173
2174
		/* Number of chars to paint on this line.  There are
		 * COLS characters on a whole line. */
2175
	    size_t index;
2176
		/* Index in converted where we paint. */
2177
2178
2179
2180
	    regmatch_t startmatch;
		/* Match position for start_regex. */
	    regmatch_t endmatch;
		/* Match position for end_regex. */
2181

2182
	    wattron(edit, varnish->attributes);
2183
2184
2185
	    /* 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. */
2186

2187
2188
	    /* First case: varnish is a single-line expression. */
	    if (varnish->end == NULL) {
2189
2190
2191
		size_t k = 0;

		/* We increment k by rm_eo, to move past the end of the
2192
		 * last match.  Even though two matches may overlap, we
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2193
2194
		 * want to ignore them, so that we can highlight e.g. C
		 * strings correctly. */
2195
2196
2197
		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
2198
2199
2200
		     * unless k is zero.  If regexec() returns
		     * REG_NOMATCH, there are no more matches in the
		     * line. */
2201
		    if (regexec(varnish->start, &fileptr->data[k], 1,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2202
2203
			&startmatch, (k == 0) ? 0 : REG_NOTBOL) ==
			REG_NOMATCH)
2204
			break;
2205
2206
		    /* Translate the match to the beginning of the
		     * line. */
2207
2208
		    startmatch.rm_so += k;
		    startmatch.rm_eo += k;
2209
2210
2211

		    /* Skip over a zero-length regex match. */
		    if (startmatch.rm_so == startmatch.rm_eo)
2212
			startmatch.rm_eo++;
2213
		    else if (startmatch.rm_so < endpos &&
2214
			startmatch.rm_eo > startpos) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2215
2216
			x_start = (startmatch.rm_so <= startpos) ? 0 :
				strnlenpt(fileptr->data,
2217
				startmatch.rm_so) - start;
2218

2219
2220
2221
			index = actual_x(converted, x_start);

			paintlen = actual_x(converted + index,
2222
2223
				strnlenpt(fileptr->data,
				startmatch.rm_eo) - start - x_start);
2224
2225
2226

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

2227
			mvwaddnstr(edit, line, x_start, converted +
2228
				index, paintlen);
2229
		    }
2230
		    k = startmatch.rm_eo;
Chris Allegretta's avatar
Chris Allegretta committed
2231
		}
2232
	    } else {	/* Second case: varnish is a multiline expression. */
2233
		const filestruct *start_line = fileptr->prev;
2234
		    /* The first line before fileptr that matches 'start'. */
2235
		size_t start_col;
2236
		    /* Where the match starts in that line. */
2237
		const filestruct *end_line;
2238
		    /* The line that matches 'end'. */
2239

2240
		/* First see if the multidata was maybe already calculated. */
2241
		if (fileptr->multidata[varnish->id] == CNONE)
2242
		    goto tail_of_loop;
2243
		else if (fileptr->multidata[varnish->id] == CWHOLELINE) {
2244
		    mvwaddnstr(edit, line, 0, converted, -1);
2245
		    goto tail_of_loop;
2246
2247
		} else if (fileptr->multidata[varnish->id] == CBEGINBEFORE) {
		    regexec(varnish->end, fileptr->data, 1, &endmatch, 0);
2248
2249
		    /* If the coloured part is scrolled off, skip it. */
		    if (endmatch.rm_eo <= startpos)
2250
			goto tail_of_loop;
2251
2252
2253
		    paintlen = actual_x(converted, strnlenpt(fileptr->data,
			endmatch.rm_eo) - start);
		    mvwaddnstr(edit, line, 0, converted, paintlen);
2254
		    goto tail_of_loop;
2255
		} if (fileptr->multidata[varnish->id] == -1)
2256
		    /* Assume this until proven otherwise below. */
2257
		    fileptr->multidata[varnish->id] = CNONE;
2258

2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
		/* 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. */

2269
		while (start_line != NULL && regexec(varnish->start,
2270
2271
2272
			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. */
2273
		    if (regexec(varnish->end, start_line->data, 0, NULL, 0) == 0)
2274
2275
2276
			goto step_two;
		    start_line = start_line->prev;
		}
2277

2278
2279
2280
2281
		/* If no start was found, skip to the next step. */
		if (start_line == NULL)
		    goto step_two;

2282
		/* If a found start has been qualified as an end earlier,
2283
		 * believe it and skip to the next step. */
2284
		if (start_line->multidata != NULL &&
2285
2286
			(start_line->multidata[varnish->id] == CBEGINBEFORE ||
			start_line->multidata[varnish->id] == CSTARTENDHERE))
2287
2288
		    goto step_two;

2289
		/* Skip over a zero-length regex match. */
2290
		if (startmatch.rm_so == startmatch.rm_eo)
2291
2292
		    goto tail_of_loop;

2293
2294
2295
2296
2297
2298
2299
		/* 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;
2300
		    if (regexec(varnish->end, start_line->data +
2301
2302
2303
				start_col + startmatch.rm_eo, 0, NULL,
				(start_col + startmatch.rm_eo == 0) ?
				0 : REG_NOTBOL) == REG_NOMATCH)
2304
2305
2306
			/* No end found after this start. */
			break;
		    start_col++;
2307
2308
		    if (regexec(varnish->start, start_line->data + start_col,
				1, &startmatch, REG_NOTBOL) == REG_NOMATCH)
2309
2310
2311
2312
2313
2314
2315
2316
2317
			/* 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;
2318
		while (end_line != NULL && regexec(varnish->end,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2319
			end_line->data, 1, &endmatch, 0) == REG_NOMATCH)
2320
		    end_line = end_line->next;
2321

2322
2323
2324
2325
		/* 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) {
2326
		    fileptr->multidata[varnish->id] = CBEGINBEFORE;
2327
2328
		    goto step_two;
		}
2329

2330
2331
2332
2333
2334
2335
2336
		/* 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;
2337
		    fileptr->multidata[varnish->id] = CWHOLELINE;
2338
#ifdef DEBUG
2339
    fprintf(stderr, "  Marking for id %i  line %i as CWHOLELINE\n", varnish->id, line);
2340
#endif
2341
2342
2343
		} else {
		    paintlen = actual_x(converted, strnlenpt(fileptr->data,
						endmatch.rm_eo) - start);
2344
		    fileptr->multidata[varnish->id] = CBEGINBEFORE;
2345
#ifdef DEBUG
2346
    fprintf(stderr, "  Marking for id %i  line %i as CBEGINBEFORE\n", varnish->id, line);
2347
#endif
2348
2349
2350
2351
2352
2353
		}
		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;
2354
  step_two:
2355
2356
2357
2358
2359
		/* 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) {
2360
		    if (regexec(varnish->start, fileptr->data + start_col,
2361
2362
				1, &startmatch, (start_col == 0) ?
				0 : REG_NOTBOL) == REG_NOMATCH ||
2363
				start_col + startmatch.rm_so >= endpos)
2364
2365
			/* No more starts on this line. */
			break;
2366

2367
2368
2369
2370
2371
2372
2373
		    /* 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,
2374
				startmatch.rm_so) - start;
2375

2376
		    index = actual_x(converted, x_start);
2377

2378
		    if (regexec(varnish->end, fileptr->data +
2379
				startmatch.rm_eo, 1, &endmatch,
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
				(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 &&
2390
				endmatch.rm_eo > startmatch.rm_so) {
2391
			    paintlen = actual_x(converted + index,
2392
					strnlenpt(fileptr->data,
2393
					endmatch.rm_eo) - start - x_start);
2394

2395
			    assert(0 <= x_start && x_start < COLS);
2396

2397
			    mvwaddnstr(edit, line, x_start,
2398
					converted + index, paintlen);
2399
			    if (paintlen > 0) {
2400
				fileptr->multidata[varnish->id] = CSTARTENDHERE;
2401
#ifdef DEBUG
2402
    fprintf(stderr, "  Marking for id %i  line %i as CSTARTENDHERE\n", varnish->id, line);
2403
#endif
2404
			    }
2405
2406
			}
			start_col = endmatch.rm_eo;
2407
2408
2409
			/* Skip over a zero-length match. */
			if (endmatch.rm_so == endmatch.rm_eo)
			    start_col += 1;
2410
2411
2412
2413
		    } else {
			/* There is no end on this line.  But we haven't yet
			 * looked for one on later lines. */
			end_line = fileptr->next;
2414

2415
			while (end_line != NULL &&
2416
				regexec(varnish->end, end_line->data,
2417
				0, NULL, 0) == REG_NOMATCH)
2418
			    end_line = end_line->next;
2419

2420
2421
2422
			/* If there is no end, we're done on this line. */
			if (end_line == NULL)
			    break;
2423

2424
2425
2426
2427
			assert(0 <= x_start && x_start < COLS);

			/* Paint the rest of the line. */
			mvwaddnstr(edit, line, x_start, converted + index, -1);
2428
			fileptr->multidata[varnish->id] = CENDAFTER;
2429
#ifdef DEBUG
2430
    fprintf(stderr, "  Marking for id %i  line %i as CENDAFTER\n", varnish->id, line);
2431
#endif
2432
2433
2434
			/* We've painted to the end of the line, so don't
			 * bother checking for any more starts. */
			break;
2435
		    }
2436
2437
		}
	    }
2438
  tail_of_loop:
2439
	    wattroff(edit, varnish->attributes);
2440
	}
2441
    }
2442
#endif /* !DISABLE_COLOR */
2443

2444
#ifndef NANO_TINY
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
    /* 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. */
2456
	int x_start;
2457
	    /* The starting column for mvwaddnstr().  Zero-based. */
2458
	int paintlen;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2459
2460
	    /* Number of characters to paint on this line.  There are
	     * COLS characters on a whole line. */
2461
	size_t index;
2462
	    /* Index in converted where we paint. */
2463

2464
	mark_order(&top, &top_x, &bot, &bot_x, NULL);
2465
2466
2467
2468
2469

	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
2470

2471
	/* Only paint if the marked bit of fileptr is on this page. */
2472
2473
	if (top_x < endpos && bot_x > startpos) {
	    assert(startpos <= top_x);
2474
2475
2476
2477

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

2479
2480
2481
2482
2483
	    /* 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. */
2484
2485
2486
	    if (bot_x >= endpos)
		paintlen = -1;
	    else
2487
		paintlen = strnlenpt(fileptr->data, bot_x) - (x_start + start);
2488
2489
2490
2491
2492
2493
2494
2495

	    /* 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;
	    }
2496
2497
2498

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

2499
	    index = actual_x(converted, x_start);
2500

2501
2502
2503
	    if (paintlen > 0)
		paintlen = actual_x(converted + index, paintlen);

2504
	    wattron(edit, hilite_attribute);
2505
	    mvwaddnstr(edit, line, x_start, converted + index, paintlen);
2506
	    wattroff(edit, hilite_attribute);
Chris Allegretta's avatar
Chris Allegretta committed
2507
	}
2508
    }
2509
#endif /* !NANO_TINY */
Chris Allegretta's avatar
Chris Allegretta committed
2510
2511
}

2512
/* Just update one line in the edit buffer.  This is basically a wrapper
2513
 * for edit_draw().  The line will be displayed starting with
2514
 * fileptr->data[index].  Likely arguments are current_x or zero.
2515
 * Returns: Number of additional lines consumed (needed for SOFTWRAP). */
2516
int update_line(filestruct *fileptr, size_t index)
Chris Allegretta's avatar
Chris Allegretta committed
2517
{
2518
    int line = 0;
2519
	/* The line in the edit window that we want to update. */
2520
    int extralinesused = 0;
2521
2522
2523
2524
    char *converted;
	/* fileptr->data converted to have tabs and control characters
	 * expanded. */
    size_t page_start;
Chris Allegretta's avatar
Chris Allegretta committed
2525

2526
    assert(fileptr != NULL);
2527

2528
#ifndef NANO_TINY
2529
    if (ISSET(SOFTWRAP)) {
2530
2531
	filestruct *tmp;

2532
2533
	for (tmp = openfile->edittop; tmp && tmp != fileptr; tmp = tmp->next)
	    line += (strlenpt(tmp->data) / COLS) + 1;
2534
    } else
2535
#endif
2536
2537
	line = fileptr->lineno - openfile->edittop->lineno;

2538
    if (line < 0 || line >= editwinrows)
Chris Allegretta's avatar
Chris Allegretta committed
2539
	return 1;
2540

2541
    /* First, blank out the line. */
2542
    blank_line(edit, line, 0, COLS);
2543

2544
2545
    /* Next, convert variables that index the line to their equivalent
     * positions in the expanded line. */
2546
#ifndef NANO_TINY
2547
2548
2549
    if (ISSET(SOFTWRAP))
	index = 0;
    else
2550
#endif
2551
	index = strnlenpt(fileptr->data, index);
2552
    page_start = get_page_start(index);
2553

2554
2555
    /* Expand the line, replacing tabs with spaces, and control
     * characters with their displayed forms. */
2556
2557
2558
#ifdef NANO_TINY
    converted = display_string(fileptr->data, page_start, COLS, TRUE);
#else
2559
2560
2561
    converted = display_string(fileptr->data, page_start, COLS, !ISSET(SOFTWRAP));
#ifdef DEBUG
    if (ISSET(SOFTWRAP) && strlen(converted) >= COLS - 2)
2562
	fprintf(stderr, "update_line(): converted(1) line = %s\n", converted);
2563
#endif
2564
#endif /* !NANO_TINY */
2565

2566
    /* Paint the line. */
2567
    edit_draw(fileptr, converted, line, page_start);
2568
    free(converted);
Chris Allegretta's avatar
Chris Allegretta committed
2569

2570
#ifndef NANO_TINY
2571
    if (!ISSET(SOFTWRAP)) {
2572
#endif
2573
2574
2575
2576
	if (page_start > 0)
	    mvwaddch(edit, line, 0, '$');
	if (strlenpt(fileptr->data) > page_start + COLS)
	    mvwaddch(edit, line, COLS - 1, '$');
2577
#ifndef NANO_TINY
2578
    } else {
2579
	size_t full_length = strlenpt(fileptr->data);
2580
	for (index += COLS; index <= full_length && line < editwinrows - 1; index += COLS) {
2581
2582
	    line++;
#ifdef DEBUG
2583
	    fprintf(stderr, "update_line(): softwrap code, moving to %d index %lu\n", line, (unsigned long)index);
2584
#endif
2585
	    blank_line(edit, line, 0, COLS);
2586
2587

	    /* Expand the line, replacing tabs with spaces, and control
2588
	     * characters with their displayed forms. */
2589
2590
2591
2592
2593
	    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
2594
2595
2596

	    /* Paint the line. */
	    edit_draw(fileptr, converted, line, index);
2597
	    free(converted);
2598
2599
2600
	    extralinesused++;
	}
    }
2601
#endif /* !NANO_TINY */
2602
    return extralinesused;
Chris Allegretta's avatar
Chris Allegretta committed
2603
2604
}

2605
2606
/* 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
2607
2608
 * old_column and new_column are on different pages. */
bool need_screen_update(const size_t old_column, const size_t new_column)
2609
2610
{
    return
2611
#ifndef NANO_TINY
2612
	openfile->mark_set ||
2613
#endif
2614
	get_page_start(old_column) != get_page_start(new_column);
2615
2616
}

2617
/* When edittop changes, try and figure out how many lines
2618
 * we really have to work with (i.e. set maxrows). */
2619
2620
2621
2622
2623
void compute_maxrows(void)
{
    int n;
    filestruct *foo = openfile->edittop;

2624
2625
2626
2627
2628
    if (!ISSET(SOFTWRAP)) {
	maxrows = editwinrows;
	return;
    }

2629
2630
    maxrows = 0;
    for (n = 0; n < editwinrows && foo; n++) {
2631
	maxrows++;
2632
	n += strlenpt(foo->data) / COLS;
2633
2634
2635
	foo = foo->next;
    }

2636
2637
2638
    if (n < editwinrows)
	maxrows += editwinrows - n;

2639
#ifdef DEBUG
2640
    fprintf(stderr, "compute_maxrows(): maxrows = %d\n", maxrows);
2641
2642
2643
#endif
}

2644
2645
/* 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
2646
2647
 * scrolling.  direction is the direction to scroll, either UPWARD or
 * DOWNWARD, and nlines is the number of lines to scroll.  We change
2648
2649
 * edittop, and assume that current and current_x are up to date.  We
 * also assume that scrollok(edit) is FALSE. */
2650
void edit_scroll(scroll_dir direction, ssize_t nlines)
2651
{
2652
    ssize_t i;
2653
    filestruct *foo;
2654

2655
    assert(nlines > 0);
2656

2657
2658
2659
    /* Part 1: nlines is the number of lines we're going to scroll the
     * text of the edit window. */

2660
    /* Move the top line of the edit window up or down (depending on the
2661
2662
     * value of direction) nlines lines, or as many lines as we can if
     * there are fewer than nlines lines available. */
2663
    for (i = nlines; i > 0; i--) {
2664
	if (direction == UPWARD) {
2665
	    if (openfile->edittop == openfile->fileage)
2666
		break;
2667
	    openfile->edittop = openfile->edittop->prev;
2668
	} else {
2669
	    if (openfile->edittop == openfile->filebot)
2670
		break;
2671
	    openfile->edittop = openfile->edittop->next;
2672
	}
2673
2674

#ifndef NANO_TINY
2675
	/* Don't over-scroll on long lines. */
2676
	if (ISSET(SOFTWRAP) && direction == UPWARD) {
2677
	    ssize_t len = strlenpt(openfile->edittop->data) / COLS;
2678
	    i -= len;
2679
	    if (len > 0)
2680
		refresh_needed = TRUE;
2681
	}
2682
#endif
2683
2684
    }

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

2688
2689
2690
2691
    /* Don't bother scrolling zero lines, nor more than the window can hold. */
    if (nlines == 0)
	return;
    if (nlines >= editwinrows)
2692
	refresh_needed = TRUE;
2693

2694
    if (refresh_needed == TRUE)
2695
	return;
2696
2697
2698

    /* Scroll the text of the edit window up or down nlines lines,
     * depending on the value of direction. */
2699
    scrollok(edit, TRUE);
2700
    wscrl(edit, (direction == UPWARD) ? -nlines : nlines);
2701
2702
    scrollok(edit, FALSE);

2703
2704
2705
    /* Part 2: nlines is the number of lines in the scrolled region of
     * the edit window that we need to draw. */

2706
2707
    /* If the top or bottom line of the file is now visible in the edit
     * window, we need to draw the entire edit window. */
2708
2709
    if ((direction == UPWARD && openfile->edittop ==
	openfile->fileage) || (direction == DOWNWARD &&
2710
2711
	openfile->edittop->lineno + editwinrows - 1 >=
	openfile->filebot->lineno))
2712
	nlines = editwinrows;
2713

2714
2715
2716
2717
2718
2719
    /* 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
2720

2721
2722
    if (nlines > editwinrows)
	nlines = editwinrows;
2723
2724
2725

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

2728
2729
    /* If we scrolled down, move down to the line before the scrolled
     * region. */
2730
    if (direction == DOWNWARD) {
2731
	for (i = editwinrows - nlines; i > 0 && foo != NULL; i--)
2732
2733
2734
	    foo = foo->next;
    }

2735
2736
2737
2738
2739
2740
    /* 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--) {
2741
2742
	if ((i == nlines && direction == DOWNWARD) || (i == 1 &&
		direction == UPWARD)) {
2743
	    if (need_screen_update(openfile->placewewant, 0))
2744
2745
2746
2747
		update_line(foo, (foo == openfile->current) ?
			openfile->current_x : 0);
	} else
	    update_line(foo, (foo == openfile->current) ?
2748
		openfile->current_x : 0);
2749
	foo = foo->next;
2750
    }
2751
    compute_maxrows();
2752
2753
2754
}

/* Update any lines between old_current and current that need to be
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2755
 * updated.  Use this if we've moved without changing any text. */
2756
void edit_redraw(filestruct *old_current)
2757
{
2758
2759
2760
2761
    size_t was_pww = openfile->placewewant;

    openfile->placewewant = xplustabs();

2762
2763
    /* If the current line is offscreen, scroll until it's onscreen. */
    if (openfile->current->lineno >= openfile->edittop->lineno + maxrows ||
2764
		openfile->current->lineno < openfile->edittop->lineno) {
2765
	edit_update((focusing || !ISSET(SMOOTH_SCROLL)) ? CENTERING : FLOWING);
2766
	refresh_needed = TRUE;
2767
    }
2768

2769
#ifndef NANO_TINY
2770
2771
2772
    /* If the mark is on, update all lines between old_current and current. */
    if (openfile->mark_set) {
	filestruct *foo = old_current;
2773

2774
	while (foo != openfile->current) {
2775
	    update_line(foo, 0);
2776

2777
2778
2779
	    foo = (foo->lineno > openfile->current->lineno) ?
			foo->prev : foo->next;
	}
2780
2781
2782
2783
2784
2785
    } 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);
2786
2787
2788

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2795
2796
/* 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
2797
2798
void edit_refresh(void)
{
2799
    filestruct *foo;
2800
    int nlines;
2801

2802
    /* Figure out what maxrows should really be. */
2803
    compute_maxrows();
2804

2805
2806
    if (openfile->current->lineno < openfile->edittop->lineno ||
	openfile->current->lineno >= openfile->edittop->lineno +
2807
2808
	maxrows) {
#ifdef DEBUG
2809
2810
	fprintf(stderr, "edit_refresh(): line = %ld, edittop %ld + maxrows %d\n",
		(long)openfile->current->lineno, (long)openfile->edittop->lineno, maxrows);
2811
2812
#endif

2813
	/* Make sure the current line is on the screen. */
2814
	edit_update((focusing || !ISSET(SMOOTH_SCROLL)) ? CENTERING : STATIONARY);
2815
    }
Chris Allegretta's avatar
Chris Allegretta committed
2816

2817
2818
    foo = openfile->edittop;

2819
#ifdef DEBUG
2820
    fprintf(stderr, "edit_refresh(): edittop->lineno = %ld\n", (long)openfile->edittop->lineno);
2821
#endif
2822

2823
    for (nlines = 0; nlines < editwinrows && foo != NULL; nlines++) {
2824
	nlines += update_line(foo, (foo == openfile->current) ?
2825
		openfile->current_x : 0);
2826
2827
2828
	foo = foo->next;
    }

2829
    for (; nlines < editwinrows; nlines++)
2830
2831
2832
	blank_line(edit, nlines, 0, COLS);

    reset_cursor();
2833
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
2834
2835
}

2836
2837
2838
2839
2840
2841
/* 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
2842
{
2843
    int goal = 0;
2844

2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
    /* 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)
2855
	goal = editwinrows / 2;
2856
    else if (manner == FLOWING) {
2857
	if (openfile->current->lineno >= openfile->edittop->lineno)
2858
2859
	    goal = editwinrows - 1;
    } else {
2860
	goal = openfile->current_y;
2861

2862
	/* Limit goal to (editwinrows - 1) lines maximum. */
2863
2864
	if (goal > editwinrows - 1)
	    goal = editwinrows - 1;
Chris Allegretta's avatar
Chris Allegretta committed
2865
    }
2866

2867
2868
2869
2870
2871
    openfile->edittop = openfile->current;

    while (goal > 0 && openfile->edittop->prev != NULL) {
	openfile->edittop = openfile->edittop->prev;
	goal --;
2872
#ifndef NANO_TINY
2873
2874
	if (ISSET(SOFTWRAP))
	    goal -= strlenpt(openfile->edittop->data) / COLS;
2875
#endif
2876
    }
2877
#ifdef DEBUG
2878
    fprintf(stderr, "edit_update(): setting edittop to lineno %ld\n", (long)openfile->edittop->lineno);
2879
#endif
2880
    compute_maxrows();
Chris Allegretta's avatar
Chris Allegretta committed
2881
2882
}

2883
/* Unconditionally redraw the entire screen. */
2884
void total_redraw(void)
2885
{
2886
2887
2888
2889
2890
2891
#ifdef USE_SLANG
    /* Slang curses emulation brain damage, part 4: Slang doesn't define
     * curscr. */
    SLsmg_touch_screen();
    SLsmg_refresh();
#else
2892
    wrefresh(curscr);
2893
#endif
2894
2895
}

2896
2897
/* Unconditionally redraw the entire screen, and then refresh it using
 * the current file. */
2898
2899
void total_refresh(void)
{
2900
    total_redraw();
2901
    titlebar(NULL);
2902
    edit_refresh();
2903
    bottombars(currmenu);
2904
2905
}

2906
2907
/* Display the main shortcut list on the last two rows of the bottom
 * portion of the window. */
2908
2909
void display_main_list(void)
{
2910
#ifndef DISABLE_COLOR
2911
2912
    if (openfile->syntax &&
		(openfile->syntax->formatter || openfile->syntax->linter))
2913
	set_lint_or_format_shortcuts();
2914
2915
2916
2917
    else
	set_spell_shortcuts();
#endif

2918
    bottombars(MMAIN);
2919
2920
}

2921
/* If constant is TRUE, we display the current cursor position only if
2922
2923
 * suppress_cursorpos is FALSE.  If constant is FALSE, we display the
 * position always.  In any case we reset suppress_cursorpos to FALSE. */
2924
void do_cursorpos(bool constant)
Chris Allegretta's avatar
Chris Allegretta committed
2925
{
2926
    filestruct *f;
2927
    char c;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2928
    size_t i, cur_xpt = xplustabs() + 1;
2929
    size_t cur_lenpt = strlenpt(openfile->current->data) + 1;
2930
    int linepct, colpct, charpct;
Chris Allegretta's avatar
Chris Allegretta committed
2931

2932
    assert(openfile->fileage != NULL && openfile->current != NULL);
2933

2934
    /* Determine the size of the file up to the cursor. */
2935
    f = openfile->current->next;
2936
    c = openfile->current->data[openfile->current_x];
2937
2938

    openfile->current->next = NULL;
2939
    openfile->current->data[openfile->current_x] = '\0';
2940
2941
2942

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

2943
    openfile->current->data[openfile->current_x] = c;
2944
    openfile->current->next = f;
2945

2946
    /* If the position needs to be suppressed, don't suppress it next time. */
2947
    if (suppress_cursorpos && constant) {
2948
	suppress_cursorpos = FALSE;
2949
	return;
2950
    }
Chris Allegretta's avatar
Chris Allegretta committed
2951

2952
    /* Display the current cursor position on the statusbar. */
2953
    linepct = 100 * openfile->current->lineno / openfile->filebot->lineno;
2954
    colpct = 100 * cur_xpt / cur_lenpt;
2955
    charpct = (openfile->totsize == 0) ? 0 : 100 * i / openfile->totsize;
2956

2957
    statusline(HUSH,
2958
	_("line %ld/%ld (%d%%), col %lu/%lu (%d%%), char %lu/%lu (%d%%)"),
2959
	(long)openfile->current->lineno,
2960
	(long)openfile->filebot->lineno, linepct,
2961
	(unsigned long)cur_xpt, (unsigned long)cur_lenpt, colpct,
2962
	(unsigned long)i, (unsigned long)openfile->totsize, charpct);
2963
2964
2965

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

2968
/* Unconditionally display the current cursor position. */
2969
void do_cursorpos_void(void)
2970
{
2971
    do_cursorpos(FALSE);
2972
2973
}

2974
2975
void enable_nodelay(void)
{
2976
2977
    nodelay_mode = TRUE;
    nodelay(edit, TRUE);
2978
2979
2980
2981
}

void disable_nodelay(void)
{
2982
2983
    nodelay_mode = FALSE;
    nodelay(edit, FALSE);
2984
2985
}

2986
2987
/* Highlight the current word being replaced or spell checked.  We
 * expect word to have tabs and control characters expanded. */
2988
void spotlight(bool active, const char *word)
Chris Allegretta's avatar
Chris Allegretta committed
2989
{
2990
    size_t word_len = strlenpt(word), room;
Chris Allegretta's avatar
Chris Allegretta committed
2991

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

2995
    assert(room > 0);
2996

2997
2998
    if (word_len > room)
	room--;
2999

Chris Allegretta's avatar
Chris Allegretta committed
3000
    reset_cursor();
3001
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
3002

3003
    if (active)
3004
	wattron(edit, hilite_attribute);
Chris Allegretta's avatar
Chris Allegretta committed
3005

3006
    /* This is so we can show zero-length matches. */
3007
    if (word_len == 0)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3008
	waddch(edit, ' ');
3009
    else
3010
	waddnstr(edit, word, actual_x(word, room));
3011

3012
    if (word_len > room)
3013
	waddch(edit, '$');
Chris Allegretta's avatar
Chris Allegretta committed
3014

3015
    if (active)
3016
	wattroff(edit, hilite_attribute);
Chris Allegretta's avatar
Chris Allegretta committed
3017
3018
}

3019
#ifndef DISABLE_EXTRA
3020
3021
#define CREDIT_LEN 54
#define XLCREDIT_LEN 9
3022

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

3087
    const char *xlcredits[XLCREDIT_LEN] = {
3088
3089
3090
3091
3092
	N_("The nano text editor"),
	N_("version"),
	N_("Brought to you by:"),
	N_("Special thanks to:"),
	N_("The Free Software Foundation"),
3093
	N_("the many translators and the TP"),
3094
3095
3096
	N_("For ncurses:"),
	N_("and anyone else we forgot..."),
	N_("Thank you for using nano!")
3097
    };
3098

3099
3100
3101
3102
3103
3104
    if (!old_more_space || !old_no_help) {
	SET(MORE_SPACE);
	SET(NO_HELP);
	window_init();
    }

3105
3106
    curs_set(0);
    nodelay(edit, TRUE);
3107

3108
    blank_titlebar();
Chris Allegretta's avatar
Chris Allegretta committed
3109
    blank_edit();
3110
    blank_statusbar();
3111

3112
    wrefresh(topwin);
Chris Allegretta's avatar
Chris Allegretta committed
3113
    wrefresh(edit);
3114
    wrefresh(bottomwin);
3115
    napms(700);
3116

3117
    for (crpos = 0; crpos < CREDIT_LEN + editwinrows / 2; crpos++) {
3118
	if ((kbinput = wgetch(edit)) != ERR)
3119
	    break;
3120

3121
	if (crpos < CREDIT_LEN) {
3122
	    const char *what;
3123
3124
	    size_t start_x;

3125
	    if (credits[crpos] == NULL) {
3126
		assert(0 <= xlpos && xlpos < XLCREDIT_LEN);
3127

3128
		what = _(xlcredits[xlpos]);
3129
		xlpos++;
3130
	    } else
3131
		what = credits[crpos];
3132

3133
	    start_x = COLS / 2 - strlenpt(what) / 2 - 1;
3134
3135
	    mvwaddstr(edit, editwinrows - 1 - (editwinrows % 2),
		start_x, what);
3136
	}
3137

3138
3139
3140
3141
	wrefresh(edit);

	if ((kbinput = wgetch(edit)) != ERR)
	    break;
3142
	napms(700);
3143

3144
	scrollok(edit, TRUE);
3145
	wscrl(edit, 1);
3146
	scrollok(edit, FALSE);
3147
	wrefresh(edit);
3148

3149
	if ((kbinput = wgetch(edit)) != ERR)
3150
	    break;
3151
	napms(700);
3152

3153
	scrollok(edit, TRUE);
3154
	wscrl(edit, 1);
3155
	scrollok(edit, FALSE);
3156
	wrefresh(edit);
3157
3158
    }

3159
3160
3161
    if (kbinput != ERR)
	ungetch(kbinput);

3162
    if (!old_more_space)
3163
	UNSET(MORE_SPACE);
3164
    if (!old_no_help)
3165
	UNSET(NO_HELP);
3166
    window_init();
3167

3168
    nodelay(edit, FALSE);
3169

3170
    total_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
3171
}
3172
#endif /* !DISABLE_EXTRA */