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

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

25
#include <ctype.h>
26
#ifdef __linux__
27
#include <sys/ioctl.h>
28
#endif
Chris Allegretta's avatar
Chris Allegretta committed
29
30
#include <string.h>

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

37
static int *key_buffer = NULL;
38
39
	/* The keystroke buffer, containing all the keystrokes we
	 * haven't handled yet at a given point. */
40
static size_t key_buffer_len = 0;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
41
	/* The length of the keystroke buffer. */
42
43
static bool solitary = FALSE;
	/* Whether an Esc arrived by itself -- not as leader of a sequence. */
44
static bool waiting_mode = TRUE;
45
	/* Whether getting a character will wait for a key to be pressed. */
46
static int statusblank = 0;
47
	/* The number of keystrokes left before we blank the statusbar. */
48
#ifdef USING_OLD_NCURSES
49
50
static bool seen_wide = FALSE;
	/* Whether we've seen a multicolumn character in the current line. */
51
#endif
52
53
static bool reveal_cursor = FALSE;
	/* Whether the cursor should be shown when waiting for input. */
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#ifndef NANO_TINY
static bool recording = FALSE;
	/* Whether we are in the process of recording a macro. */
static int *macro_buffer = NULL;
	/* A buffer where the recorded key codes are stored. */
static size_t macro_length = 0;
	/* The current length of the macro. */

/* Add the given code to the macro buffer. */
void add_to_macrobuffer(int code)
{
    macro_length++;
    macro_buffer = (int*)nrealloc(macro_buffer, macro_length * sizeof(int));
    macro_buffer[macro_length - 1] = code;
}

/* Remove the last key code plus any trailing Esc codes from macro buffer. */
void snip_last_keystroke(void)
{
    macro_length--;
    while (macro_length > 0 && macro_buffer[macro_length - 1] == '\x1b')
	macro_length--;
}

/* Start or stop the recording of keystrokes. */
void record_macro(void)
{
    recording = !recording;

    if (recording) {
	macro_length = 0;
	statusbar(_("Recording a macro..."));
    } else {
	snip_last_keystroke();
	statusbar(_("Stopped recording"));
    }
}

/* Copy the stored sequence of codes into the regular key buffer,
 * so they will be "executed" again. */
void run_macro(void)
{
    size_t i;

    if (recording) {
	statusline(HUSH, _("Cannot run macro while recording"));
	snip_last_keystroke();
	return;
    }

    if (macro_length == 0)
	return;

    free(key_buffer);
    key_buffer = (int *)nmalloc(macro_length * sizeof(int));
    key_buffer_len = macro_length;

    for (i = 0; i < macro_length; i++)
	key_buffer[i] = macro_buffer[i];
}
#endif /* !NANO_TINY */
115

116
117
/* Control character compatibility:
 *
118
119
120
121
122
 * - 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.
123
124
 * - Ctrl-8 (Ctrl-?) is Delete under ASCII, ANSI, VT100, and VT220,
 *          but is Backspace under VT320.
125
 *
126
 * Note: VT220 and VT320 also generate Esc [ 3 ~ for Delete.  By
127
128
 * default, xterm assumes it's running on a VT320 and generates Ctrl-8
 * (Ctrl-?) for Backspace and Esc [ 3 ~ for Delete.  This causes
129
 * problems for VT100-derived terminals such as the FreeBSD console,
130
 * which expect Ctrl-H for Backspace and Ctrl-8 (Ctrl-?) for Delete, and
131
132
133
134
135
136
137
138
139
 * 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
140
 * console, the FreeBSD console, the Mach console, xterm, rxvt, Eterm,
141
142
 * and Terminal, and some for iTerm2.  Among these, there are several
 * conflicts and omissions, outlined as follows:
143
144
145
146
147
148
 *
 * - 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
149
 *   keypad key, because the latter has no value when NumLock is off.)
150
151
152
153
 * - 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.)
154
 * - F9 on FreeBSD console == PageDown on Mach console; the former is
155
156
157
 *   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.)
158
 * - F10 on FreeBSD console == PageUp on Mach console; the former is
159
 *   omitted.  (Same as above.)
160
 * - F13 on FreeBSD console == End on Mach console; the former is
161
 *   omitted.  (Same as above.)
162
163
164
165
166
167
 * - 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
168
 *   omitted.  (Same as above.) */
169

170
/* Read in a sequence of keystrokes from win and save them in the
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
171
172
 * keystroke buffer.  This should only be called when the keystroke
 * buffer is empty. */
173
void get_key_buffer(WINDOW *win)
174
{
175
    int input = ERR;
176
    size_t errcount = 0;
177
178
179
180
181

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

182
183
184
185
    /* Just before reading in the first character, display any pending
     * screen updates. */
    doupdate();

186
187
188
    if (reveal_cursor)
	curs_set(1);

189
190
191
    /* Read in the first keystroke using whatever mode we're in. */
    while (input == ERR) {
	input = wgetch(win);
192

193
#ifndef NANO_TINY
194
195
196
197
	if (the_window_resized) {
	    regenerate_screen();
	    input = KEY_WINCH;
	}
198
#endif
199
200
201
202
	if (input == ERR && !waiting_mode) {
	    curs_set(0);
	    return;
	}
203

204
205
206
207
	/* 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. */
208
	if (input == ERR && ++errcount == MAX_BUF_SIZE)
209
	    die(_("Too many errors from stdin"));
210
    }
211

212
213
    curs_set(0);

214
215
    /* Increment the length of the keystroke buffer, and save the value
     * of the keystroke at the end of it. */
216
    key_buffer_len++;
217
218
    key_buffer = (int *)nmalloc(sizeof(int));
    key_buffer[0] = input;
219

220
221
222
223
224
225
226
#ifndef NANO_TINY
    /* If we got SIGWINCH, get out immediately since the win argument is
     * no longer valid. */
    if (input == KEY_WINCH)
	return;
#endif

227
228
229
230
    /* Read in the remaining characters using non-blocking input. */
    nodelay(win, TRUE);

    while (TRUE) {
231
232
233
234
#ifndef NANO_TINY
	if (recording)
	    add_to_macrobuffer(input);
#endif
235
	input = wgetch(win);
236

237
	/* If there aren't any more characters, stop reading. */
238
	if (input == ERR)
239
240
	    break;

241
242
	/* Otherwise, increment the length of the keystroke buffer, and
	 * save the value of the keystroke at the end of it. */
243
	key_buffer_len++;
244
245
246
	key_buffer = (int *)nrealloc(key_buffer, key_buffer_len *
		sizeof(int));
	key_buffer[key_buffer_len - 1] = input;
247
248
    }

249
    /* Restore waiting mode if it was on. */
250
    if (waiting_mode)
251
	nodelay(win, FALSE);
252
253

#ifdef DEBUG
254
255
    {
	size_t i;
256
	fprintf(stderr, "\nget_key_buffer(): the sequence of hex codes:");
257
258
259
260
	for (i = 0; i < key_buffer_len; i++)
	    fprintf(stderr, " %3x", key_buffer[i]);
	fprintf(stderr, "\n");
    }
261
#endif
262
}
263

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
264
/* Return the length of the keystroke buffer. */
265
size_t get_key_buffer_len(void)
266
267
268
269
{
    return key_buffer_len;
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
270
/* Add the keystrokes in input to the keystroke buffer. */
271
void unget_input(int *input, size_t input_len)
272
273
{
    /* If input is empty, get out. */
274
    if (input_len == 0)
275
276
	return;

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
277
278
    /* If adding input would put the keystroke buffer beyond maximum
     * capacity, only add enough of input to put it at maximum
279
     * capacity. */
280
281
    if (key_buffer_len + input_len < key_buffer_len)
	input_len = (size_t)-1 - key_buffer_len;
282

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
283
284
285
    /* 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. */
286
287
288
    key_buffer_len += input_len;
    key_buffer = (int *)nrealloc(key_buffer, key_buffer_len *
	sizeof(int));
289

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
290
291
    /* If the keystroke buffer wasn't empty before, move its beginning
     * forward far enough so that we can add input to its beginning. */
292
293
294
    if (key_buffer_len > input_len)
	memmove(key_buffer + input_len, key_buffer,
		(key_buffer_len - input_len) * sizeof(int));
295

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
296
    /* Copy input to the beginning of the keystroke buffer. */
297
    memcpy(key_buffer, input, input_len * sizeof(int));
298
299
}

300
301
302
/* 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)
303
{
304
    unget_input(&kbinput, 1);
305

306
    if (metakey) {
307
	kbinput = ESC_CODE;
308
	unget_input(&kbinput, 1);
309
310
311
    }
}

312
/* Try to read input_len codes from the keystroke buffer.  If the
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
313
 * keystroke buffer is empty and win isn't NULL, try to read in more
314
 * codes from win and add them to the keystroke buffer before doing
315
 * anything else.  If the keystroke buffer is (still) empty, return NULL. */
316
int *get_input(WINDOW *win, size_t input_len)
317
{
318
    int *input;
319

320
321
    if (key_buffer_len == 0 && win != NULL)
	get_key_buffer(win);
322

323
324
    if (key_buffer_len == 0)
	return NULL;
325

326
    /* Limit the request to the number of available codes in the buffer. */
327
328
329
    if (input_len > key_buffer_len)
	input_len = key_buffer_len;

330
    /* Copy input_len codes from the head of the keystroke buffer. */
331
332
    input = (int *)nmalloc(input_len * sizeof(int));
    memcpy(input, key_buffer, input_len * sizeof(int));
333
    key_buffer_len -= input_len;
334

335
    /* If the keystroke buffer is now empty, mark it as such. */
336
337
338
339
    if (key_buffer_len == 0) {
	free(key_buffer);
	key_buffer = NULL;
    } else {
340
	/* Trim from the buffer the codes that were copied. */
341
	memmove(key_buffer, key_buffer + input_len, key_buffer_len *
342
343
344
		sizeof(int));
	key_buffer = (int *)nrealloc(key_buffer, key_buffer_len *
		sizeof(int));
345
346
347
    }

    return input;
348
349
}

350
/* Read in a single keystroke, ignoring any that are invalid. */
351
int get_kbinput(WINDOW *win, bool showcursor)
352
{
353
    int kbinput = ERR;
354

355
356
    reveal_cursor = showcursor;

357
    /* Extract one keystroke from the input stream. */
358
359
    while (kbinput == ERR)
	kbinput = parse_kbinput(win);
360

361
362
363
364
365
#ifdef DEBUG
    fprintf(stderr, "after parsing:  kbinput = %d, meta_key = %s\n",
	kbinput, meta_key ? "TRUE" : "FALSE");
#endif

366
    /* If we read from the edit window, blank the statusbar if needed. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
367
    if (win == edit)
368
369
	check_statusblank();

370
371
372
    return kbinput;
}

373
374
/* Extract a single keystroke from the input stream.  Translate escape
 * sequences and extended keypad codes into their corresponding values.
375
 * Set meta_key to TRUE when appropriate.  Supported extended keypad values
376
377
378
 * 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. */
379
int parse_kbinput(WINDOW *win)
380
{
381
    static int escapes = 0, byte_digits = 0;
382
    static bool double_esc = FALSE;
383
    int *kbinput, keycode, retval = ERR;
384

385
    meta_key = FALSE;
386
    shift_held = FALSE;
387

388
    /* Read in a character. */
389
390
    kbinput = get_input(win, 1);

391
    if (kbinput == NULL && !waiting_mode)
392
393
394
	return 0;

    while (kbinput == NULL)
395
	kbinput = get_input(win, 1);
396

397
398
399
    keycode = *kbinput;
    free(kbinput);

400
401
402
403
404
#ifdef DEBUG
    fprintf(stderr, "before parsing:  keycode = %d, escapes = %d, byte_digits = %d\n",
	keycode, escapes, byte_digits);
#endif

405
406
407
    if (keycode == ERR)
	return ERR;

408
    if (keycode == ESC_CODE) {
409
410
411
412
413
414
415
	/* 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;
416
417
    }

418
419
420
421
422
423
    switch (escapes) {
	case 0:
	    /* One non-escape: normal input mode. */
	    retval = keycode;
	    break;
	case 1:
424
425
426
	    if (keycode >= 0x80)
		retval = keycode;
	    else if ((keycode != 'O' && keycode != 'o' && keycode != '[') ||
427
			key_buffer_len == 0 || *key_buffer == ESC_CODE) {
428
429
430
431
432
433
434
435
436
		/* 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);
437
	    escapes = 0;
438
439
440
	    break;
	case 2:
	    if (double_esc) {
441
442
		/* An "ESC ESC [ X" sequence from Option+arrow, or
		 * an "ESC ESC [ x" sequence from Shift+Alt+arrow. */
443
444
445
446
447
448
449
450
		switch (keycode) {
		    case 'A':
			retval = KEY_HOME;
			break;
		    case 'B':
			retval = KEY_END;
			break;
		    case 'C':
451
			retval = CONTROL_RIGHT;
452
453
			break;
		    case 'D':
454
			retval = CONTROL_LEFT;
455
			break;
456
#ifndef NANO_TINY
457
458
459
460
461
462
463
464
465
466
467
468
		    case 'a':
			retval = shiftaltup;
			break;
		    case 'b':
			retval = shiftaltdown;
			break;
		    case 'c':
			retval = shiftaltright;
			break;
		    case 'd':
			retval = shiftaltleft;
			break;
469
#endif
470
471
472
		}
		double_esc = FALSE;
		escapes = 0;
473
	    } else if (key_buffer_len == 0) {
474
475
476
477
478
479
480
481
482
483
484
485
486
487
		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);

488
489
		    /* If the decimal byte value is complete, convert it and
		     * put the obtained byte(s) back into the input buffer. */
490
491
492
493
		    if (byte != ERR) {
			char *byte_mb;
			int byte_mb_len, *seq, i;

494
495
			/* Convert the decimal code to one or two bytes. */
			byte_mb = make_mbchar((long)byte, &byte_mb_len);
496

497
			seq = (int *)nmalloc(byte_mb_len * sizeof(int));
498
499
500
501

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

502
			/* Insert the byte(s) into the input buffer. */
503
504
505
506
			unget_input(seq, byte_mb_len);

			free(byte_mb);
			free(seq);
507
508
509

			byte_digits = 0;
			escapes = 0;
510
		    }
511
512
513
514
515
516
517
518
519
520
521
522
523
		} 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;
524
			retval = keycode;
525
		    }
526
		    escapes = 0;
527
528
		}
	    } else if (keycode == '[' && key_buffer_len > 0 &&
529
530
531
			(('A' <= *key_buffer && *key_buffer <= 'D') ||
			('a' <= *key_buffer && *key_buffer <= 'd'))) {
		/* An iTerm2/Eterm/rxvt sequence: ^[ ^[ [ X. */
532
533
534
535
536
		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);
537
538
		meta_key = TRUE;
		escapes = 0;
539
	    }
540
541
	    break;
	case 3:
542
	    if (key_buffer_len == 0)
543
544
545
546
547
548
549
550
551
552
		/* 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));
553
	    escapes = 0;
554
555
	    break;
    }
556

557
558
559
    if (retval == ERR)
	return ERR;

560
    if (retval == controlleft)
561
	return CONTROL_LEFT;
562
    else if (retval == controlright)
563
	return CONTROL_RIGHT;
564
    else if (retval == controlup)
565
	return CONTROL_UP;
566
    else if (retval == controldown)
567
	return CONTROL_DOWN;
568
569
570
571
    else if (retval == controlhome)
	return CONTROL_HOME;
    else if (retval == controlend)
	return CONTROL_END;
572
#ifndef NANO_TINY
573
574
    else if (retval == shiftcontrolleft) {
	shift_held = TRUE;
575
	return CONTROL_LEFT;
576
577
    } else if (retval == shiftcontrolright) {
	shift_held = TRUE;
578
	return CONTROL_RIGHT;
579
580
    } else if (retval == shiftcontrolup) {
	shift_held = TRUE;
581
	return CONTROL_UP;
582
583
    } else if (retval == shiftcontroldown) {
	shift_held = TRUE;
584
	return CONTROL_DOWN;
585
586
587
588
589
590
    } else if (retval == shiftcontrolhome) {
	shift_held = TRUE;
	return CONTROL_HOME;
    } else if (retval == shiftcontrolend) {
	shift_held = TRUE;
	return CONTROL_END;
591
    } else if (retval == altleft)
592
	return ALT_LEFT;
593
    else if (retval == altright)
594
	return ALT_RIGHT;
595
    else if (retval == altup)
596
	return ALT_UP;
597
    else if (retval == altdown)
598
	return ALT_DOWN;
599
    else if (retval == shiftaltleft) {
600
	shift_held = TRUE;
601
	return KEY_HOME;
602
603
    } else if (retval == shiftaltright) {
	shift_held = TRUE;
604
	return KEY_END;
605
606
    } else if (retval == shiftaltup) {
	shift_held = TRUE;
607
	return KEY_PPAGE;
608
609
    } else if (retval == shiftaltdown) {
	shift_held = TRUE;
610
	return KEY_NPAGE;
611
    }
612
613
#endif

614
#ifdef __linux__
615
    /* When not running under X, check for the bare arrow keys whether
616
617
618
619
     * Shift/Ctrl/Alt are being held together with them. */
    unsigned char modifiers = 6;

    if (console && ioctl(0, TIOCLINUX, &modifiers) >= 0) {
620
621
622
623
624
#ifndef NANO_TINY
	/* Is Shift being held? */
	if (modifiers & 0x01)
	    shift_held = TRUE;
#endif
625
626
	/* Is Ctrl being held? */
	if (modifiers & 0x04) {
627
	    if (retval == KEY_UP)
628
		return CONTROL_UP;
629
	    else if (retval == KEY_DOWN)
630
		return CONTROL_DOWN;
631
	    else if (retval == KEY_LEFT)
632
		return CONTROL_LEFT;
633
	    else if (retval == KEY_RIGHT)
634
		return CONTROL_RIGHT;
635
636
637
638
	    else if (retval == KEY_HOME)
		return CONTROL_HOME;
	    else if (retval == KEY_END)
		return CONTROL_END;
639
	}
640
#ifndef NANO_TINY
641
642
643
	/* Are both Shift and Alt being held? */
	if ((modifiers & 0x09) == 0x09) {
	    if (retval == KEY_UP)
644
		return KEY_PPAGE;
645
	    else if (retval == KEY_DOWN)
646
		return KEY_NPAGE;
647
	    else if (retval == KEY_LEFT)
648
		return KEY_HOME;
649
	    else if (retval == KEY_RIGHT)
650
		return KEY_END;
651
	}
652
#endif
653
    }
654
#endif /* __linux__ */
655

656
    switch (retval) {
657
#ifdef KEY_SLEFT
658
659
	/* Slang doesn't support KEY_SLEFT. */
	case KEY_SLEFT:
660
	    shift_held = TRUE;
661
	    return KEY_LEFT;
662
#endif
663
#ifdef KEY_SRIGHT
664
665
	/* Slang doesn't support KEY_SRIGHT. */
	case KEY_SRIGHT:
666
	    shift_held = TRUE;
667
	    return KEY_RIGHT;
668
#endif
669
#ifdef KEY_SR
670
#ifdef KEY_SUP
671
672
	/* ncurses and Slang don't support KEY_SUP. */
	case KEY_SUP:
673
#endif
674
675
	case KEY_SR:	/* Scroll backward, on Xfce4-terminal. */
	    shift_held = TRUE;
676
	    return KEY_UP;
677
678
#endif
#ifdef KEY_SF
679
#ifdef KEY_SDOWN
680
681
	/* ncurses and Slang don't support KEY_SDOWN. */
	case KEY_SDOWN:
682
#endif
683
684
	case KEY_SF:	/* Scroll forward, on Xfce4-terminal. */
	    shift_held = TRUE;
685
	    return KEY_DOWN;
686
#endif
687
#ifdef KEY_SHOME
688
689
	/* HP-UX 10-11 and Slang don't support KEY_SHOME. */
	case KEY_SHOME:
690
#endif
691
692
	case SHIFT_HOME:
	    shift_held = TRUE;
693
	case KEY_A1:	/* Home (7) on keypad with NumLock off. */
694
	    return KEY_HOME;
695
#ifdef KEY_SEND
696
697
	/* HP-UX 10-11 and Slang don't support KEY_SEND. */
	case KEY_SEND:
698
#endif
699
700
	case SHIFT_END:
	    shift_held = TRUE;
701
	case KEY_C1:	/* End (1) on keypad with NumLock off. */
702
	    return KEY_END;
703
#ifndef NANO_TINY
704
705
706
707
#ifdef KEY_SPREVIOUS
	case KEY_SPREVIOUS:
#endif
	case SHIFT_PAGEUP:	/* Fake key, from Shift+Alt+Up. */
708
709
	    shift_held = TRUE;
#endif
710
	case KEY_A3:	/* PageUp (9) on keypad with NumLock off. */
711
	    return KEY_PPAGE;
712
#ifndef NANO_TINY
713
714
715
#ifdef KEY_SNEXT
	case KEY_SNEXT:
#endif
716
717
718
	case SHIFT_PAGEDOWN:	/* Fake key, from Shift+Alt+Down. */
	    shift_held = TRUE;
#endif
719
	case KEY_C3:	/* PageDown (3) on keypad with NumLock off. */
720
	    return KEY_NPAGE;
721
#ifdef KEY_SDC
722
723
	/* Slang doesn't support KEY_SDC. */
	case KEY_SDC:
724
#endif
725
	case DEL_CODE:
726
	    if (ISSET(REBIND_DELETE))
727
		return the_code_for(do_delete, KEY_DC);
728
	    else
729
		return KEY_BACKSPACE;
730
#ifdef KEY_SIC
731
732
	/* Slang doesn't support KEY_SIC. */
	case KEY_SIC:
733
	    return the_code_for(do_insertfile_void, KEY_IC);
734
#endif
735
#ifdef KEY_SBEG
736
737
	/* Slang doesn't support KEY_SBEG. */
	case KEY_SBEG:
738
#endif
739
#ifdef KEY_BEG
740
741
	/* Slang doesn't support KEY_BEG. */
	case KEY_BEG:
742
#endif
743
744
	case KEY_B2:	/* Center (5) on keypad with NumLock off. */
	    return ERR;
745
#ifdef KEY_CANCEL
746
#ifdef KEY_SCANCEL
747
748
	/* Slang doesn't support KEY_SCANCEL. */
	case KEY_SCANCEL:
749
#endif
750
751
	/* Slang doesn't support KEY_CANCEL. */
	case KEY_CANCEL:
752
	    return the_code_for(do_cancel, 0x03);
753
#endif
754
#ifdef KEY_SUSPEND
755
#ifdef KEY_SSUSPEND
756
757
	/* Slang doesn't support KEY_SSUSPEND. */
	case KEY_SSUSPEND:
758
#endif
759
760
	/* Slang doesn't support KEY_SUSPEND. */
	case KEY_SUSPEND:
761
	    return the_code_for(do_suspend_void, KEY_SUSPEND);
762
763
#endif
#ifdef PDCURSES
764
765
766
767
768
769
770
	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;
771
#endif
772
773
#ifdef KEY_RESIZE
	/* Slang and SunOS 5.7-5.9 don't support KEY_RESIZE. */
774
	case KEY_RESIZE:
775
#endif
776
777
778
779
#if defined(USE_SLANG) && defined(ENABLE_UTF8)
	case KEY_BAD:
#endif
	case KEY_FLUSH:
780
	    return ERR;
781
    }
782

783
784
785
    return retval;
}

786
/* Translate escape sequences, most of which correspond to extended
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
787
 * keypad values, into their corresponding key values.  These sequences
788
789
 * are generated when the keypad doesn't support the needed keys.
 * Assume that Escape has already been read in. */
790
int convert_sequence(const int *seq, size_t seq_len)
791
{
792
    if (seq_len > 1) {
793
	switch (seq[0]) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
794
	    case 'O':
795
		switch (seq[1]) {
796
		    case '1':
797
798
			if (seq_len > 4  && seq[2] == ';') {

799
800
	switch (seq[3]) {
	    case '2':
801
802
803
804
805
		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. */
806
			shift_held = TRUE;
807
808
809
810
811
812
813
814
815
816
			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);
		}
817
818
		break;
	    case '5':
819
820
821
822
823
824
825
826
827
828
		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;
		}
829
830
		break;
	}
831

832
833
			}
			break;
834
		    case '2':
835
			if (seq_len >= 3) {
836
			    switch (seq[2]) {
837
				case 'P': /* Esc O 2 P == F13 on xterm. */
838
				    return KEY_F(13);
839
				case 'Q': /* Esc O 2 Q == F14 on xterm. */
840
				    return KEY_F(14);
841
				case 'R': /* Esc O 2 R == F15 on xterm. */
842
				    return KEY_F(15);
843
				case 'S': /* Esc O 2 S == F16 on xterm. */
844
				    return KEY_F(16);
845
846
			    }
			}
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
847
			break;
848
849
850
851
852
853
854
855
856
857
858
859
860
		    case '5':
			if (seq_len >= 3) {
			    switch (seq[2]) {
				case 'A': /* Esc O 5 A == Ctrl-Up on Haiku. */
				    return CONTROL_UP;
				case 'B': /* Esc O 5 B == Ctrl-Down on Haiku. */
				    return CONTROL_DOWN;
				case 'C': /* Esc O 5 C == Ctrl-Right on Haiku. */
				    return CONTROL_RIGHT;
				case 'D': /* Esc O 5 D == Ctrl-Left on Haiku. */
				    return CONTROL_LEFT;
			    }
			}
861
		    case 'A': /* Esc O A == Up on VT100/VT320/xterm. */
862
863
864
		    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. */
865
			return arrow_from_abcd(seq[1]);
866
867
		    case 'E': /* Esc O E == Center (5) on numeric keypad
			       * with NumLock off on xterm. */
868
			return KEY_B2;
869
		    case 'F': /* Esc O F == End on xterm/Terminal. */
870
			return KEY_END;
871
		    case 'H': /* Esc O H == Home on xterm/Terminal. */
872
			return KEY_HOME;
873
		    case 'M': /* Esc O M == Enter on numeric keypad with
874
			       * NumLock off on VT100/VT220/VT320/xterm/
875
			       * rxvt/Eterm. */
876
			return KEY_ENTER;
877
		    case 'P': /* Esc O P == F1 on VT100/VT220/VT320/Mach
878
			       * console. */
879
			return KEY_F(1);
880
		    case 'Q': /* Esc O Q == F2 on VT100/VT220/VT320/Mach
881
			       * console. */
882
			return KEY_F(2);
883
		    case 'R': /* Esc O R == F3 on VT100/VT220/VT320/Mach
884
			       * console. */
885
			return KEY_F(3);
886
		    case 'S': /* Esc O S == F4 on VT100/VT220/VT320/Mach
887
			       * console. */
888
			return KEY_F(4);
889
		    case 'T': /* Esc O T == F5 on Mach console. */
890
			return KEY_F(5);
891
		    case 'U': /* Esc O U == F6 on Mach console. */
892
			return KEY_F(6);
893
		    case 'V': /* Esc O V == F7 on Mach console. */
894
			return KEY_F(7);
895
		    case 'W': /* Esc O W == F8 on Mach console. */
896
			return KEY_F(8);
897
		    case 'X': /* Esc O X == F9 on Mach console. */
898
			return KEY_F(9);
899
		    case 'Y': /* Esc O Y == F10 on Mach console. */
900
			return KEY_F(10);
901
		    case 'a': /* Esc O a == Ctrl-Up on rxvt. */
902
			return CONTROL_UP;
903
		    case 'b': /* Esc O b == Ctrl-Down on rxvt. */
904
			return CONTROL_DOWN;
905
		    case 'c': /* Esc O c == Ctrl-Right on rxvt. */
906
			return CONTROL_RIGHT;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
907
		    case 'd': /* Esc O d == Ctrl-Left on rxvt. */
908
			return CONTROL_LEFT;
909
		    case 'j': /* Esc O j == '*' on numeric keypad with
910
			       * NumLock off on VT100/VT220/VT320/xterm/
911
			       * rxvt/Eterm/Terminal. */
912
			return '*';
913
		    case 'k': /* Esc O k == '+' on numeric keypad with
914
			       * NumLock off on VT100/VT220/VT320/xterm/
915
			       * rxvt/Eterm/Terminal. */
916
			return '+';
917
		    case 'l': /* Esc O l == ',' on numeric keypad with
918
			       * NumLock off on VT100/VT220/VT320/xterm/
919
			       * rxvt/Eterm/Terminal. */
920
			return ',';
921
		    case 'm': /* Esc O m == '-' on numeric keypad with
922
			       * NumLock off on VT100/VT220/VT320/xterm/
923
			       * rxvt/Eterm/Terminal. */
924
			return '-';
925
		    case 'n': /* Esc O n == Delete (.) on numeric keypad
926
			       * with NumLock off on VT100/VT220/VT320/
927
			       * xterm/rxvt/Eterm/Terminal. */
928
			return KEY_DC;
929
		    case 'o': /* Esc O o == '/' on numeric keypad with
930
			       * NumLock off on VT100/VT220/VT320/xterm/
931
			       * rxvt/Eterm/Terminal. */
932
			return '/';
933
		    case 'p': /* Esc O p == Insert (0) on numeric keypad
934
			       * with NumLock off on VT100/VT220/VT320/
935
			       * rxvt/Eterm/Terminal. */
936
			return KEY_IC;
937
		    case 'q': /* Esc O q == End (1) on numeric keypad
938
			       * with NumLock off on VT100/VT220/VT320/
939
			       * rxvt/Eterm/Terminal. */
940
			return KEY_END;
941
		    case 'r': /* Esc O r == Down (2) on numeric keypad
942
			       * with NumLock off on VT100/VT220/VT320/
943
			       * rxvt/Eterm/Terminal. */
944
			return KEY_DOWN;
945
		    case 's': /* Esc O s == PageDown (3) on numeric
946
			       * keypad with NumLock off on VT100/VT220/
947
			       * VT320/rxvt/Eterm/Terminal. */
948
			return KEY_NPAGE;
949
		    case 't': /* Esc O t == Left (4) on numeric keypad
950
			       * with NumLock off on VT100/VT220/VT320/
951
			       * rxvt/Eterm/Terminal. */
952
			return KEY_LEFT;
953
		    case 'u': /* Esc O u == Center (5) on numeric keypad
954
955
			       * with NumLock off on VT100/VT220/VT320/
			       * rxvt/Eterm. */
956
			return KEY_B2;
957
		    case 'v': /* Esc O v == Right (6) on numeric keypad
958
			       * with NumLock off on VT100/VT220/VT320/
959
			       * rxvt/Eterm/Terminal. */
960
			return KEY_RIGHT;
961
		    case 'w': /* Esc O w == Home (7) on numeric keypad
962
			       * with NumLock off on VT100/VT220/VT320/
963
			       * rxvt/Eterm/Terminal. */
964
			return KEY_HOME;
965
		    case 'x': /* Esc O x == Up (8) on numeric keypad
966
			       * with NumLock off on VT100/VT220/VT320/
967
			       * rxvt/Eterm/Terminal. */
968
			return KEY_UP;
969
		    case 'y': /* Esc O y == PageUp (9) on numeric keypad
970
			       * with NumLock off on VT100/VT220/VT320/
971
			       * rxvt/Eterm/Terminal. */
972
			return KEY_PPAGE;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
973
974
975
		}
		break;
	    case 'o':
976
		switch (seq[1]) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
977
		    case 'a': /* Esc o a == Ctrl-Up on Eterm. */
978
			return CONTROL_UP;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
979
		    case 'b': /* Esc o b == Ctrl-Down on Eterm. */
980
			return CONTROL_DOWN;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
981
		    case 'c': /* Esc o c == Ctrl-Right on Eterm. */
982
			return CONTROL_RIGHT;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
983
		    case 'd': /* Esc o d == Ctrl-Left on Eterm. */
984
			return CONTROL_LEFT;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
985
986
987
		}
		break;
	    case '[':
988
		switch (seq[1]) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
989
		    case '1':
990
			if (seq_len > 3 && seq[3] == '~') {
991
			    switch (seq[2]) {
992
				case '1': /* Esc [ 1 1 ~ == F1 on rxvt/Eterm. */
993
				    return KEY_F(1);
994
				case '2': /* Esc [ 1 2 ~ == F2 on rxvt/Eterm. */
995
				    return KEY_F(2);
996
				case '3': /* Esc [ 1 3 ~ == F3 on rxvt/Eterm. */
997
				    return KEY_F(3);
998
				case '4': /* Esc [ 1 4 ~ == F4 on rxvt/Eterm. */
999
				    return KEY_F(4);
1000
1001
				case '5': /* Esc [ 1 5 ~ == F5 on xterm/
					   * rxvt/Eterm. */
1002
				    return KEY_F(5);
1003
				case '7': /* Esc [ 1 7 ~ == F6 on
1004
1005
					   * VT220/VT320/Linux console/
					   * xterm/rxvt/Eterm. */
1006
				    return KEY_F(6);
1007
				case '8': /* Esc [ 1 8 ~ == F7 on
1008
1009
					   * VT220/VT320/Linux console/
					   * xterm/rxvt/Eterm. */
1010
				    return KEY_F(7);
1011
				case '9': /* Esc [ 1 9 ~ == F8 on
1012
1013
					   * VT220/VT320/Linux console/
					   * xterm/rxvt/Eterm. */
1014
				    return KEY_F(8);
1015
1016
1017
			    }
			} else if (seq_len > 4 && seq[2] == ';') {

1018
	switch (seq[3]) {
1019
	    case '2':
1020
1021
1022
1023
1024
		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. */
1025
			shift_held = TRUE;
1026
1027
			return arrow_from_abcd(seq[4]);
		}
1028
		break;
1029
#ifndef NANO_TINY
1030
	    case '9': /* To accomodate iTerm2 in "xterm mode". */
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
	    case '3':
		switch (seq[4]) {
		    case 'A': /* Esc [ 1 ; 3 A == Alt-Up on xterm. */
			return ALT_UP;
		    case 'B': /* Esc [ 1 ; 3 B == Alt-Down on xterm. */
			return ALT_DOWN;
		    case 'C': /* Esc [ 1 ; 3 C == Alt-Right on xterm. */
			return ALT_RIGHT;
		    case 'D': /* Esc [ 1 ; 3 D == Alt-Left on xterm. */
			return ALT_LEFT;
		}
		break;
1043
1044
1045
1046
1047
1048
1049
1050
1051
	    case '4':
		/* When the arrow keys are held together with Shift+Meta,
		 * act as if they are Home/End/PgUp/PgDown with Shift. */
		switch (seq[4]) {
		    case 'A': /* Esc [ 1 ; 4 A == Shift-Alt-Up on xterm. */
			return SHIFT_PAGEUP;
		    case 'B': /* Esc [ 1 ; 4 B == Shift-Alt-Down on xterm. */
			return SHIFT_PAGEDOWN;
		    case 'C': /* Esc [ 1 ; 4 C == Shift-Alt-Right on xterm. */
1052
			return SHIFT_END;
1053
		    case 'D': /* Esc [ 1 ; 4 D == Shift-Alt-Left on xterm. */
1054
			return SHIFT_HOME;
1055
1056
1057
		}
		break;
#endif
1058
	    case '5':
1059
1060
1061
1062
1063
1064
1065
1066
1067
		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;
1068
1069
1070
1071
		    case 'F': /* Esc [ 1 ; 5 F == Ctrl-End on xterm. */
			return CONTROL_END;
		    case 'H': /* Esc [ 1 ; 5 H == Ctrl-Home on xterm. */
			return CONTROL_HOME;
1072
		}
1073
		break;
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
#ifndef NANO_TINY
	    case '6':
		switch (seq[4]) {
		    case 'A': /* Esc [ 1 ; 6 A == Shift-Ctrl-Up on xterm. */
			return shiftcontrolup;
		    case 'B': /* Esc [ 1 ; 6 B == Shift-Ctrl-Down on xterm. */
			return shiftcontroldown;
		    case 'C': /* Esc [ 1 ; 6 C == Shift-Ctrl-Right on xterm. */
			return shiftcontrolright;
		    case 'D': /* Esc [ 1 ; 6 D == Shift-Ctrl-Left on xterm. */
			return shiftcontrolleft;
1085
1086
1087
1088
		    case 'F': /* Esc [ 1 ; 6 F == Shift-Ctrl-End on xterm. */
			return shiftcontrolend;
		    case 'H': /* Esc [ 1 ; 6 H == Shift-Ctrl-Home on xterm. */
			return shiftcontrolhome;
1089
1090
1091
		}
		break;
#endif
1092
	}
1093
1094
1095
1096

			} else if (seq_len > 2 && seq[2] == '~')
			    /* Esc [ 1 ~ == Home on VT320/Linux console. */
			    return KEY_HOME;
1097
1098
			break;
		    case '2':
1099
			if (seq_len > 3 && seq[3] == '~') {
1100
			    switch (seq[2]) {
1101
1102
				case '0': /* Esc [ 2 0 ~ == F9 on VT220/VT320/
					   * Linux console/xterm/rxvt/Eterm. */
1103
				    return KEY_F(9);
1104
1105
				case '1': /* Esc [ 2 1 ~ == F10 on VT220/VT320/
					   * Linux console/xterm/rxvt/Eterm. */
1106
				    return KEY_F(10);
1107
1108
				case '3': /* Esc [ 2 3 ~ == F11 on VT220/VT320/
					   * Linux console/xterm/rxvt/Eterm. */
1109
				    return KEY_F(11);
1110
1111
				case '4': /* Esc [ 2 4 ~ == F12 on VT220/VT320/
					   * Linux console/xterm/rxvt/Eterm. */
1112
				    return KEY_F(12);
1113
1114
				case '5': /* Esc [ 2 5 ~ == F13 on VT220/VT320/
					   * Linux console/rxvt/Eterm. */
1115
				    return KEY_F(13);
1116
1117
				case '6': /* Esc [ 2 6 ~ == F14 on VT220/VT320/
					   * Linux console/rxvt/Eterm. */
1118
				    return KEY_F(14);
1119
1120
				case '8': /* Esc [ 2 8 ~ == F15 on VT220/VT320/
					   * Linux console/rxvt/Eterm. */
1121
				    return KEY_F(15);
1122
1123
				case '9': /* Esc [ 2 9 ~ == F16 on VT220/VT320/
					   * Linux console/rxvt/Eterm. */
1124
				    return KEY_F(16);
1125
			    }
1126
1127
1128
1129
			} 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
1130
			break;
1131
		    case '3': /* Esc [ 3 ~ == Delete on VT220/VT320/
1132
			       * Linux console/xterm/Terminal. */
1133
1134
1135
			if (seq_len > 2 && seq[2] == '~')
			    return KEY_DC;
			break;
1136
		    case '4': /* Esc [ 4 ~ == End on VT220/VT320/Linux
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1137
			       * console/xterm. */
1138
1139
1140
			if (seq_len > 2 && seq[2] == '~')
			    return KEY_END;
			break;
1141
		    case '5': /* Esc [ 5 ~ == PageUp on VT220/VT320/
1142
1143
			       * Linux console/xterm/Terminal;
			       * Esc [ 5 ^ == PageUp on Eterm. */
1144
1145
1146
			if (seq_len > 2 && (seq[2] == '~' || seq[2] == '^'))
			    return KEY_PPAGE;
			break;
1147
		    case '6': /* Esc [ 6 ~ == PageDown on VT220/VT320/
1148
			       * Linux console/xterm/Terminal;
1149
			       * Esc [ 6 ^ == PageDown on Eterm. */
1150
1151
1152
			if (seq_len > 2 && (seq[2] == '~' || seq[2] == '^'))
			    return KEY_NPAGE;
			break;
1153
1154
1155
1156
		    case '7': /* Esc [ 7 ~ == Home on Eterm/rxvt;
			       * Esc [ 7 $ == Shift-Home on Eterm/rxvt;
			       * Esc [ 7 ^ == Control-Home on Eterm/rxvt;
			       * Esc [ 7 @ == Shift-Control-Home on same. */
1157
1158
1159
1160
			if (seq_len > 2 && seq[2] == '~')
			    return KEY_HOME;
			else if (seq_len > 2 && seq[2] == '$')
			    return SHIFT_HOME;
1161
1162
1163
1164
1165
1166
			else if (seq_len > 2 && seq[2] == '^')
			    return CONTROL_HOME;
#ifndef NANO_TINY
			else if (seq_len > 2 && seq[2] == '@')
			    return shiftcontrolhome;
#endif
1167
			break;
1168
1169
1170
1171
		    case '8': /* Esc [ 8 ~ == End on Eterm/rxvt;
			       * Esc [ 8 $ == Shift-End on Eterm/rxvt;
			       * Esc [ 8 ^ == Control-End on Eterm/rxvt;
			       * Esc [ 8 @ == Shift-Control-End on same. */
1172
1173
1174
1175
			if (seq_len > 2 && seq[2] == '~')
			    return KEY_END;
			else if (seq_len > 2 && seq[2] == '$')
			    return SHIFT_END;
1176
1177
1178
1179
1180
1181
			else if (seq_len > 2 && seq[2] == '^')
			    return CONTROL_END;
#ifndef NANO_TINY
			else if (seq_len > 2 && seq[2] == '@')
			    return shiftcontrolend;
#endif
1182
			break;
1183
		    case '9': /* Esc [ 9 == Delete on Mach console. */
1184
			return KEY_DC;
1185
		    case '@': /* Esc [ @ == Insert on Mach console. */
1186
			return KEY_IC;
1187
		    case 'A': /* Esc [ A == Up on ANSI/VT220/Linux
1188
			       * console/FreeBSD console/Mach console/
1189
			       * rxvt/Eterm/Terminal. */
1190
		    case 'B': /* Esc [ B == Down on ANSI/VT220/Linux
1191
			       * console/FreeBSD console/Mach console/
1192
			       * rxvt/Eterm/Terminal. */
1193
		    case 'C': /* Esc [ C == Right on ANSI/VT220/Linux
1194
			       * console/FreeBSD console/Mach console/
1195
			       * rxvt/Eterm/Terminal. */
1196
		    case 'D': /* Esc [ D == Left on ANSI/VT220/Linux
1197
			       * console/FreeBSD console/Mach console/
1198
			       * rxvt/Eterm/Terminal. */
1199
			return arrow_from_abcd(seq[1]);
1200
		    case 'E': /* Esc [ E == Center (5) on numeric keypad
1201
1202
			       * with NumLock off on FreeBSD console/
			       * Terminal. */
1203
			return KEY_B2;
1204
		    case 'F': /* Esc [ F == End on FreeBSD console/Eterm. */
1205
			return KEY_END;
1206
		    case 'G': /* Esc [ G == PageDown on FreeBSD console. */
1207
			return KEY_NPAGE;
1208
		    case 'H': /* Esc [ H == Home on ANSI/VT220/FreeBSD
1209
			       * console/Mach console/Eterm. */
1210
			return KEY_HOME;
1211
		    case 'I': /* Esc [ I == PageUp on FreeBSD console. */
1212
			return KEY_PPAGE;
1213
		    case 'L': /* Esc [ L == Insert on ANSI/FreeBSD console. */
1214
			return KEY_IC;
1215
		    case 'M': /* Esc [ M == F1 on FreeBSD console. */
1216
			return KEY_F(1);
1217
		    case 'N': /* Esc [ N == F2 on FreeBSD console. */
1218
			return KEY_F(2);
1219
		    case 'O':
1220
			if (seq_len > 2) {
1221
			    switch (seq[2]) {
1222
				case 'P': /* Esc [ O P == F1 on xterm. */
1223
				    return KEY_F(1);
1224
				case 'Q': /* Esc [ O Q == F2 on xterm. */
1225
				    return KEY_F(2);
1226
				case 'R': /* Esc [ O R == F3 on xterm. */
1227
				    return KEY_F(3);
1228
				case 'S': /* Esc [ O S == F4 on xterm. */
1229
				    return KEY_F(4);
1230
			    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1231
			} else
1232
			    /* Esc [ O == F3 on FreeBSD console. */
1233
			    return KEY_F(3);
1234
1235
			break;
		    case 'P': /* Esc [ P == F4 on FreeBSD console. */
1236
			return KEY_F(4);
1237
		    case 'Q': /* Esc [ Q == F5 on FreeBSD console. */
1238
			return KEY_F(5);
1239
		    case 'R': /* Esc [ R == F6 on FreeBSD console. */
1240
			return KEY_F(6);
1241
		    case 'S': /* Esc [ S == F7 on FreeBSD console. */
1242
			return KEY_F(7);
1243
		    case 'T': /* Esc [ T == F8 on FreeBSD console. */
1244
			return KEY_F(8);
1245
		    case 'U': /* Esc [ U == PageDown on Mach console. */
1246
			return KEY_NPAGE;
1247
		    case 'V': /* Esc [ V == PageUp on Mach console. */
1248
			return KEY_PPAGE;
1249
		    case 'W': /* Esc [ W == F11 on FreeBSD console. */
1250
			return KEY_F(11);
1251
		    case 'X': /* Esc [ X == F12 on FreeBSD console. */
1252
			return KEY_F(12);
1253
		    case 'Y': /* Esc [ Y == End on Mach console. */
1254
			return KEY_END;
1255
		    case 'Z': /* Esc [ Z == F14 on FreeBSD console. */
1256
			return KEY_F(14);
1257
		    case 'a': /* Esc [ a == Shift-Up on rxvt/Eterm. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1258
		    case 'b': /* Esc [ b == Shift-Down on rxvt/Eterm. */
1259
		    case 'c': /* Esc [ c == Shift-Right on rxvt/Eterm. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1260
		    case 'd': /* Esc [ d == Shift-Left on rxvt/Eterm. */
1261
			shift_held = TRUE;
1262
			return arrow_from_abcd(seq[1]);
1263
		    case '[':
1264
			if (seq_len > 2 ) {
1265
			    switch (seq[2]) {
1266
1267
				case 'A': /* Esc [ [ A == F1 on Linux
					   * console. */
1268
				    return KEY_F(1);
1269
1270
				case 'B': /* Esc [ [ B == F2 on Linux
					   * console. */
1271
				    return KEY_F(2);
1272
1273
				case 'C': /* Esc [ [ C == F3 on Linux
					   * console. */
1274
				    return KEY_F(3);
1275
1276
				case 'D': /* Esc [ [ D == F4 on Linux
					   * console. */
1277
				    return KEY_F(4);
1278
1279
				case 'E': /* Esc [ [ E == F5 on Linux
					   * console. */
1280
				    return KEY_F(5);
1281
1282
			    }
			}
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1283
1284
1285
1286
			break;
		}
		break;
	}
1287
1288
    }

1289
    return ERR;
1290
1291
}

1292
1293
/* Return the equivalent arrow-key value for the first four letters
 * in the alphabet, common to many escape sequences. */
1294
int arrow_from_abcd(int kbinput)
1295
1296
1297
{
    switch (tolower(kbinput)) {
	case 'a':
1298
	    return KEY_UP;
1299
	case 'b':
1300
	    return KEY_DOWN;
1301
	case 'c':
1302
	    return KEY_RIGHT;
1303
	case 'd':
1304
	    return KEY_LEFT;
1305
1306
1307
1308
1309
	default:
	    return ERR;
    }
}

1310
/* Interpret the escape sequence in the keystroke buffer, the first
1311
1312
 * character of which is kbinput.  Assume that the keystroke buffer
 * isn't empty, and that the initial escape has already been read in. */
1313
int parse_escape_sequence(WINDOW *win, int kbinput)
1314
1315
1316
1317
1318
1319
1320
1321
{
    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);
1322
    seq_len = key_buffer_len;
1323
    seq = get_input(NULL, seq_len);
1324
    retval = convert_sequence(seq, seq_len);
1325
1326
1327

    free(seq);

1328
    /* If we got an unrecognized escape sequence, notify the user. */
1329
    if (retval == ERR) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1330
	if (win == edit) {
1331
1332
	    /* TRANSLATORS: This refers to a sequence of escape codes
	     * (from the keyboard) that nano does not know about. */
1333
	    statusline(ALERT, _("Unknown sequence"));
1334
	    suppress_cursorpos = FALSE;
1335
	    lastmessage = HUSH;
1336
	    if (currmenu == MMAIN) {
1337
		place_the_cursor();
1338
1339
		curs_set(1);
	    }
1340
1341
1342
	}
    }

1343
#ifdef DEBUG
1344
1345
    fprintf(stderr, "parse_escape_sequence(): kbinput = %d, seq_len = %lu, retval = %d\n",
		kbinput, (unsigned long)seq_len, retval);
1346
1347
1348
1349
1350
#endif

    return retval;
}

1351
1352
/* Translate a byte sequence: turn a three-digit decimal number (from
 * 000 to 255) into its corresponding byte value. */
1353
int get_byte_kbinput(int kbinput)
1354
{
1355
    static int byte_digits = 0, byte = 0;
1356
    int retval = ERR;
1357

1358
1359
    /* Increment the byte digit counter. */
    byte_digits++;
1360

1361
    switch (byte_digits) {
1362
	case 1:
1363
1364
	    /* First digit: This must be from zero to two.  Put it in
	     * the 100's position of the byte sequence holder. */
1365
	    if ('0' <= kbinput && kbinput <= '2')
1366
		byte = (kbinput - '0') * 100;
1367
	    else
1368
1369
		/* This isn't the start of a byte sequence.  Return this
		 * character as the result. */
1370
1371
1372
		retval = kbinput;
	    break;
	case 2:
1373
1374
1375
1376
	    /* 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. */
1377
1378
1379
	    if (('0' <= kbinput && kbinput <= '5') || (byte < 200 &&
		'6' <= kbinput && kbinput <= '9'))
		byte += (kbinput - '0') * 10;
1380
	    else
1381
1382
		/* This isn't the second digit of a byte sequence.
		 * Return this character as the result. */
1383
1384
1385
		retval = kbinput;
	    break;
	case 3:
1386
	    /* Third digit: This must be from zero to five if the first
1387
1388
1389
	     * 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. */
1390
1391
	    if (('0' <= kbinput && kbinput <= '5') || (byte < 250 &&
		'6' <= kbinput && kbinput <= '9')) {
1392
		byte += kbinput - '0';
1393
		/* The byte sequence is complete. */
1394
		retval = byte;
1395
	    } else
1396
1397
		/* This isn't the third digit of a byte sequence.
		 * Return this character as the result. */
1398
1399
		retval = kbinput;
	    break;
1400
	default:
1401
1402
1403
	    /* If there are more than three digits, return this
	     * character as the result.  (Maybe we should produce an
	     * error instead?) */
1404
1405
1406
1407
1408
1409
1410
1411
	    retval = kbinput;
	    break;
    }

    /* If we have a result, reset the byte digit counter and the byte
     * sequence holder. */
    if (retval != ERR) {
	byte_digits = 0;
1412
	byte = 0;
1413
1414
1415
    }

#ifdef DEBUG
1416
    fprintf(stderr, "get_byte_kbinput(): kbinput = %d, byte_digits = %d, byte = %d, retval = %d\n", kbinput, byte_digits, byte, retval);
1417
1418
1419
1420
1421
#endif

    return retval;
}

1422
#ifdef ENABLE_UTF8
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1423
/* If the character in kbinput is a valid hexadecimal digit, multiply it
1424
 * by factor and add the result to uni, and return ERR to signify okay. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1425
1426
1427
1428
1429
1430
1431
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
1432
1433
	/* The character isn't hexadecimal; give it as the result. */
	return (long)kbinput;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1434

1435
    return ERR;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1436
1437
}

1438
/* Translate a Unicode sequence: turn a six-digit hexadecimal number
1439
 * (from 000000 to 10FFFF, case-insensitive) into its corresponding
1440
 * multibyte value. */
1441
long get_unicode_kbinput(WINDOW *win, int kbinput)
1442
{
1443
1444
1445
    static int uni_digits = 0;
    static long uni = 0;
    long retval = ERR;
1446

1447
    /* Increment the Unicode digit counter. */
1448
    uni_digits++;
1449

1450
    switch (uni_digits) {
1451
	case 1:
1452
1453
1454
1455
	    /* 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')
1456
		uni = (kbinput - '0') * 0x100000;
1457
1458
1459
1460
	    else
		retval = kbinput;
	    break;
	case 2:
1461
1462
1463
	    /* 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)
1464
		retval = add_unicode_digit(kbinput, 0x10000, &uni);
1465
1466
1467
1468
	    else
		retval = kbinput;
	    break;
	case 3:
1469
	    /* Later digits may be any hexadecimal value. */
1470
	    retval = add_unicode_digit(kbinput, 0x1000, &uni);
1471
	    break;
1472
	case 4:
1473
	    retval = add_unicode_digit(kbinput, 0x100, &uni);
1474
	    break;
1475
	case 5:
1476
	    retval = add_unicode_digit(kbinput, 0x10, &uni);
1477
	    break;
1478
	case 6:
1479
	    retval = add_unicode_digit(kbinput, 0x1, &uni);
1480
1481
	    /* If also the sixth digit was a valid hexadecimal value, then
	     * the Unicode sequence is complete, so return it. */
1482
	    if (retval == ERR)
1483
		retval = uni;
1484
1485
	    break;
    }
1486

1487
    /* Show feedback only when editing, not when at a prompt. */
1488
    if (retval == ERR && win == edit) {
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
	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);
    }
1499

1500
1501
1502
1503
    /* If we have an end result, reset the Unicode digit counter. */
    if (retval != ERR)
	uni_digits = 0;

1504
1505
    return retval;
}
1506
#endif /* ENABLE_UTF8 */
1507

1508
1509
1510
1511
1512
1513
/* Translate a control character sequence: turn an ASCII non-control
 * character into its corresponding control character. */
int get_control_kbinput(int kbinput)
{
    int retval;

1514
    /* Ctrl-Space (Ctrl-2, Ctrl-@, Ctrl-`) */
1515
    if (kbinput == ' ' || kbinput == '2')
1516
	retval = 0;
1517
1518
    /* Ctrl-/ (Ctrl-7, Ctrl-_) */
    else if (kbinput == '/')
1519
	retval = 31;
1520
    /* Ctrl-3 (Ctrl-[, Esc) to Ctrl-7 (Ctrl-/, Ctrl-_) */
1521
1522
1523
    else if ('3' <= kbinput && kbinput <= '7')
	retval = kbinput - 24;
    /* Ctrl-8 (Ctrl-?) */
1524
    else if (kbinput == '8' || kbinput == '?')
1525
	retval = DEL_CODE;
1526
1527
    /* Ctrl-@ (Ctrl-Space, Ctrl-2, Ctrl-`) to Ctrl-_ (Ctrl-/, Ctrl-7) */
    else if ('@' <= kbinput && kbinput <= '_')
1528
	retval = kbinput - '@';
1529
1530
    /* Ctrl-` (Ctrl-2, Ctrl-Space, Ctrl-@) to Ctrl-~ (Ctrl-6, Ctrl-^) */
    else if ('`' <= kbinput && kbinput <= '~')
1531
	retval = kbinput - '`';
1532
1533
1534
    else
	retval = kbinput;

1535
#ifdef DEBUG
1536
    fprintf(stderr, "get_control_kbinput(): kbinput = %d, retval = %d\n", kbinput, retval);
1537
1538
#endif

1539
1540
    return retval;
}
1541

1542
/* Read in a stream of characters verbatim, and return the length of the
1543
1544
1545
1546
 * string in kbinput_len.  Assume nodelay(win) is FALSE. */
int *get_verbatim_kbinput(WINDOW *win, size_t *kbinput_len)
{
    int *retval;
1547

1548
    /* Turn off flow control characters if necessary so that we can type
1549
1550
     * them in verbatim, and turn the keypad off if necessary so that we
     * don't get extended keypad values. */
1551
1552
    if (ISSET(PRESERVE))
	disable_flow_control();
1553
1554
    if (!ISSET(REBIND_KEYPAD))
	keypad(win, FALSE);
1555

1556
    /* Read in one keycode, or one or two escapes. */
1557
    retval = parse_verbatim_kbinput(win, kbinput_len);
1558

1559
    /* If the code is invalid in the current mode, discard it. */
1560
1561
    if (retval != NULL && ((*retval == '\n' && as_an_at) ||
				(*retval == '\0' && !as_an_at))) {
1562
1563
1564
1565
	*kbinput_len = 0;
	beep();
    }

1566
    /* Turn flow control characters back on if necessary and turn the
1567
     * keypad back on if necessary now that we're done. */
1568
1569
    if (ISSET(PRESERVE))
	enable_flow_control();
1570
1571
1572
1573
1574
1575
    /* 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);
    }
1576

1577
    return retval;
1578
1579
}

1580
1581
1582
1583
/* Read in one control character (or an iTerm/Eterm/rxvt 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/Eterm/rxvt double Escape). */
1584
int *parse_verbatim_kbinput(WINDOW *win, size_t *count)
1585
{
1586
    int *kbinput;
1587

1588
1589
    reveal_cursor = TRUE;

1590
    /* Read in the first code. */
1591
1592
    while ((kbinput = get_input(win, 1)) == NULL)
	;
1593

1594
#ifndef NANO_TINY
1595
1596
1597
    /* When the window was resized, abort and return nothing. */
    if (*kbinput == KEY_WINCH) {
	free(kbinput);
1598
	*count = 0;
1599
1600
	return NULL;
    }
1601
#endif
1602

1603
1604
    *count = 1;

1605
#ifdef ENABLE_UTF8
1606
    if (using_utf8()) {
1607
	/* Check whether the first code is a valid starter digit: 0 or 1. */
1608
	long unicode = get_unicode_kbinput(win, *kbinput);
1609

1610
	/* If the first code isn't the digit 0 nor 1, put it back. */
1611
	if (unicode != ERR)
1612
	    unget_input(kbinput, 1);
1613
1614
	/* Otherwise, continue reading in digits until we have a complete
	 * Unicode value, and put back the corresponding byte(s). */
1615
	else {
1616
	    char *multibyte;
1617
	    int onebyte, i;
1618

1619
1620
	    reveal_cursor = FALSE;

1621
	    while (unicode == ERR) {
1622
		free(kbinput);
1623
1624
		while ((kbinput = get_input(win, 1)) == NULL)
		    ;
1625
		unicode = get_unicode_kbinput(win, *kbinput);
1626
	    }
1627

1628
	    /* Convert the Unicode value to a multibyte sequence. */
1629
	    multibyte = make_mbchar(unicode, (int *)count);
1630

1631
	    /* Insert the multibyte sequence into the input buffer. */
1632
	    for (i = *count; i > 0 ; i--) {
1633
		onebyte = (unsigned char)multibyte[i - 1];
1634
1635
		unget_input(&onebyte, 1);
	    }
1636

1637
	    free(multibyte);
1638
	}
1639
    } else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1640
#endif /* ENABLE_UTF8 */
1641
	/* Put back the first code. */
1642
	unget_input(kbinput, 1);
1643

1644
1645
    free(kbinput);

1646
    /* If this is an iTerm/Eterm/rxvt double escape, take both Escapes. */
1647
1648
1649
1650
1651
    if (key_buffer_len > 3 && *key_buffer == ESC_CODE &&
		key_buffer[1] == ESC_CODE && key_buffer[2] == '[')
	*count = 2;

    return get_input(NULL, *count);
1652
1653
}

1654
#ifdef ENABLE_MOUSE
1655
/* Handle any mouse event that may have occurred.  We currently handle
1656
1657
1658
1659
1660
1661
1662
 * 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
1663
1664
1665
1666
1667
1668
 * 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. */
1669
int get_mouseinput(int *mouse_x, int *mouse_y, bool allow_shortcuts)
1670
1671
{
    MEVENT mevent;
1672
    bool in_bottomwin;
1673
    subnfunc *f;
1674
1675
1676
1677
1678
1679

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

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

1682
    /* Save the screen coordinates where the mouse event took place. */
1683
    *mouse_x = mevent.x - margin;
1684
    *mouse_y = mevent.y;
1685

1686
1687
    in_bottomwin = wenclose(bottomwin, *mouse_y, *mouse_x);

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1688
    /* Handle releases/clicks of the first mouse button. */
1689
    if (mevent.bstate & (BUTTON1_RELEASED | BUTTON1_CLICKED)) {
1690
1691
	/* If we're allowing shortcuts, the current shortcut list is
	 * being displayed on the last two lines of the screen, and the
1692
1693
1694
	 * 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. */
1695
	if (allow_shortcuts && !ISSET(NO_HELP) && in_bottomwin) {
1696
1697
1698
1699
	    int i;
		/* The width of all the shortcuts, except for the last
		 * two, in the shortcut list in bottomwin. */
	    int j;
1700
		/* The calculated index number of the clicked item. */
1701
1702
	    size_t number;
		/* The number of available shortcuts in the current menu. */
1703

1704
1705
1706
1707
1708
1709
1710
1711
1712
	    /* 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. */
1713
		*mouse_x = mevent.x - margin;
1714
1715
1716
1717
		*mouse_y = mevent.y;

		return 0;
	    }
1718

1719
	    /* Determine how many shortcuts are being shown. */
1720
	    number = length_of_list(currmenu);
1721

1722
1723
	    if (number > MAIN_VISIBLE)
		number = MAIN_VISIBLE;
1724

1725
1726
1727
	    /* 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. */
1728
	    if (number < 2)
1729
1730
		i = COLS / (MAIN_VISIBLE / 2);
	    else
1731
		i = COLS / ((number / 2) + (number % 2));
1732

1733
1734
	    /* Calculate the one-based index in the shortcut list. */
	    j = (*mouse_x / i) * 2 + *mouse_y;
1735

1736
	    /* Adjust the index if we hit the last two wider ones. */
1737
	    if ((j > number) && (*mouse_x % i < COLS % i))
1738
		j -= 2;
1739

1740
1741
	    /* Ignore releases/clicks of the first mouse button beyond
	     * the last shortcut. */
1742
	    if (j > number)
1743
		return 2;
1744

1745
1746
	    /* Go through the list of functions to determine which
	     * shortcut in the current menu we released/clicked on. */
1747
	    for (f = allfuncs; f != NULL; f = f->next) {
1748
		if ((f->menus & currmenu) == 0)
1749
1750
		    continue;
		if (first_sc_for(currmenu, f->scfunc) == NULL)
1751
		    continue;
1752
1753
		/* Tick off an actually shown shortcut. */
		j -= 1;
1754
1755
		if (j == 0)
		    break;
1756
1757
	    }

1758
	    /* And put the corresponding key into the keyboard buffer. */
1759
	    if (f != NULL) {
1760
		const sc *s = first_sc_for(currmenu, f->scfunc);
1761
		unget_kbinput(s->keycode, s->meta);
1762
	    }
1763
	    return 1;
1764
	} else
1765
1766
	    /* Handle releases/clicks of the first mouse button that
	     * aren't on the current shortcut list elsewhere. */
1767
	    return 0;
1768
    }
1769
1770
#if NCURSES_MOUSE_VERSION >= 2
    /* Handle presses of the fourth mouse button (upward rolls of the
1771
1772
1773
     * 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
1774
	bool in_edit = wenclose(edit, *mouse_y, *mouse_x);
1775

1776
1777
1778
1779
	if (in_bottomwin)
	    /* Translate the mouse event coordinates so that they're
	     * relative to bottomwin. */
	    wmouse_trafo(bottomwin, mouse_y, mouse_x, FALSE);
1780

1781
	if (in_edit || (in_bottomwin && *mouse_y == 0)) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1782
1783
	    int i;

1784
	    /* One roll of the mouse wheel should move three lines. */
1785
1786
	    for (i = 0; i < 3; i++)
		unget_kbinput((mevent.bstate & BUTTON4_PRESSED) ?
1787
				KEY_UP : KEY_DOWN, FALSE);
1788
1789
1790
1791
1792
1793
1794

	    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;
1795
1796
    }
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1797
1798
1799

    /* Ignore all other mouse events. */
    return 2;
1800
}
1801
#endif /* ENABLE_MOUSE */
1802

1803
1804
1805
1806
/* 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. */
1807
const sc *get_shortcut(int *kbinput)
1808
{
1809
    sc *s;
1810

1811
#ifdef DEBUG
1812
1813
    fprintf(stderr, "get_shortcut(): kbinput = %d, meta_key = %s -- ",
				*kbinput, meta_key ? "TRUE" : "FALSE");
1814
1815
#endif

1816
    for (s = sclist; s != NULL; s = s->next) {
1817
	if ((s->menus & currmenu) && *kbinput == s->keycode &&
1818
					meta_key == s->meta) {
1819
#ifdef DEBUG
1820
1821
	    fprintf (stderr, "matched seq '%s'  (menu is %x from %x)\n",
				s->keystr, currmenu, s->menus);
1822
#endif
1823
	    return s;
1824
1825
	}
    }
1826
#ifdef DEBUG
1827
    fprintf (stderr, "matched nothing\n");
1828
#endif
1829
1830
1831
1832

    return NULL;
}

1833
1834
/* Move to (x, y) in win, and display a line of n spaces with the
 * current attributes. */
1835
void blank_row(WINDOW *win, int y, int x, int n)
1836
1837
{
    wmove(win, y, x);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1838

1839
1840
1841
1842
    for (; n > 0; n--)
	waddch(win, ' ');
}

1843
/* Blank the first line of the top portion of the window. */
1844
void blank_titlebar(void)
Chris Allegretta's avatar
Chris Allegretta committed
1845
{
1846
    blank_row(topwin, 0, 0, COLS);
1847
1848
}

1849
/* Blank all the lines of the middle portion of the window, i.e. the
1850
 * edit window. */
Chris Allegretta's avatar
Chris Allegretta committed
1851
1852
void blank_edit(void)
{
1853
    int row;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1854

1855
1856
    for (row = 0; row < editwinrows; row++)
	blank_row(edit, row, 0, COLS);
Chris Allegretta's avatar
Chris Allegretta committed
1857
1858
}

1859
/* Blank the first line of the bottom portion of the window. */
Chris Allegretta's avatar
Chris Allegretta committed
1860
1861
void blank_statusbar(void)
{
1862
    blank_row(bottomwin, 0, 0, COLS);
Chris Allegretta's avatar
Chris Allegretta committed
1863
1864
}

1865
1866
/* If the NO_HELP flag isn't set, blank the last two lines of the bottom
 * portion of the window. */
1867
1868
void blank_bottombars(void)
{
1869
    if (!ISSET(NO_HELP) && LINES > 4) {
1870
1871
	blank_row(bottomwin, 1, 0, COLS);
	blank_row(bottomwin, 2, 0, COLS);
1872
1873
1874
    }
}

1875
1876
/* Check if the number of keystrokes needed to blank the statusbar has
 * been pressed.  If so, blank the statusbar, unless constant cursor
1877
 * position display is on and we are in the editing screen. */
1878
void check_statusblank(void)
Chris Allegretta's avatar
Chris Allegretta committed
1879
{
1880
1881
1882
1883
1884
1885
    if (statusblank == 0)
	return;

    statusblank--;

    /* When editing and 'constantshow' is active, skip the blanking. */
1886
    if (currmenu == MMAIN && ISSET(CONSTANT_SHOW))
1887
1888
1889
1890
1891
	return;

    if (statusblank == 0) {
	blank_statusbar();
	wnoutrefresh(bottomwin);
Chris Allegretta's avatar
Chris Allegretta committed
1892
    }
1893
1894
1895
1896

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

1899
1900
1901
1902
1903
1904
1905
/* Convert buf into a string that can be displayed on screen.  The caller
 * wants to display buf starting with the given column, and extending for
 * at most span columns.  column is zero-based, and span is one-based, so
 * span == 0 means you get "" returned.  The returned string is dynamically
 * allocated, and should be freed.  If isdata is TRUE, the caller might put
 * "$" at the beginning or end of the line if it's too long. */
char *display_string(const char *buf, size_t column, size_t span, bool isdata)
1906
{
1907
1908
1909
1910
    size_t start_index = actual_x(buf, column);
	/* The index of the first character that the caller wishes to show. */
    size_t start_col = strnlenpt(buf, start_index);
	/* The actual column where that first character starts. */
1911
    char *converted;
1912
	/* The expanded string we will return. */
1913
    size_t index = 0;
1914
	/* Current position in converted. */
1915
    size_t beyond = column + span;
1916
	/* The column number just beyond the last shown character. */
1917

1918
#ifdef USING_OLD_NCURSES
1919
    seen_wide = FALSE;
1920
#endif
1921
    buf += start_index;
1922

1923
    /* Allocate enough space for converting the relevant part of the line. */
1924
    converted = charalloc(strlen(buf) * (MAXCHARLEN + tabsize) + 1);
1925

1926
    /* If the first character starts before the left edge, or would be
1927
     * overwritten by a "$" token, then show placeholders instead. */
1928
1929
    if (*buf != '\0' && *buf != '\t' && (start_col < column ||
			(start_col > 0 && isdata && !ISSET(SOFTWRAP)))) {
1930
	if (is_cntrl_mbchar(buf)) {
1931
	    if (start_col < column) {
1932
		converted[index++] = control_mbrep(buf, isdata);
1933
		column++;
1934
		buf += parse_mbchar(buf, NULL, NULL);
1935
	    }
1936
	}
1937
#ifdef ENABLE_UTF8
1938
	else if (mbwidth(buf) == 2) {
1939
	    if (start_col == column) {
1940
		converted[index++] = ' ';
1941
		column++;
1942
1943
	    }

1944
1945
	    /* Display the right half of a two-column character as '<'. */
	    converted[index++] = '<';
1946
	    column++;
1947
	    buf += parse_mbchar(buf, NULL, NULL);
1948
	}
1949
#endif
1950
1951
    }

1952
    while (*buf != '\0' && column < beyond) {
1953
	int charlength, charwidth = 1;
1954

1955
	if (*buf == ' ') {
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
	    /* 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++] = ' ';
1966
	    column++;
1967
1968
	    buf++;
	    continue;
1969
	} else if (*buf == '\t') {
1970
	    /* Show a tab as a visible character, or as as a space. */
1971
#ifndef NANO_TINY
1972
	    if (ISSET(WHITESPACE_DISPLAY) && (index > 0 || !isdata ||
1973
1974
			!ISSET(SOFTWRAP) || column % tabsize == 0 ||
			column == start_col)) {
1975
		int i = 0;
1976

1977
1978
		while (i < whitespace_len[0])
		    converted[index++] = whitespace[i++];
1979
	    } else
1980
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1981
		converted[index++] = ' ';
1982
	    column++;
1983
	    /* Fill the tab up with the required number of spaces. */
1984
	    while (column % tabsize != 0 && column < beyond) {
1985
		converted[index++] = ' ';
1986
		column++;
1987
	    }
1988
1989
1990
1991
	    buf++;
	    continue;
	}

1992
	charlength = length_of_char(buf, &charwidth);
1993

1994
	/* If buf contains a control character, represent it. */
1995
	if (is_cntrl_mbchar(buf)) {
1996
	    converted[index++] = '^';
1997
	    converted[index++] = control_mbrep(buf, isdata);
1998
	    column += 2;
1999
2000
2001
	    buf += charlength;
	    continue;
	}
2002

2003
2004
2005
2006
	/* If buf contains a valid non-control character, simply copy it. */
	if (charlength > 0) {
	    for (; charlength > 0; charlength--)
		converted[index++] = *(buf++);
2007

2008
	    column += charwidth;
2009
#ifdef USING_OLD_NCURSES
2010
	    if (charwidth > 1)
2011
		seen_wide = TRUE;
2012
#endif
2013
	    continue;
2014
2015
	}

2016
2017
2018
2019
	/* Represent an invalid sequence with the Replacement Character. */
	converted[index++] = '\xEF';
	converted[index++] = '\xBF';
	converted[index++] = '\xBD';
2020
	column++;
2021
2022
2023
2024
2025
	buf++;

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

2028
    /* If there is more text than can be shown, make room for the $ or >. */
2029
    if (*buf != '\0' && (column > beyond || (isdata && !ISSET(SOFTWRAP)))) {
2030
	index = move_mbleft(converted, index);
2031

2032
2033
#ifdef ENABLE_UTF8
	/* Display the left half of a two-column character as '>'. */
2034
	if (mbwidth(converted + index) == 2)
2035
2036
2037
2038
	    converted[index++] = '>';
#endif
    }

2039
2040
    /* Null-terminate the converted string. */
    converted[index] = '\0';
2041

2042
    return converted;
2043
2044
}

2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
/* Determine the sequence number of the given buffer in the circular list. */
int buffer_number(openfilestruct *buffer)
{
    int count = 1;

    while (buffer != firstfile) {
	buffer = buffer->prev;
	count++;
    }

    return count;
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2058
2059
/* 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
2060
2061
2062
 * has been modified on the titlebar.  If path isn't NULL, we're either
 * in the file browser or the help viewer, so show either the current
 * directory or the title of help text, that is: whatever is in path. */
2063
void titlebar(const char *path)
Chris Allegretta's avatar
Chris Allegretta committed
2064
{
2065
2066
2067
2068
2069
2070
    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. */
2071
    const char *upperleft = "";
2072
	/* What is shown in the top left corner. */
2073
    const char *prefix = "";
2074
	/* What is shown before the path -- "DIR:" or nothing. */
2075
2076
    const char *state = "";
	/* The state of the current buffer -- "Modified", "View", or "". */
2077
2078
    char *caption;
	/* The presentable form of the pathname. */
2079
2080
    char *indicator = NULL;
	/* The buffer sequence number plus buffer count. */
2081

2082
2083
2084
2085
    /* If the screen is too small, there is no titlebar. */
    if (topwin == NULL)
	return;

2086
    wattron(topwin, interface_color_pair[TITLE_BAR]);
2087

2088
    blank_titlebar();
2089
    as_an_at = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
2090

2091
2092
2093
    /* 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
2094

2095
    /* Figure out the path, prefix and state strings. */
2096
#ifdef ENABLE_BROWSER
2097
    if (!inhelp && path != NULL)
2098
	prefix = _("DIR:");
2099
    else
2100
#endif
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
    if (!inhelp) {
	/* If there are/were multiple buffers, show which out of how many. */
	if (more_than_one) {
	    indicator = charalloc(24);
	    sprintf(indicator, "[%i/%i]", buffer_number(openfile),
					buffer_number(firstfile->prev));
	    upperleft = indicator;
	} else
	    upperleft = BRANDING;

2111
2112
	if (openfile->filename[0] == '\0')
	    path = _("New Buffer");
2113
	else
2114
	    path = openfile->filename;
2115

2116
2117
2118
2119
	if (openfile->modified)
	    state = _("Modified");
	else if (ISSET(VIEW_MODE))
	    state = _("View");
2120

2121
2122
	pluglen = strlenpt(_("Modified")) + 1;
    }
2123

2124
    /* Determine the widths of the four elements, including their padding. */
2125
    verlen = strlenpt(upperleft) + 3;
2126
2127
2128
    prefixlen = strlenpt(prefix);
    if (prefixlen > 0)
	prefixlen++;
2129
    pathlen = strlenpt(path);
2130
2131
2132
2133
    statelen = strlenpt(state) + 2;
    if (statelen > 2) {
	pathlen++;
	pluglen = 0;
2134
2135
    }

2136
2137
    /* Only print the version message when there is room for it. */
    if (verlen + prefixlen + pathlen + pluglen + statelen <= COLS)
2138
	mvwaddstr(topwin, 0, 2, upperleft);
2139
2140
2141
2142
2143
2144
2145
2146
2147
    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;
2148
2149
2150
	}
    }

2151
2152
    free(indicator);

2153
2154
2155
2156
    /* If we have side spaces left, center the path name. */
    if (verlen > 0)
	offset = verlen + (COLS - (verlen + pluglen + statelen) -
					(prefixlen + pathlen)) / 2;
2157

2158
2159
2160
2161
2162
2163
2164
2165
2166
    /* 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. */
2167
2168
2169
2170
2171
    if (pathlen + pluglen + statelen <= COLS) {
	caption = display_string(path, 0, pathlen, FALSE);
	waddstr(topwin, caption);
	free(caption);
    } else if (5 + statelen <= COLS) {
2172
	waddstr(topwin, "...");
2173
	caption = display_string(path, 3 + pathlen - COLS + statelen,
2174
					COLS - statelen, FALSE);
2175
2176
	waddstr(topwin, caption);
	free(caption);
2177
    }
2178

2179
2180
2181
2182
2183
2184
    /* 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));

2185
    wattroff(topwin, interface_color_pair[TITLE_BAR]);
2186

2187
    wrefresh(topwin);
Chris Allegretta's avatar
Chris Allegretta committed
2188
2189
}

2190
2191
2192
2193
2194
2195
/* Display a normal message on the statusbar, quietly. */
void statusbar(const char *msg)
{
    statusline(HUSH, msg);
}

2196
2197
2198
2199
2200
2201
2202
2203
2204
/* Warn the user on the statusbar and pause for a moment, so that the
 * message can be noticed and read. */
void warn_and_shortly_pause(const char *msg)
{
    statusbar(msg);
    beep();
    napms(1800);
}

2205
/* Display a message on the statusbar, and set suppress_cursorpos to
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2206
2207
 * TRUE, so that the message won't be immediately overwritten if
 * constant cursor position display is on. */
2208
void statusline(message_type importance, const char *msg, ...)
2209
2210
{
    va_list ap;
2211
    static int alerts = 0;
2212
    char *compound, *message;
2213
    size_t start_col;
2214
    bool bracketed;
2215
2216
2217
2218
#ifndef NANO_TINY
    bool old_whitespace = ISSET(WHITESPACE_DISPLAY);

    UNSET(WHITESPACE_DISPLAY);
2219
#endif
2220
2221
2222
2223
2224

    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(). */
2225
    if (isendwin()) {
2226
2227
2228
2229
2230
	vfprintf(stderr, msg, ap);
	va_end(ap);
	return;
    }

2231
2232
2233
2234
2235
    /* If there already was an alert message, ignore lesser ones. */
    if ((lastmessage == ALERT && importance != ALERT) ||
		(lastmessage == MILD && importance == HUSH))
	return;

2236
2237
2238
2239
2240
2241
    /* If the ALERT status has been reset, reset the counter. */
    if (lastmessage == HUSH)
	alerts = 0;

    /* Shortly pause after each of the first three alert messages,
     * to give the user time to read them. */
2242
    if (lastmessage == ALERT && alerts < 4 && !ISSET(NO_PAUSES))
2243
2244
	napms(1200);

2245
    if (importance == ALERT) {
2246
	if (++alerts > 3 && !ISSET(NO_PAUSES))
2247
	    msg = _("Further warnings were suppressed");
2248
2249
	else if (alerts < 4)
	    beep();
2250
    }
2251
2252

    lastmessage = importance;
2253

2254
2255
    blank_statusbar();

2256
    /* Construct the message out of all the arguments. */
2257
2258
    compound = charalloc(MAXCHARLEN * (COLS + 1));
    vsnprintf(compound, MAXCHARLEN * (COLS + 1), msg, ap);
2259
    va_end(ap);
2260
2261
    message = display_string(compound, 0, COLS, FALSE);
    free(compound);
2262

2263
2264
    start_col = (COLS - strlenpt(message)) / 2;
    bracketed = (start_col > 1);
2265

2266
    wmove(bottomwin, 0, (bracketed ? start_col - 2 : start_col));
2267
    wattron(bottomwin, interface_color_pair[STATUS_BAR]);
2268
2269
    if (bracketed)
	waddstr(bottomwin, "[ ");
2270
2271
    waddstr(bottomwin, message);
    free(message);
2272
2273
    if (bracketed)
	waddstr(bottomwin, " ]");
2274
    wattroff(bottomwin, interface_color_pair[STATUS_BAR]);
2275

2276
2277
2278
2279
    /* Defeat a VTE/Konsole bug, where the cursor can go off-limits. */
    if (ISSET(CONSTANT_SHOW) && ISSET(NO_HELP))
	wmove(bottomwin, 0, 0);

2280
    /* Push the message to the screen straightaway. */
2281
    wrefresh(bottomwin);
2282

2283
    suppress_cursorpos = TRUE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2284

2285
#ifndef NANO_TINY
2286
2287
    if (old_whitespace)
	SET(WHITESPACE_DISPLAY);
2288
#endif
2289
2290
2291

    /* If doing quick blanking, blank the statusbar after just one keystroke.
     * Otherwise, blank it after twenty-six keystrokes, as Pico does. */
2292
2293
2294
2295
    if (ISSET(QUICK_BLANK))
	statusblank = 1;
    else
	statusblank = 26;
2296
2297
}

2298
2299
/* Display the shortcut list corresponding to menu on the last two rows
 * of the bottom portion of the window. */
2300
void bottombars(int menu)
Chris Allegretta's avatar
Chris Allegretta committed
2301
{
2302
    size_t number, itemwidth, i;
2303
2304
    subnfunc *f;
    const sc *s;
2305

2306
2307
2308
    /* Set the global variable to the given menu. */
    currmenu = menu;

2309
    if (ISSET(NO_HELP) || LINES < 5)
Chris Allegretta's avatar
Chris Allegretta committed
2310
2311
	return;

2312
    /* Determine how many shortcuts there are to show. */
2313
    number = length_of_list(menu);
2314

2315
2316
    if (number > MAIN_VISIBLE)
	number = MAIN_VISIBLE;
2317

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

2321
    /* If there is no room, don't print anything. */
2322
    if (itemwidth == 0)
2323
2324
	return;

2325
    blank_bottombars();
2326

2327
2328
    /* Display the first number of shortcuts in the given menu that
     * have a key combination assigned to them. */
2329
    for (f = allfuncs, i = 0; i < number && f != NULL; f = f->next) {
2330
	if ((f->menus & menu) == 0)
2331
	    continue;
2332

2333
	s = first_sc_for(menu, f->scfunc);
2334
	if (s == NULL)
2335
	    continue;
2336
2337

	wmove(bottomwin, 1 + i % 2, (i / 2) * itemwidth);
2338

2339
	onekey(s->keystr, _(f->desc), itemwidth + (COLS % itemwidth));
2340
	i++;
Chris Allegretta's avatar
Chris Allegretta committed
2341
    }
2342

2343
    /* Defeat a VTE bug by homing the cursor and forcing a screen update. */
2344
    wmove(bottomwin, 0, 0);
2345
    wrefresh(bottomwin);
Chris Allegretta's avatar
Chris Allegretta committed
2346
2347
}

2348
2349
/* 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
2350
 * to write at most length characters, even if length is very small and
2351
2352
 * keystroke and desc are long.  Note that waddnstr(,,(size_t)-1) adds
 * the whole string!  We do not bother padding the entry with blanks. */
2353
void onekey(const char *keystroke, const char *desc, int length)
Chris Allegretta's avatar
Chris Allegretta committed
2354
{
2355
    wattron(bottomwin, interface_color_pair[KEY_COMBO]);
2356
    waddnstr(bottomwin, keystroke, actual_x(keystroke, length));
2357
    wattroff(bottomwin, interface_color_pair[KEY_COMBO]);
2358

2359
    length -= strlenpt(keystroke) + 1;
2360

2361
    if (length > 0) {
2362
	waddch(bottomwin, ' ');
2363
	wattron(bottomwin, interface_color_pair[FUNCTION_TAG]);
2364
	waddnstr(bottomwin, desc, actual_x(desc, length));
2365
	wattroff(bottomwin, interface_color_pair[FUNCTION_TAG]);
Chris Allegretta's avatar
Chris Allegretta committed
2366
2367
2368
    }
}

2369
/* Redetermine current_y from the position of current relative to edittop,
2370
 * and put the cursor in the edit window at (current_y, "current_x"). */
2371
void place_the_cursor(void)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2372
{
2373
    ssize_t row = 0;
2374
    size_t col, xpt = xplustabs();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2375

2376
#ifndef NANO_TINY
2377
    if (ISSET(SOFTWRAP)) {
2378
	filestruct *line = openfile->edittop;
2379
	size_t leftedge;
2380

2381
	row -= chunk_for(openfile->firstcolumn, openfile->edittop);
2382

2383
	/* Calculate how many rows the lines from edittop to current use. */
2384
	while (line != NULL && line != openfile->current) {
2385
	    row += number_of_chunks_in(line) + 1;
2386
2387
2388
	    line = line->next;
	}

2389
	/* Add the number of wraps in the current line before the cursor. */
2390
	row += get_chunk_and_edge(xpt, openfile->current, &leftedge);
2391
	col = xpt - leftedge;
2392
2393
2394
    } else
#endif
    {
2395
2396
	row = openfile->current->lineno - openfile->edittop->lineno;
	col = xpt - get_page_start(xpt);
2397
    }
2398
2399
2400
2401

    if (row < editwinrows)
	wmove(edit, row, margin + col);

2402
    openfile->current_y = row;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2403
}
Chris Allegretta's avatar
Chris Allegretta committed
2404

2405
/* edit_draw() takes care of the job of actually painting a line into
2406
 * the edit window.  fileptr is the line to be painted, at row row of
2407
2408
 * the window.  converted is the actual string to be written to the
 * window, with tabs and control characters replaced by strings of
2409
 * regular characters.  from_col is the column number of the first
2410
 * character of this page.  That is, the first character of converted
2411
 * corresponds to character number actual_x(fileptr->data, from_col) of the
2412
 * line. */
2413
void edit_draw(filestruct *fileptr, const char *converted,
2414
	int row, size_t from_col)
Chris Allegretta's avatar
Chris Allegretta committed
2415
{
2416
#if !defined(NANO_TINY) || !defined(DISABLE_COLOR)
2417
    size_t from_x = actual_x(fileptr->data, from_col);
2418
2419
	/* The position in fileptr->data of the leftmost character
	 * that displays at least partially on the window. */
2420
    size_t till_x = actual_x(fileptr->data, from_col + editwincols - 1) + 1;
2421
	/* The position in fileptr->data of the first character that is
2422
2423
	 * completely off the window to the right.  Note that till_x
	 * might be beyond the null terminator of the string. */
2424
2425
#endif

2426
#ifdef ENABLE_LINENUMBERS
2427
    /* If line numbering is switched on, put a line number in front of
2428
2429
     * the text -- but only for the parts that are not softwrapped. */
    if (margin > 0) {
2430
	wattron(edit, interface_color_pair[LINE_NUMBER]);
2431
#ifndef NANO_TINY
2432
	if (ISSET(SOFTWRAP) && from_col != 0)
2433
	    mvwprintw(edit, row, 0, "%*s", margin - 1, " ");
2434
2435
	else
#endif
2436
	    mvwprintw(edit, row, 0, "%*ld", margin - 1, (long)fileptr->lineno);
2437
	wattroff(edit, interface_color_pair[LINE_NUMBER]);
2438
2439
    }
#endif
2440

2441
2442
2443
    /* First simply write the converted line -- afterward we'll add colors
     * and the marking highlight on just the pieces that need it. */
    mvwaddstr(edit, row, margin, converted);
2444

2445
#ifdef USING_OLD_NCURSES
2446
2447
2448
    /* 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. */
2449
    if (seen_wide)
2450
	wredrawln(edit, row, 1);
2451
#endif
2452

2453
#ifndef DISABLE_COLOR
2454
    /* If color syntaxes are available and turned on, apply them. */
2455
    if (openfile->colorstrings != NULL && !ISSET(NO_COLOR_SYNTAX)) {
2456
	const colortype *varnish = openfile->colorstrings;
2457

2458
2459
2460
2461
	/* If there are multiline regexes, make sure there is a cache. */
	if (openfile->syntax->nmultis > 0)
	    alloc_multidata_if_needed(fileptr);

2462
	/* Iterate through all the coloring regexes. */
2463
	for (; varnish != NULL; varnish = varnish->next) {
2464
2465
	    size_t index = 0;
		/* Where in the line we currently begin looking for a match. */
2466
	    int start_col;
2467
		/* The starting column of a piece to paint.  Zero-based. */
2468
	    int paintlen = 0;
2469
2470
2471
		/* The number of characters to paint. */
	    const char *thetext;
		/* The place in converted from where painting starts. */
2472
2473
	    regmatch_t match;
		/* The match positions of a single-line regex. */
2474
2475
2476
2477
2478
2479
	    const filestruct *start_line = fileptr->prev;
		/* The first line before fileptr that matches 'start'. */
	    const filestruct *end_line = fileptr;
		/* The line that matches 'end'. */
	    regmatch_t startmatch, endmatch;
		/* The match positions of the start and end regexes. */
2480

2481
2482
2483
	    /* 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. */
2484

2485
2486
	    wattron(edit, varnish->attributes);

2487
2488
	    /* First case: varnish is a single-line expression. */
	    if (varnish->end == NULL) {
2489
		/* We increment index by rm_eo, to move past the end of the
2490
		 * last match.  Even though two matches may overlap, we
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2491
2492
		 * want to ignore them, so that we can highlight e.g. C
		 * strings correctly. */
2493
		while (index < till_x) {
2494
2495
		    /* Note the fifth parameter to regexec().  It says
		     * not to match the beginning-of-line character
2496
		     * unless index is zero.  If regexec() returns
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2497
2498
		     * REG_NOMATCH, there are no more matches in the
		     * line. */
2499
		    if (regexec(varnish->start, &fileptr->data[index], 1,
2500
				&match, (index == 0) ? 0 : REG_NOTBOL) != 0)
2501
			break;
2502

2503
		    /* If the match is of length zero, skip it. */
2504
		    if (match.rm_so == match.rm_eo) {
2505
			index = move_mbright(fileptr->data,
2506
						index + match.rm_eo);
2507
2508
2509
			continue;
		    }

2510
		    /* Translate the match to the beginning of the line. */
2511
2512
2513
		    match.rm_so += index;
		    match.rm_eo += index;
		    index = match.rm_eo;
2514

2515
2516
		    /* If the matching part is not visible, skip it. */
		    if (match.rm_eo <= from_x || match.rm_so >= till_x)
2517
2518
			continue;

2519
2520
2521
		    start_col = (match.rm_so <= from_x) ?
					0 : strnlenpt(fileptr->data,
					match.rm_so) - from_col;
2522

2523
		    thetext = converted + actual_x(converted, start_col);
2524

2525
		    paintlen = actual_x(thetext, strnlenpt(fileptr->data,
2526
					match.rm_eo) - from_col - start_col);
2527

2528
		    mvwaddnstr(edit, row, margin + start_col,
2529
						thetext, paintlen);
Chris Allegretta's avatar
Chris Allegretta committed
2530
		}
2531
2532
2533
2534
		goto tail_of_loop;
	    }

	    /* Second case: varnish is a multiline expression. */
2535

2536
2537
2538
	    /* Assume nothing gets painted until proven otherwise below. */
	    fileptr->multidata[varnish->id] = CNONE;

2539
2540
2541
2542
	    /* First check the multidata of the preceding line -- it tells
	     * us about the situation so far, and thus what to do here. */
	    if (start_line != NULL && start_line->multidata != NULL) {
		if (start_line->multidata[varnish->id] == CWHOLELINE ||
2543
2544
			start_line->multidata[varnish->id] == CENDAFTER ||
			start_line->multidata[varnish->id] == CWOULDBE)
2545
2546
2547
		    goto seek_an_end;
		if (start_line->multidata[varnish->id] == CNONE ||
			start_line->multidata[varnish->id] == CBEGINBEFORE ||
2548
			start_line->multidata[varnish->id] == CSTARTENDHERE)
2549
2550
2551
		    goto step_two;
	    }

2552
2553
	    /* The preceding line has no precalculated multidata.  So, do
	     * some backtracking to find out what to paint. */
2554

2555
2556
	    /* First step: see if there is a line before current that
	     * matches 'start' and is not complemented by an 'end'. */
2557
2558
	    while (start_line != NULL && regexec(varnish->start,
		    start_line->data, 1, &startmatch, 0) == REG_NOMATCH) {
2559
		/* There is no start on this line; but if there is an end,
2560
2561
		 * there is no need to look for starts on earlier lines. */
		if (regexec(varnish->end, start_line->data, 0, NULL, 0) == 0)
2562
		    goto step_two;
2563
2564
		start_line = start_line->prev;
	    }
2565

2566
2567
2568
2569
2570
2571
2572
	    /* If no start was found, skip to the next step. */
	    if (start_line == NULL)
		goto step_two;

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

2577
	    /* Is there an uncomplemented start on the found line? */
2578
	    while (TRUE) {
2579
		/* Begin searching for an end after the start match. */
2580
		index += startmatch.rm_eo;
2581
		/* If there is no end after this last start, good. */
2582
2583
		if (regexec(varnish->end, start_line->data + index, 1, &endmatch,
				(index == 0) ? 0 : REG_NOTBOL) == REG_NOMATCH)
2584
		    break;
2585
2586
		/* Begin searching for a new start after the end match. */
		index += endmatch.rm_eo;
2587
2588
2589
		/* If both start and end match are mere anchors, advance. */
		if (startmatch.rm_so == startmatch.rm_eo &&
				endmatch.rm_so == endmatch.rm_eo) {
2590
		    if (start_line->data[index] == '\0')
2591
			break;
2592
		    index = move_mbright(start_line->data, index);
2593
		}
2594
		/* If there is no later start on this line, next step. */
2595
		if (regexec(varnish->start, start_line->data + index,
2596
				1, &startmatch, REG_NOTBOL) == REG_NOMATCH)
2597
		    goto step_two;
2598
2599
2600
	    }
	    /* Indeed, there is a start without an end on that line. */

2601
  seek_an_end:
2602
2603
	    /* We've already checked that there is no end between the start
	     * and the current line.  But is there an end after the start
2604
2605
2606
2607
2608
	     * at all?  We don't paint unterminated starts. */
	    while (end_line != NULL && regexec(varnish->end, end_line->data,
				 1, &endmatch, 0) == REG_NOMATCH)
		end_line = end_line->next;

2609
	    /* If there is no end, there is nothing to paint. */
2610
2611
	    if (end_line == NULL) {
		fileptr->multidata[varnish->id] = CWOULDBE;
2612
		goto tail_of_loop;
2613
	    }
2614

2615
	    /* If the end is on a later line, paint whole line, and be done. */
2616
	    if (end_line != fileptr) {
2617
		mvwaddnstr(edit, row, margin, converted, -1);
2618
2619
		fileptr->multidata[varnish->id] = CWHOLELINE;
		goto tail_of_loop;
2620
2621
2622
2623
	    }

	    /* Only if it is visible, paint the part to be coloured. */
	    if (endmatch.rm_eo > from_x) {
2624
		paintlen = actual_x(converted, strnlenpt(fileptr->data,
2625
						endmatch.rm_eo) - from_col);
2626
		mvwaddnstr(edit, row, margin, converted, paintlen);
2627
2628
	    }
	    fileptr->multidata[varnish->id] = CBEGINBEFORE;
2629

2630
  step_two:
2631
2632
2633
	    /* Second step: look for starts on this line, but begin
	     * looking only after an end match, if there is one. */
	    index = (paintlen == 0) ? 0 : endmatch.rm_eo;
2634

2635
	    while (regexec(varnish->start, fileptr->data + index,
2636
				1, &startmatch, (index == 0) ?
2637
				0 : REG_NOTBOL) == 0) {
2638
2639
2640
2641
		/* Translate the match to be relative to the
		 * beginning of the line. */
		startmatch.rm_so += index;
		startmatch.rm_eo += index;
2642

2643
		start_col = (startmatch.rm_so <= from_x) ?
2644
				0 : strnlenpt(fileptr->data,
2645
				startmatch.rm_so) - from_col;
2646

2647
		thetext = converted + actual_x(converted, start_col);
2648

2649
2650
		if (regexec(varnish->end, fileptr->data + startmatch.rm_eo,
				1, &endmatch, (startmatch.rm_eo == 0) ?
2651
				0 : REG_NOTBOL) == 0) {
2652
2653
2654
2655
		    /* 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;
2656
2657
		    /* Only paint the match if it is visible on screen and
		     * it is more than zero characters long. */
2658
		    if (endmatch.rm_eo > from_x &&
2659
					endmatch.rm_eo > startmatch.rm_so) {
2660
			paintlen = actual_x(thetext, strnlenpt(fileptr->data,
2661
					endmatch.rm_eo) - from_col - start_col);
2662

2663
			mvwaddnstr(edit, row, margin + start_col,
2664
						thetext, paintlen);
2665

2666
			fileptr->multidata[varnish->id] = CSTARTENDHERE;
2667
		    }
2668
		    index = endmatch.rm_eo;
2669
2670
2671
		    /* If both start and end match are anchors, advance. */
		    if (startmatch.rm_so == startmatch.rm_eo &&
				endmatch.rm_so == endmatch.rm_eo) {
2672
			if (fileptr->data[index] == '\0')
2673
			    break;
2674
			index = move_mbright(fileptr->data, index);
2675
2676
		    }
		    continue;
2677
		}
2678

2679
2680
		/* There is no end on this line.  But maybe on later lines? */
		end_line = fileptr->next;
2681

2682
2683
2684
		while (end_line != NULL && regexec(varnish->end, end_line->data,
					0, NULL, 0) == REG_NOMATCH)
		    end_line = end_line->next;
2685

2686
		/* If there is no end, we're done with this regex. */
2687
2688
		if (end_line == NULL) {
		    fileptr->multidata[varnish->id] = CWOULDBE;
2689
		    break;
2690
		}
2691

2692
		/* Paint the rest of the line, and we're done. */
2693
		mvwaddnstr(edit, row, margin + start_col, thetext, -1);
2694
2695
		fileptr->multidata[varnish->id] = CENDAFTER;
		break;
2696
	    }
2697
  tail_of_loop:
2698
	    wattroff(edit, varnish->attributes);
2699
	}
2700
    }
2701
#endif /* !DISABLE_COLOR */
2702

2703
#ifndef NANO_TINY
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
    /* 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. */
2715
	int start_col;
2716
2717
2718
2719
2720
	    /* The column where painting starts.  Zero-based. */
	const char *thetext;
	    /* The place in converted from where painting starts. */
	int paintlen = -1;
	    /* The number of characters to paint.  Negative means "all". */
2721

2722
	mark_order(&top, &top_x, &bot, &bot_x, NULL);
2723

2724
2725
2726
2727
	if (top->lineno < fileptr->lineno || top_x < from_x)
	    top_x = from_x;
	if (bot->lineno > fileptr->lineno || bot_x > till_x)
	    bot_x = till_x;
Chris Allegretta's avatar
Chris Allegretta committed
2728

2729
	/* Only paint if the marked part of the line is on this page. */
2730
	if (top_x < till_x && bot_x > from_x) {
2731
2732
	    /* Compute on which screen column to start painting. */
	    start_col = strnlenpt(fileptr->data, top_x) - from_col;
2733

2734
2735
2736
	    if (start_col < 0)
		start_col = 0;

2737
	    thetext = converted + actual_x(converted, start_col);
2738

2739
2740
2741
2742
2743
2744
	    /* If the end of the mark is onscreen, compute how many
	     * characters to paint.  Otherwise, just paint all. */
	    if (bot_x < till_x) {
		size_t end_col = strnlenpt(fileptr->data, bot_x) - from_col;
		paintlen = actual_x(thetext, end_col - start_col);
	    }
2745

2746
	    wattron(edit, interface_color_pair[SELECTED_TEXT]);
2747
	    mvwaddnstr(edit, row, margin + start_col, thetext, paintlen);
2748
	    wattroff(edit, interface_color_pair[SELECTED_TEXT]);
Chris Allegretta's avatar
Chris Allegretta committed
2749
	}
2750
    }
2751
#endif /* !NANO_TINY */
Chris Allegretta's avatar
Chris Allegretta committed
2752
2753
}

2754
2755
2756
/* Redraw the line at fileptr.  The line will be displayed so that the
 * character with the given index is visible -- if necessary, the line
 * will be horizontally scrolled.  In softwrap mode, however, the entire
2757
2758
2759
 * line will be passed to update_softwrapped_line().  Likely values of
 * index are current_x or zero.  Return the number of additional rows
 * consumed (when softwrapping). */
2760
int update_line(filestruct *fileptr, size_t index)
Chris Allegretta's avatar
Chris Allegretta committed
2761
{
2762
    int row = 0;
2763
	/* The row in the edit window we will be updating. */
2764
    char *converted;
2765
	/* The data of the line with tabs and control characters expanded. */
2766
2767
    size_t from_col = 0;
	/* From which column a horizontally scrolled line is displayed. */
Chris Allegretta's avatar
Chris Allegretta committed
2768

2769
#ifndef NANO_TINY
2770
2771
    if (ISSET(SOFTWRAP))
	return update_softwrapped_line(fileptr);
2772
#endif
2773
2774

    row = fileptr->lineno - openfile->edittop->lineno;
2775

2776
    /* If the line is offscreen, don't even try to display it. */
2777
    if (row < 0 || row >= editwinrows) {
2778
#ifndef NANO_TINY
2779
	statusline(ALERT, "Badness: tried to display a line on row %i"
2780
				" -- please report a bug", row);
2781
#endif
2782
	return 0;
2783
    }
2784

2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
    /* First, blank out the row. */
    blank_row(edit, row, 0, COLS);

    /* Next, find out from which column to start displaying the line. */
    from_col = get_page_start(strnlenpt(fileptr->data, index));

    /* Expand the line, replacing tabs with spaces, and control
     * characters with their displayed forms. */
    converted = display_string(fileptr->data, from_col, editwincols, TRUE);

    /* Draw the line. */
    edit_draw(fileptr, converted, row, from_col);
    free(converted);

    if (from_col > 0)
	mvwaddch(edit, row, margin, '$');
    if (strlenpt(fileptr->data) > from_col + editwincols)
	mvwaddch(edit, row, COLS - 1, '$');

2804
    return 1;
Chris Allegretta's avatar
Chris Allegretta committed
2805
2806
}

2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
#ifndef NANO_TINY
/* Redraw all the chunks of the given line (as far as they fit onscreen),
 * unless it's edittop, which will be displayed from column firstcolumn.
 * Return the number of additional rows consumed. */
int update_softwrapped_line(filestruct *fileptr)
{
    int row = 0;
	/* The row in the edit window we will write to. */
    filestruct *line = openfile->edittop;
	/* An iterator needed to find the relevant row. */
    int starting_row;
	/* The first row in the edit window that gets updated. */
    size_t from_col = 0;
	/* The starting column of the current chunk. */
2821
2822
    size_t to_col = 0;
	/* To which column a line is displayed. */
2823
2824
2825
2826
2827
2828
    char *converted;
	/* The data of the chunk with tabs and control characters expanded. */

    if (fileptr == openfile->edittop)
	from_col = openfile->firstcolumn;
    else
2829
	row -= chunk_for(openfile->firstcolumn, openfile->edittop);
2830
2831
2832

    /* Find out on which screen row the target line should be shown. */
    while (line != fileptr && line != NULL) {
2833
	row += number_of_chunks_in(line) + 1;
2834
2835
2836
	line = line->next;
    }

2837
2838
2839
2840
    /* If the first chunk is offscreen, don't even try to display it. */
    if (row < 0 || row >= editwinrows) {
	statusline(ALERT, "Badness: tried to display a chunk on row %i"
				" -- please report a bug", row);
2841
	return 0;
2842
    }
2843
2844
2845

    starting_row = row;

2846
    while (row < editwinrows) {
2847
	bool end_of_line = FALSE;
2848
2849
2850

	to_col = get_softwrap_breakpoint(fileptr->data, from_col, &end_of_line);

2851
2852
2853
	blank_row(edit, row, 0, COLS);

	/* Convert the chunk to its displayable form and draw it. */
2854
	converted = display_string(fileptr->data, from_col, to_col - from_col, TRUE);
2855
2856
2857
	edit_draw(fileptr, converted, row++, from_col);
	free(converted);

2858
2859
2860
2861
	if (end_of_line)
	    break;

	/* If the line is softwrapped before its last column, add a ">" just
2862
2863
2864
	 * after its softwrap breakpoint, unless we're softwrapping at blanks
	 * and not in the middle of a word. */
	if (!ISSET(AT_BLANKS) && to_col - from_col < editwincols)
2865
2866
2867
	    mvwaddch(edit, row - 1, to_col - from_col, '>');

	from_col = to_col;
2868
2869
2870
2871
2872
2873
    }

    return (row - starting_row);
}
#endif

2874
2875
2876
2877
/* Check whether the mark is on, or whether old_column and new_column are on
 * different "pages" (in softwrap mode, only the former applies), which means
 * that the relevant line needs to be redrawn. */
bool line_needs_update(const size_t old_column, const size_t new_column)
2878
{
2879
#ifndef NANO_TINY
2880
2881
2882
    if (openfile->mark_set)
	return TRUE;
    else
2883
#endif
2884
	return (get_page_start(old_column) != get_page_start(new_column));
2885
2886
}

2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
/* Try to move up nrows softwrapped chunks from the given line and the
 * given column (leftedge).  After moving, leftedge will be set to the
 * starting column of the current chunk.  Return the number of chunks we
 * couldn't move up, which will be zero if we completely succeeded. */
int go_back_chunks(int nrows, filestruct **line, size_t *leftedge)
{
    int i;

#ifndef NANO_TINY
    if (ISSET(SOFTWRAP)) {
2897
	/* Recede through the requested number of chunks. */
2898
	for (i = nrows; i > 0; i--) {
2899
2900
2901
2902
2903
2904
	    size_t chunk = chunk_for(*leftedge, *line);

	    *leftedge = 0;

	    if (chunk >= i)
		return go_forward_chunks(chunk - i, line, leftedge);
2905
2906
2907
2908

	    if (*line == openfile->fileage)
		break;

2909
	    i -= chunk;
2910
	    *line = (*line)->prev;
2911
	    *leftedge = HIGHEST_POSITIVE;
2912
2913
	}

2914
2915
	if (*leftedge == HIGHEST_POSITIVE)
	    *leftedge = leftedge_for(*leftedge, *line);
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
    } else
#endif
	for (i = nrows; i > 0 && (*line)->prev != NULL; i--)
	    *line = (*line)->prev;

    return i;
}

/* Try to move down nrows softwrapped chunks from the given line and the
 * given column (leftedge).  After moving, leftedge will be set to the
 * starting column of the current chunk.  Return the number of chunks we
 * couldn't move down, which will be zero if we completely succeeded. */
int go_forward_chunks(int nrows, filestruct **line, size_t *leftedge)
{
    int i;

#ifndef NANO_TINY
    if (ISSET(SOFTWRAP)) {
2934
	size_t current_leftedge = *leftedge;
2935

2936
	/* Advance through the requested number of chunks. */
2937
	for (i = nrows; i > 0; i--) {
2938
	    bool end_of_line = FALSE;
2939

2940
2941
2942
2943
	    current_leftedge = get_softwrap_breakpoint((*line)->data,
					current_leftedge, &end_of_line);

	    if (!end_of_line)
2944
2945
2946
2947
2948
2949
		continue;

	    if (*line == openfile->filebot)
		break;

	    *line = (*line)->next;
2950
	    current_leftedge = 0;
2951
2952
2953
2954
	}

	/* Only change leftedge when we actually could move. */
	if (i < nrows)
2955
	    *leftedge = current_leftedge;
2956
2957
2958
2959
2960
2961
2962
2963
    } else
#endif
	for (i = nrows; i > 0 && (*line)->next != NULL; i--)
	    *line = (*line)->next;

    return i;
}

2964
2965
2966
2967
2968
2969
2970
2971
/* Return TRUE if there are fewer than a screen's worth of lines between
 * the line at line number was_lineno (and column was_leftedge, if we're
 * in softwrap mode) and the line at current[current_x]. */
bool less_than_a_screenful(size_t was_lineno, size_t was_leftedge)
{
#ifndef NANO_TINY
    if (ISSET(SOFTWRAP)) {
	filestruct *line = openfile->current;
2972
	size_t leftedge = leftedge_for(xplustabs(), openfile->current);
2973
2974
2975
2976
2977
2978
2979
2980
2981
	int rows_left = go_back_chunks(editwinrows - 1, &line, &leftedge);

	return (rows_left > 0 || line->lineno < was_lineno ||
		(line->lineno == was_lineno && leftedge <= was_leftedge));
    } else
#endif
	return (openfile->current->lineno - was_lineno < editwinrows);
}

2982
/* Scroll the edit window in the given direction and the given number of rows,
2983
 * and draw new lines on the blank lines left after the scrolling. */
2984
void edit_scroll(bool direction, int nrows)
2985
{
2986
    filestruct *line;
2987
2988
    size_t leftedge;

2989
2990
    /* Part 1: nrows is the number of rows we're going to scroll the text of
     * the edit window. */
2991

2992
2993
    /* Move the top line of the edit window the requested number of rows up or
     * down, and reduce the number of rows with the amount we couldn't move. */
2994
    if (direction == BACKWARD)
2995
	nrows -= go_back_chunks(nrows, &openfile->edittop, &openfile->firstcolumn);
2996
    else
2997
	nrows -= go_forward_chunks(nrows, &openfile->edittop, &openfile->firstcolumn);
2998

2999
    /* Don't bother scrolling zero rows, nor more than the window can hold. */
3000
    if (nrows == 0) {
3001
#ifndef NANO_TINY
3002
	statusline(ALERT, "Underscrolling -- please report a bug");
3003
#endif
3004
	return;
3005
    }
3006
    if (nrows >= editwinrows) {
3007
#ifndef NANO_TINY
3008
3009
	if (editwinrows > 1)
	    statusline(ALERT, "Overscrolling -- please report a bug");
3010
#endif
3011
	refresh_needed = TRUE;
3012
	return;
3013
    }
3014

3015
    /* Scroll the text of the edit window a number of rows up or down. */
3016
    scrollok(edit, TRUE);
3017
    wscrl(edit, (direction == BACKWARD) ? -nrows : nrows);
3018
3019
    scrollok(edit, FALSE);

3020
3021
    /* Part 2: nrows is now the number of rows in the scrolled region of the
     * edit window that we need to draw. */
3022

3023
3024
3025
3026
    /* If we're not on the first "page" (when not softwrapping), or the mark
     * is on, the row next to the scrolled region needs to be redrawn too. */
    if (line_needs_update(openfile->placewewant, 0) && nrows < editwinrows)
	nrows++;
3027

3028
    /* If we scrolled backward, start on the first line of the blank region. */
3029
    line = openfile->edittop;
3030
    leftedge = openfile->firstcolumn;
3031

3032
    /* If we scrolled forward, move down to the start of the blank region. */
3033
    if (direction == FORWARD)
3034
	go_forward_chunks(editwinrows - nrows, &line, &leftedge);
3035

3036
#ifndef NANO_TINY
3037
3038
3039
    if (ISSET(SOFTWRAP)) {
	/* Compensate for the earlier chunks of a softwrapped line. */
	nrows += chunk_for(leftedge, line);
3040

3041
3042
3043
3044
	/* Don't compensate for the chunks that are offscreen. */
	if (line == openfile->edittop)
	    nrows -= chunk_for(openfile->firstcolumn, line);
    }
3045
#endif
3046
3047
3048

    /* Draw new content on the blank rows inside the scrolled region
     * (and on the bordering row too when it was deemed necessary). */
3049
3050
3051
    while (nrows > 0 && line != NULL) {
	nrows -= update_line(line, (line == openfile->current) ?
					openfile->current_x : 0);
3052
	line = line->next;
3053
3054
3055
    }
}

3056
#ifndef NANO_TINY
3057
3058
3059
3060
3061
3062
3063
3064
3065
/* Get the column number after leftedge where we can break the given text, and
 * return it.  This will always be editwincols or less after leftedge.  Set
 * end_of_line to TRUE if we reach the end of the line while searching the
 * text.  Assume leftedge is the leftmost column of a softwrapped chunk. */
size_t get_softwrap_breakpoint(const char *text, size_t leftedge,
				bool *end_of_line)
{
    size_t column = 0;
	/* Current column position in text. */
3066
    size_t previous_col = 0;
3067
3068
	/* Previous column position in text. */
    size_t goal_column;
3069
3070
3071
	/* The place at or before which text must be broken. */
    size_t last_blank_col = 0;
	/* The column position of the last seen whitespace character. */
3072
3073
    const char *farthest_blank = NULL;
	/* A pointer to the last seen whitespace character in text. */
3074
    int char_len = 0;
3075
	/* Length of the current character, in bytes. */
3076

3077
3078
3079
3080
    while (*text != '\0' && column < leftedge) {
	char_len = parse_mbchar(text, NULL, &column);
	text += char_len;
    }
3081

3082
3083
    /* The intention is to use the entire available width. */
    goal_column = leftedge + editwincols;
3084
3085

    while (*text != '\0' && column <= goal_column) {
3086
3087
	/* When breaking at blanks, do it *before* the target column. */
	if (ISSET(AT_BLANKS) && is_blank_mbchar(text) && column < goal_column) {
3088
	    farthest_blank = text;
3089
	    last_blank_col = column;
3090
3091
	}

3092
	previous_col = column;
3093
3094
3095
3096
	char_len = parse_mbchar(text, NULL, &column);
	text += char_len;
    }

3097
3098
    /* If we didn't overshoot the limit, we've found a breaking point;
     * and we've reached EOL if we didn't even *reach* the limit. */
3099
    if (column <= goal_column) {
3100
	*end_of_line = (column < goal_column);
3101
3102
3103
	return column;
    }

3104
3105
    /* If we're softwrapping at blanks and we found at least one blank, break
     * after that blank -- if it doesn't overshoot the screen's edge. */
3106
    if (farthest_blank != NULL) {
3107
	char_len = parse_mbchar(farthest_blank, NULL, &last_blank_col);
3108
	text = farthest_blank + char_len;
3109

3110
3111
	if (last_blank_col <= goal_column)
	    return last_blank_col;
3112
3113
    }

3114
3115
    /* If a tab is split over two chunks, break at the screen's edge. */
    if (*(text - char_len) == '\t')
3116
	previous_col = goal_column;
3117

3118
    /* Otherwise, return the column of the last character that doesn't
3119
3120
     * overshoot the limit, since we can't break the text anywhere else. */
    return (editwincols > 1) ? previous_col : column - 1;
3121
3122
3123
3124
3125
}

/* Get the row of the softwrapped chunk of the given line that column is on,
 * relative to the first row (zero-based), and return it.  If leftedge isn't
 * NULL, return the leftmost column of the chunk in it. */
3126
size_t get_chunk_and_edge(size_t column, filestruct *line, size_t *leftedge)
3127
3128
{
    size_t current_chunk = 0, start_col = 0, end_col;
3129
    bool end_of_line = FALSE;
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145

    while (TRUE) {
	end_col = get_softwrap_breakpoint(line->data, start_col, &end_of_line);

	/* We reached the end of the line and/or found column, so get out. */
	if (end_of_line || (column >= start_col && column < end_col)) {
	    if (leftedge != NULL)
		*leftedge = start_col;
	    return current_chunk;
	}

	current_chunk++;
	start_col = end_col;
    }
}

3146
3147
/* Return the row of the softwrapped chunk of the given line that column is on,
 * relative to the first row (zero-based). */
3148
size_t chunk_for(size_t column, filestruct *line)
3149
{
3150
3151
3152
3153
    if (ISSET(SOFTWRAP))
	return get_chunk_and_edge(column, line, NULL);
    else
	return 0;
3154
3155
3156
3157
}

/* Return the leftmost column of the softwrapped chunk of the given line that
 * column is on. */
3158
size_t leftedge_for(size_t column, filestruct *line)
3159
{
3160
3161
    size_t leftedge;

3162
3163
3164
    if (!ISSET(SOFTWRAP))
	return 0;

3165
    get_chunk_and_edge(column, line, &leftedge);
3166
3167

    return leftedge;
3168
3169
3170
3171
}

/* Return the row of the last softwrapped chunk of the given line, relative to
 * the first row (zero-based). */
3172
size_t number_of_chunks_in(filestruct *line)
3173
{
3174
    return chunk_for((size_t)-1, line);
3175
3176
}

3177
/* Ensure that firstcolumn is at the starting column of the softwrapped chunk
3178
3179
3180
3181
 * it's on.  We need to do this when the number of columns of the edit window
 * has changed, because then the width of softwrapped chunks has changed. */
void ensure_firstcolumn_is_aligned(void)
{
3182
3183
    openfile->firstcolumn = leftedge_for(openfile->firstcolumn,
						openfile->edittop);
3184
3185
3186

    /* If smooth scrolling is on, make sure the viewport doesn't center. */
    focusing = FALSE;
3187
}
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
#endif /* !NANO_TINY */

/* When in softwrap mode, and the given column is on or after the breakpoint of
 * a softwrapped chunk, shift it back to the last column before the breakpoint.
 * The given column is relative to the given leftedge in current.  The returned
 * column is relative to the start of the text. */
size_t actual_last_column(size_t leftedge, size_t column)
{
#ifndef NANO_TINY
    if (ISSET(SOFTWRAP)) {
	bool last_chunk;
	size_t end_col = get_softwrap_breakpoint(openfile->current->data,
					leftedge, &last_chunk) - leftedge;

	/* If we're not on the last chunk, we're one column past the end of
	 * the row.  Shifting back one column might put us in the middle of
3204
	 * a multi-column character, but actual_x() will fix that later. */
3205
3206
3207
3208
3209
3210
	if (!last_chunk)
	    end_col--;

	if (column > end_col)
	    column = end_col;
    }
3211
#endif
3212

3213
3214
3215
    return leftedge + column;
}

3216
3217
3218
3219
/* Return TRUE if current[current_x] is above the top of the screen, and FALSE
 * otherwise. */
bool current_is_above_screen(void)
{
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
#ifndef NANO_TINY
    if (ISSET(SOFTWRAP))
	/* The cursor is above screen when current[current_x] is before edittop
	 * at column firstcolumn. */
	return (openfile->current->lineno < openfile->edittop->lineno ||
		(openfile->current->lineno == openfile->edittop->lineno &&
		xplustabs() < openfile->firstcolumn));
    else
#endif
	return (openfile->current->lineno < openfile->edittop->lineno);
3230
3231
3232
3233
3234
3235
3236
3237
3238
}

/* Return TRUE if current[current_x] is below the bottom of the screen, and
 * FALSE otherwise. */
bool current_is_below_screen(void)
{
#ifndef NANO_TINY
    if (ISSET(SOFTWRAP)) {
	filestruct *line = openfile->edittop;
3239
	size_t leftedge = openfile->firstcolumn;
3240
3241

	/* If current[current_x] is more than a screen's worth of lines after
3242
	 * edittop at column firstcolumn, it's below the screen. */
3243
3244
3245
	return (go_forward_chunks(editwinrows - 1, &line, &leftedge) == 0 &&
			(line->lineno < openfile->current->lineno ||
			(line->lineno == openfile->current->lineno &&
3246
3247
			leftedge < leftedge_for(xplustabs(),
						openfile->current))));
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
    } else
#endif
	return (openfile->current->lineno >=
			openfile->edittop->lineno + editwinrows);
}

/* Return TRUE if current[current_x] is offscreen relative to edittop, and
 * FALSE otherwise. */
bool current_is_offscreen(void)
{
    return (current_is_above_screen() || current_is_below_screen());
}

3261
/* Update any lines between old_current and current that need to be
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3262
 * updated.  Use this if we've moved without changing any text. */
3263
void edit_redraw(filestruct *old_current, update_type manner)
3264
{
3265
3266
3267
3268
    size_t was_pww = openfile->placewewant;

    openfile->placewewant = xplustabs();

3269
    /* If the current line is offscreen, scroll until it's onscreen. */
3270
    if (current_is_offscreen()) {
3271
	adjust_viewport(ISSET(SMOOTH_SCROLL) ? manner : CENTERING);
3272
	refresh_needed = TRUE;
3273
	return;
3274
    }
3275

3276
#ifndef NANO_TINY
3277
3278
    /* If the mark is on, update all lines between old_current and current. */
    if (openfile->mark_set) {
3279
	filestruct *line = old_current;
3280

3281
3282
	while (line != openfile->current) {
	    update_line(line, 0);
3283

3284
3285
	    line = (line->lineno > openfile->current->lineno) ?
			line->prev : line->next;
3286
	}
3287
3288
3289
3290
3291
3292
    } 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);
3293

3294
3295
    /* Update current if the mark is on or it has changed "page", or if it
     * differs from old_current and needs to be horizontally scrolled. */
3296
    if (line_needs_update(was_pww, openfile->placewewant) ||
3297
			(old_current != openfile->current &&
3298
			get_page_start(openfile->placewewant) > 0))
3299
	update_line(openfile->current, openfile->current_x);
3300
3301
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3302
3303
/* 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
3304
3305
void edit_refresh(void)
{
3306
3307
    filestruct *line;
    int row = 0;
3308

3309
3310
3311
3312
3313
3314
#ifndef DISABLE_COLOR
    /* When needed, initialize the colors for the current syntax. */
    if (!have_palette)
	color_init();
#endif

3315
    /* If the current line is out of view, get it back on screen. */
3316
3317
    if (current_is_offscreen())
	adjust_viewport((focusing || !ISSET(SMOOTH_SCROLL)) ? CENTERING : FLOWING);
Chris Allegretta's avatar
Chris Allegretta committed
3318

3319
3320
3321
3322
    line = openfile->edittop;

    while (row < editwinrows && line != NULL) {
	if (line == openfile->current)
3323
	    row += update_line(line, openfile->current_x);
3324
	else
3325
	    row += update_line(line, 0);
3326
	line = line->next;
3327
3328
    }

3329
    while (row < editwinrows)
3330
	blank_row(edit, row++, 0, COLS);
3331

3332
    place_the_cursor();
3333
    wnoutrefresh(edit);
3334
3335

    refresh_needed = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
3336
3337
}

3338
3339
3340
3341
3342
/* Move edittop so that current is on the screen.  manner says how:
 * STATIONARY means that the cursor should stay on the same screen row,
 * CENTERING means that current should end up in the middle of the screen,
 * and FLOWING means that it should scroll no more than needed to bring
 * current into view. */
3343
void adjust_viewport(update_type manner)
Chris Allegretta's avatar
Chris Allegretta committed
3344
{
3345
    int goal = 0;
3346

3347
    if (manner == STATIONARY)
3348
	goal = openfile->current_y;
3349
3350
3351
3352
    else if (manner == CENTERING)
	goal = editwinrows / 2;
    else if (!current_is_above_screen())
	goal = editwinrows - 1;
3353

3354
    openfile->edittop = openfile->current;
3355
#ifndef NANO_TINY
3356
    if (ISSET(SOFTWRAP))
3357
	openfile->firstcolumn = leftedge_for(xplustabs(), openfile->current);
3358
#endif
3359
3360

    /* Move edittop back goal rows, starting at current[current_x]. */
3361
    go_back_chunks(goal, &openfile->edittop, &openfile->firstcolumn);
Chris Allegretta's avatar
Chris Allegretta committed
3362
3363
}

3364
/* Unconditionally redraw the entire screen. */
3365
void total_redraw(void)
3366
{
3367
3368
3369
3370
3371
3372
#ifdef USE_SLANG
    /* Slang curses emulation brain damage, part 4: Slang doesn't define
     * curscr. */
    SLsmg_touch_screen();
    SLsmg_refresh();
#else
3373
    wrefresh(curscr);
3374
#endif
3375
3376
}

3377
3378
/* Redraw the entire screen, then refresh the title bar and the content of
 * the edit window (when not in the file browser), and the bottom bars. */
3379
3380
void total_refresh(void)
{
3381
    total_redraw();
3382
3383
    if (currmenu != MBROWSER && currmenu != MWHEREISFILE && currmenu != MGOTODIR)
	titlebar(title);
3384
#ifdef ENABLE_HELP
3385
    if (inhelp)
3386
	wrap_the_help_text(TRUE);
3387
3388
    else
#endif
3389
    if (currmenu != MBROWSER && currmenu != MWHEREISFILE && currmenu != MGOTODIR)
3390
	edit_refresh();
3391
    bottombars(currmenu);
3392
3393
}

3394
3395
/* Display the main shortcut list on the last two rows of the bottom
 * portion of the window. */
3396
3397
void display_main_list(void)
{
3398
#ifndef DISABLE_COLOR
3399
3400
    if (openfile->syntax &&
		(openfile->syntax->formatter || openfile->syntax->linter))
3401
	set_lint_or_format_shortcuts();
3402
3403
3404
3405
    else
	set_spell_shortcuts();
#endif

3406
    bottombars(MMAIN);
3407
3408
}

3409
3410
3411
3412
/* Show info about the current cursor position on the statusbar.
 * Do this unconditionally when force is TRUE; otherwise, only if
 * suppress_cursorpos is FALSE.  In any case, reset the latter. */
void do_cursorpos(bool force)
Chris Allegretta's avatar
Chris Allegretta committed
3413
{
3414
3415
    char saved_byte;
    size_t sum, cur_xpt = xplustabs() + 1;
3416
    size_t cur_lenpt = strlenpt(openfile->current->data) + 1;
3417
    int linepct, colpct, charpct;
Chris Allegretta's avatar
Chris Allegretta committed
3418

3419
3420
3421
3422
3423
    /* If the showing needs to be suppressed, don't suppress it next time. */
    if (suppress_cursorpos && !force) {
	suppress_cursorpos = FALSE;
	return;
    }
3424

3425
    /* Determine the size of the file up to the cursor. */
3426
    saved_byte = openfile->current->data[openfile->current_x];
3427
    openfile->current->data[openfile->current_x] = '\0';
3428

3429
    sum = get_totsize(openfile->fileage, openfile->current);
3430

3431
    openfile->current->data[openfile->current_x] = saved_byte;
3432
3433
3434

    /* When not at EOF, subtract 1 for an overcounted newline. */
    if (openfile->current != openfile->filebot)
3435
	sum--;
3436

3437
    /* Display the current cursor position on the statusbar. */
3438
    linepct = 100 * openfile->current->lineno / openfile->filebot->lineno;
3439
    colpct = 100 * cur_xpt / cur_lenpt;
3440
    charpct = (openfile->totsize == 0) ? 0 : 100 * sum / openfile->totsize;
3441

3442
    statusline(HUSH,
3443
	_("line %ld/%ld (%d%%), col %lu/%lu (%d%%), char %lu/%lu (%d%%)"),
3444
	(long)openfile->current->lineno,
3445
	(long)openfile->filebot->lineno, linepct,
3446
	(unsigned long)cur_xpt, (unsigned long)cur_lenpt, colpct,
3447
	(unsigned long)sum, (unsigned long)openfile->totsize, charpct);
3448
3449
3450

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

3453
/* Unconditionally display the current cursor position. */
3454
void do_cursorpos_void(void)
3455
{
3456
    do_cursorpos(TRUE);
3457
3458
}

3459
void disable_waiting(void)
3460
{
3461
    waiting_mode = FALSE;
3462
    nodelay(edit, TRUE);
3463
3464
}

3465
void enable_waiting(void)
3466
{
3467
    waiting_mode = TRUE;
3468
    nodelay(edit, FALSE);
3469
3470
}

3471
3472
3473
/* Highlight the text between from_col and to_col when active is TRUE.
 * Remove the highlight when active is FALSE. */
void spotlight(bool active, size_t from_col, size_t to_col)
Chris Allegretta's avatar
Chris Allegretta committed
3474
{
3475
3476
    char *word;
    size_t word_span, room;
Chris Allegretta's avatar
Chris Allegretta committed
3477

3478
    place_the_cursor();
3479

3480
3481
3482
3483
#ifndef NANO_TINY
    if (ISSET(SOFTWRAP)) {
	spotlight_softwrapped(active, from_col, to_col);
	return;
3484
    }
3485
#endif
3486

3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
3497
3498
3499
3500
3501
3502
3503
    /* This is so we can show zero-length matches. */
    if (to_col == from_col) {
	word = mallocstrcpy(NULL, " ");
	to_col++;
    } else
	word = display_string(openfile->current->data, from_col,
				to_col - from_col, FALSE);

    word_span = strlenpt(word);

    /* Compute the number of columns that are available for the word. */
    room = editwincols + get_page_start(from_col) - from_col;

    /* If the word is partially offscreen, reserve space for the "$". */
    if (word_span > room)
	room--;

3504
    if (active)
3505
	wattron(edit, interface_color_pair[SELECTED_TEXT]);
Chris Allegretta's avatar
Chris Allegretta committed
3506

3507
    waddnstr(edit, word, actual_x(word, room));
3508

3509
    if (word_span > room)
3510
	waddch(edit, '$');
Chris Allegretta's avatar
Chris Allegretta committed
3511

3512
    if (active)
3513
	wattroff(edit, interface_color_pair[SELECTED_TEXT]);
3514

3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
    free(word);

    wnoutrefresh(edit);
}

#ifndef NANO_TINY
/* Highlight the text between from_col and to_col when active is TRUE; remove
 * the highlight when active is FALSE.  This will not highlight softwrapped
 * line breaks, since they're not actually part of the spotlighted text. */
void spotlight_softwrapped(bool active, size_t from_col, size_t to_col)
{
3526
    ssize_t row = openfile->current_y;
3527
    size_t leftedge = leftedge_for(from_col, openfile->current);
3528
    size_t break_col;
3529
    bool end_of_line = FALSE;
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
    char *word;

    while (row < editwinrows) {
	break_col = get_softwrap_breakpoint(openfile->current->data,
						leftedge, &end_of_line);

	/* Stop after the end of the word, by pretending the end of the word is
	 * the end of the line. */
	if (break_col >= to_col) {
	    end_of_line = TRUE;
	    break_col = to_col;
	}

	/* This is so we can show zero-length matches. */
	if (break_col == from_col) {
	    word = mallocstrcpy(NULL, " ");
	    break_col++;
	} else
	    word = display_string(openfile->current->data, from_col,
					break_col - from_col, FALSE);

	if (active)
3552
	    wattron(edit, interface_color_pair[SELECTED_TEXT]);
3553
3554
3555
3556

	waddnstr(edit, word, actual_x(word, break_col));

	if (active)
3557
	    wattroff(edit, interface_color_pair[SELECTED_TEXT]);
3558
3559
3560
3561
3562
3563

	free(word);

	if (end_of_line)
	    break;

3564
	wmove(edit, ++row, 0);
3565
3566
3567
3568
3569

	leftedge = break_col;
	from_col = break_col;
    }

3570
    wnoutrefresh(edit);
Chris Allegretta's avatar
Chris Allegretta committed
3571
}
3572
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3573

3574
#ifndef DISABLE_EXTRA
3575
3576
#define CREDIT_LEN 54
#define XLCREDIT_LEN 9
3577

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3578
3579
/* Easter egg: Display credits.  Assume nodelay(edit) and scrollok(edit)
 * are FALSE. */
3580
3581
void do_credits(void)
{
3582
3583
    bool old_more_space = ISSET(MORE_SPACE);
    bool old_no_help = ISSET(NO_HELP);
3584
    int kbinput = ERR, crpos = 0, xlpos = 0;
3585
3586
3587
    const char *credits[CREDIT_LEN] = {
	NULL,				/* "The nano text editor" */
	NULL,				/* "version" */
Chris Allegretta's avatar
Chris Allegretta committed
3588
3589
	VERSION,
	"",
3590
	NULL,				/* "Brought to you by:" */
Chris Allegretta's avatar
Chris Allegretta committed
3591
3592
3593
3594
3595
	"Chris Allegretta",
	"Jordi Mallach",
	"Adam Rogoyski",
	"Rob Siemborski",
	"Rocco Corsi",
3596
	"David Lawrence Ramsey",
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3597
	"David Benbennick",
Benno Schulenberg's avatar
Benno Schulenberg committed
3598
	"Mark Majeres",
3599
	"Mike Frysinger",
3600
	"Benno Schulenberg",
Chris Allegretta's avatar
Chris Allegretta committed
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
	"Ken Tyler",
	"Sven Guckes",
	"Bill Soudan",
	"Christian Weisgerber",
	"Erik Andersen",
	"Big Gaute",
	"Joshua Jensen",
	"Ryan Krebs",
	"Albert Chin",
	"",
3611
	NULL,				/* "Special thanks to:" */
3612
	"Monique, Brielle & Joseph",
Chris Allegretta's avatar
Chris Allegretta committed
3613
3614
3615
3616
3617
3618
	"Plattsburgh State University",
	"Benet Laboratories",
	"Amy Allegretta",
	"Linda Young",
	"Jeremy Robichaud",
	"Richard Kolb II",
3619
	NULL,				/* "The Free Software Foundation" */
Chris Allegretta's avatar
Chris Allegretta committed
3620
	"Linus Torvalds",
3621
	NULL,				/* "the many translators and the TP" */
3622
	NULL,				/* "For ncurses:" */
3623
3624
3625
3626
	"Thomas Dickey",
	"Pavel Curtis",
	"Zeyd Ben-Halim",
	"Eric S. Raymond",
3627
3628
3629
3630
3631
3632
	NULL,				/* "and anyone else we forgot..." */
	NULL,				/* "Thank you for using nano!" */
	"",
	"",
	"",
	"",
3633
	"(C) 2017",
3634
	"Free Software Foundation, Inc.",
3635
3636
3637
3638
	"",
	"",
	"",
	"",
3639
	"https://nano-editor.org/"
3640
3641
    };

3642
    const char *xlcredits[XLCREDIT_LEN] = {
3643
3644
3645
3646
3647
	N_("The nano text editor"),
	N_("version"),
	N_("Brought to you by:"),
	N_("Special thanks to:"),
	N_("The Free Software Foundation"),
3648
	N_("the many translators and the TP"),
3649
3650
3651
	N_("For ncurses:"),
	N_("and anyone else we forgot..."),
	N_("Thank you for using nano!")
3652
    };
3653

3654
3655
3656
3657
3658
3659
    if (!old_more_space || !old_no_help) {
	SET(MORE_SPACE);
	SET(NO_HELP);
	window_init();
    }

3660
    nodelay(edit, TRUE);
3661

3662
    blank_titlebar();
Chris Allegretta's avatar
Chris Allegretta committed
3663
    blank_edit();
3664
    blank_statusbar();
3665

3666
    wrefresh(topwin);
Chris Allegretta's avatar
Chris Allegretta committed
3667
    wrefresh(edit);
3668
    wrefresh(bottomwin);
3669
    napms(700);
3670

3671
    for (crpos = 0; crpos < CREDIT_LEN + editwinrows / 2; crpos++) {
3672
	if ((kbinput = wgetch(edit)) != ERR)
3673
	    break;
3674

3675
	if (crpos < CREDIT_LEN) {
3676
	    const char *what;
3677
	    size_t start_col;
3678

3679
3680
3681
	    if (credits[crpos] == NULL)
		what = _(xlcredits[xlpos++]);
	    else
3682
		what = credits[crpos];
3683

3684
	    start_col = COLS / 2 - strlenpt(what) / 2 - 1;
3685
	    mvwaddstr(edit, editwinrows - 1 - (editwinrows % 2),
3686
						start_col, what);
3687
	}
3688

3689
3690
3691
3692
	wrefresh(edit);

	if ((kbinput = wgetch(edit)) != ERR)
	    break;
3693
	napms(700);
3694

3695
	scrollok(edit, TRUE);
3696
	wscrl(edit, 1);
3697
	scrollok(edit, FALSE);
3698
	wrefresh(edit);
3699

3700
	if ((kbinput = wgetch(edit)) != ERR)
3701
	    break;
3702
	napms(700);
3703

3704
	scrollok(edit, TRUE);
3705
	wscrl(edit, 1);
3706
	scrollok(edit, FALSE);
3707
	wrefresh(edit);
3708
3709
    }

3710
3711
3712
    if (kbinput != ERR)
	ungetch(kbinput);

3713
    if (!old_more_space)
3714
	UNSET(MORE_SPACE);
3715
    if (!old_no_help)
3716
	UNSET(NO_HELP);
3717
    window_init();
3718

3719
    nodelay(edit, FALSE);
3720

3721
    total_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
3722
}
3723
#endif /* !DISABLE_EXTRA */