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

24
#include "proto.h"
25

26
#include <stdio.h>
27
28
29
30
#include <stdarg.h>
#include <string.h>

static char *prompt = NULL;
31
	/* The prompt string used for statusbar questions. */
32
static size_t statusbar_x = (size_t)-1;
33
	/* The cursor position in answer. */
34
static size_t statusbar_pww = (size_t)-1;
35
	/* The place we want in answer. */
36
37
static size_t old_statusbar_x = (size_t)-1;
	/* The old cursor position in answer, if any. */
38
static size_t old_pww = (size_t)-1;
39
	/* The old place we want in answer, if any. */
40
static bool reset_statusbar_x = FALSE;
41
42
	/* Should we reset the cursor position at the statusbar
	 * prompt? */
43

44
/* Read in a character, interpret it as a shortcut or toggle if
45
46
 * necessary, and return it.
 * Set ran_func to TRUE if we ran a function associated with a
47
 * shortcut key, and set finished to TRUE if we're done after running
48
49
 * or trying to run a function associated with a shortcut key.
 * refresh_func is the function we will call to refresh the edit window. */
50
51
int do_statusbar_input(bool *ran_func, bool *finished,
	void (*refresh_func)(void))
52
53
54
55
56
57
58
{
    int input;
	/* The character we read in. */
    static int *kbinput = NULL;
	/* The input buffer. */
    static size_t kbinput_len = 0;
	/* The length of the input buffer. */
59
    const sc *s;
60
    bool have_shortcut = FALSE;
61
    const subnfunc *f;
62
63
64
65
66

    *ran_func = FALSE;
    *finished = FALSE;

    /* Read in a character. */
67
    input = get_kbinput(bottomwin);
68

69
70
71
72
73
#ifndef NANO_TINY
    if (input == KEY_WINCH)
	return KEY_WINCH;
#endif

74
#ifndef DISABLE_MOUSE
75
76
    /* If we got a mouse click and it was on a shortcut, read in the
     * shortcut character. */
77
    if (func_key && input == KEY_MOUSE) {
78
	if (do_statusbar_mouse() == 1)
79
	    input = get_kbinput(bottomwin);
80
	else {
81
82
	    meta_key = FALSE;
	    func_key = FALSE;
83
	    input = ERR;
84
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
85
    }
86
87
88
#endif

    /* Check for a shortcut in the current list. */
89
    s = get_shortcut(&input);
90
91
92

    /* If we got a shortcut from the current list, or a "universal"
     * statusbar prompt shortcut, set have_shortcut to TRUE. */
93
    have_shortcut = (s != NULL);
94

95
96
    /* If we got a non-high-bit control key, a meta key sequence, or a
     * function key, and it's not a shortcut or toggle, throw it out. */
97
    if (!have_shortcut) {
98
	if (is_ascii_cntrl_char(input) || meta_key || func_key) {
99
	    beep();
100
101
	    meta_key = FALSE;
	    func_key = FALSE;
102
	    input = ERR;
103
104
105
	}
    }

106
107
108
109
110
111
112
113
    /* If we got a character, and it isn't a shortcut or toggle,
     * it's a normal text character.  Display the warning if we're
     * in view mode, or add the character to the input buffer if
     * we're not. */
    if (input != ERR && !have_shortcut) {
	/* If we're using restricted mode, the filename isn't blank,
	 * and we're at the "Write File" prompt, disable text input. */
	if (!ISSET(RESTRICTED) || openfile->filename[0] == '\0' ||
114
		currmenu != MWRITEFILE) {
115
116
117
	    kbinput_len++;
	    kbinput = (int *)nrealloc(kbinput, kbinput_len * sizeof(int));
	    kbinput[kbinput_len - 1] = input;
118
	}
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
     }

    /* If we got a shortcut, or if there aren't any other characters
     * waiting after the one we read in, we need to display all the
     * characters in the input buffer if it isn't empty. */
    if (have_shortcut || get_key_buffer_len() == 0) {
	if (kbinput != NULL) {
	    /* Display all the characters in the input buffer at
	     * once, filtering out control characters. */
	    char *output = charalloc(kbinput_len + 1);
	    size_t i;
	    bool got_enter;
		/* Whether we got the Enter key. */

	    for (i = 0; i < kbinput_len; i++)
		output[i] = (char)kbinput[i];
	    output[i] = '\0';

	    do_statusbar_output(output, kbinput_len, &got_enter, FALSE);

	    free(output);

	    /* Empty the input buffer. */
	    kbinput_len = 0;
	    free(kbinput);
	    kbinput = NULL;
145
146
	}

147
	if (have_shortcut) {
148
	    if (s->scfunc == do_tab || s->scfunc == do_enter)
149
		;
150
	    else if (s->scfunc == total_refresh)
151
		total_statusbar_refresh(refresh_func);
152
	    else if (s->scfunc == do_cut_text_void) {
153
154
155
156
		/* If we're using restricted mode, the filename
		 * isn't blank, and we're at the "Write File"
		 * prompt, disable Cut. */
		if (!ISSET(RESTRICTED) || openfile->filename[0] ==
157
			'\0' || currmenu != MWRITEFILE)
158
		    do_statusbar_cut_text();
159
	    } else if (s->scfunc == do_left)
160
		do_statusbar_left();
161
162
	    else if (s->scfunc == do_right)
		do_statusbar_right();
163
#ifndef NANO_TINY
164
	    else if (s->scfunc == do_prev_word_void)
165
		do_statusbar_prev_word(FALSE);
166
167
	    else if (s->scfunc == do_next_word_void)
		do_statusbar_next_word(FALSE);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
168
#endif
169
	    else if (s->scfunc == do_home)
170
		do_statusbar_home();
171
	    else if (s->scfunc == do_end)
172
		do_statusbar_end();
173
	    else if (s->scfunc == do_verbatim_input) {
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
		/* If we're using restricted mode, the filename
		 * isn't blank, and we're at the "Write File"
		 * prompt, disable verbatim input. */
		if (!ISSET(RESTRICTED) || currmenu != MWRITEFILE ||
			openfile->filename[0] == '\0') {
		    bool got_enter;
		    /* Whether we got the Enter key. */

		    do_statusbar_verbatim_input(&got_enter);

		    /* If we got the Enter key, remove it from the input
		     * buffer, set input to the key value for Enter, and
		     * set finished to TRUE to indicate that we're done. */
		    if (got_enter) {
			get_input(NULL, 1);
189
			input = sc_seq_or(do_enter, 0);
190
			*finished = TRUE;
191
		    }
192
		}
193
	    } else if (s->scfunc == do_delete) {
194
195
196
197
		/* If we're using restricted mode, the filename
		 * isn't blank, and we're at the "Write File"
		 * prompt, disable Delete. */
		if (!ISSET(RESTRICTED) || openfile->filename[0] ==
198
			'\0' || currmenu != MWRITEFILE)
199
		    do_statusbar_delete();
200
	    } else if (s->scfunc == do_backspace) {
201
202
203
204
		/* If we're using restricted mode, the filename
		 * isn't blank, and we're at the "Write File"
		 * prompt, disable Backspace. */
		if (!ISSET(RESTRICTED) || openfile->filename[0] ==
205
			'\0' || currmenu != MWRITEFILE)
206
207
		    do_statusbar_backspace();
	    } else {
208
		/* Handle any other shortcut in the current menu, setting
209
210
211
212
		 * ran_func to TRUE if we try to run their associated
		 * functions and setting finished to TRUE to indicate
		 * that we're done after running or trying to run their
		 * associated functions. */
213
		f = sctofunc(s);
214
		if (s->scfunc != NULL) {
215
		    *ran_func = TRUE;
216
217
		    if (f && (!ISSET(VIEW_MODE) || f->viewok) &&
				f->scfunc != do_gotolinecolumn_void)
218
			f->scfunc();
219
220
		}
		*finished = TRUE;
221
222
223
224
225
226
227
228
	    }
	}
    }

    return input;
}

#ifndef DISABLE_MOUSE
229
/* Handle a mouse click on the statusbar prompt or the shortcut list. */
230
int do_statusbar_mouse(void)
231
232
{
    int mouse_x, mouse_y;
233
    int retval = get_mouseinput(&mouse_x, &mouse_y, TRUE);
234

235
236
237
238
    /* We can click on the statusbar window text to move the cursor. */
    if (retval == 0 && wmouse_trafo(bottomwin, &mouse_y, &mouse_x,
	FALSE)) {
	size_t start_col;
239

240
	assert(prompt != NULL);
241

242
	start_col = strlenpt(prompt) + 2;
243

244
	/* Move to where the click occurred. */
245
	if (mouse_x >= start_col && mouse_y == 0) {
246
	    size_t pww_save = statusbar_pww;
247

248
	    statusbar_x = actual_x(answer,
249
			get_statusbar_page_start(start_col, start_col +
250
			statusbar_xplustabs()) + mouse_x - start_col);
251
	    statusbar_pww = statusbar_xplustabs();
252

253
	    if (need_statusbar_update(pww_save))
254
		update_statusbar_line(answer, statusbar_x);
255
256
257
258
259
260
261
262
	}
    }

    return retval;
}
#endif

/* The user typed output_len multibyte characters.  Add them to the
263
264
265
 * statusbar prompt, setting got_enter to TRUE if we get a newline, and
 * filtering out all ASCII control characters if allow_cntrls is
 * TRUE. */
266
void do_statusbar_output(char *output, size_t output_len, bool
267
	*got_enter, bool allow_cntrls)
268
269
270
271
272
273
274
275
276
277
278
{
    size_t answer_len, i = 0;
    char *char_buf = charalloc(mb_cur_max());
    int char_buf_len;

    assert(answer != NULL);

    answer_len = strlen(answer);
    *got_enter = FALSE;

    while (i < output_len) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
279
280
	/* If allow_cntrls is TRUE, convert nulls and newlines
	 * properly. */
281
282
283
	if (allow_cntrls) {
	    /* Null to newline, if needed. */
	    if (output[i] == '\0')
284
		output[i] = '\n';
285
286
	    /* Newline to Enter, if needed. */
	    else if (output[i] == '\n') {
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
		/* Set got_enter to TRUE to indicate that we got the
		 * Enter key, put back the rest of the characters in
		 * output so that they can be parsed and output again,
		 * and get out. */
		*got_enter = TRUE;
		unparse_kbinput(output + i, output_len - i);
		return;
	    }
	}

	/* Interpret the next multibyte character. */
	char_buf_len = parse_mbchar(output + i, char_buf, NULL);

	i += char_buf_len;

302
	/* If allow_cntrls is FALSE, filter out an ASCII control
303
304
305
	 * character. */
	if (!allow_cntrls && is_ascii_cntrl_char(*(output + i -
		char_buf_len)))
306
307
	    continue;

Benno Schulenberg's avatar
Benno Schulenberg committed
308
	/* More dangerousness fun. =) */
309
310
311
312
	answer = charealloc(answer, answer_len + (char_buf_len * 2));

	assert(statusbar_x <= answer_len);

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
313
314
	charmove(answer + statusbar_x + char_buf_len,
		answer + statusbar_x, answer_len - statusbar_x +
315
		char_buf_len);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
316
	strncpy(answer + statusbar_x, char_buf, char_buf_len);
317
318
319
320
321
322
	answer_len += char_buf_len;

	statusbar_x += char_buf_len;
    }

    free(char_buf);
323
324
325

    statusbar_pww = statusbar_xplustabs();

326
    update_statusbar_line(answer, statusbar_x);
327
328
}

329
330
331
332
/* Move to the beginning of the prompt text.  If the SMART_HOME flag is
 * set, move to the first non-whitespace character of the prompt text if
 * we're not already there, or to the beginning of the prompt text if we
 * are. */
333
334
void do_statusbar_home(void)
{
335
336
    size_t pww_save = statusbar_pww;

337
#ifndef NANO_TINY
338
339
340
341
342
343
344
345
    if (ISSET(SMART_HOME)) {
	size_t statusbar_x_save = statusbar_x;

	statusbar_x = indent_length(answer);

	if (statusbar_x == statusbar_x_save ||
		statusbar_x == strlen(answer))
	    statusbar_x = 0;
346
347

	statusbar_pww = statusbar_xplustabs();
348
    } else
349
#endif
350
    {
351
	statusbar_x = 0;
352
353
354
	statusbar_pww = statusbar_xplustabs();
    }

355
    if (need_statusbar_update(pww_save))
356
	update_statusbar_line(answer, statusbar_x);
357
358
}

359
/* Move to the end of the prompt text. */
360
361
void do_statusbar_end(void)
{
362
363
    size_t pww_save = statusbar_pww;

364
    statusbar_x = strlen(answer);
365
366
    statusbar_pww = statusbar_xplustabs();

367
    if (need_statusbar_update(pww_save))
368
	update_statusbar_line(answer, statusbar_x);
369
370
}

371
372
/* Move left one character. */
void do_statusbar_left(void)
373
{
374
    if (statusbar_x > 0) {
375
376
	size_t pww_save = statusbar_pww;

377
	statusbar_x = move_mbleft(answer, statusbar_x);
378
379
	statusbar_pww = statusbar_xplustabs();

380
	if (need_statusbar_update(pww_save))
381
	    update_statusbar_line(answer, statusbar_x);
382
    }
383
384
}

385
386
/* Move right one character. */
void do_statusbar_right(void)
387
{
388
    if (statusbar_x < strlen(answer)) {
389
390
	size_t pww_save = statusbar_pww;

391
	statusbar_x = move_mbright(answer, statusbar_x);
392
393
	statusbar_pww = statusbar_xplustabs();

394
	if (need_statusbar_update(pww_save))
395
	    update_statusbar_line(answer, statusbar_x);
396
    }
397
398
}

399
/* Backspace over one character. */
400
401
402
403
404
405
406
407
void do_statusbar_backspace(void)
{
    if (statusbar_x > 0) {
	do_statusbar_left();
	do_statusbar_delete();
    }
}

408
/* Delete one character. */
409
410
void do_statusbar_delete(void)
{
411
412
    statusbar_pww = statusbar_xplustabs();

413
414
415
416
417
418
419
420
421
422
423
424
    if (answer[statusbar_x] != '\0') {
	int char_buf_len = parse_mbchar(answer + statusbar_x, NULL,
		NULL);
	size_t line_len = strlen(answer + statusbar_x);

	assert(statusbar_x < strlen(answer));

	charmove(answer + statusbar_x, answer + statusbar_x +
		char_buf_len, strlen(answer) - statusbar_x -
		char_buf_len + 1);

	null_at(&answer, statusbar_x + line_len - char_buf_len);
425
426

	update_statusbar_line(answer, statusbar_x);
427
428
429
    }
}

430
/* Move text from the prompt into oblivion. */
431
432
433
434
void do_statusbar_cut_text(void)
{
    assert(answer != NULL);

435
#ifndef NANO_TINY
436
437
    if (ISSET(CUT_TO_END))
	null_at(&answer, statusbar_x);
438
    else
439
#endif
440
    {
441
442
	null_at(&answer, 0);
	statusbar_x = 0;
443
	statusbar_pww = statusbar_xplustabs();
444
    }
445

446
    update_statusbar_line(answer, statusbar_x);
447
448
}

449
#ifndef NANO_TINY
450
451
452
/* Move to the next word in the prompt text.  If allow_punct is TRUE,
 * treat punctuation as part of a word.  Return TRUE if we started on a
 * word, and FALSE otherwise. */
453
454
bool do_statusbar_next_word(bool allow_punct)
{
455
    size_t pww_save = statusbar_pww;
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
    char *char_mb;
    int char_mb_len;
    bool end_line = FALSE, started_on_word = FALSE;

    assert(answer != NULL);

    char_mb = charalloc(mb_cur_max());

    /* Move forward until we find the character after the last letter of
     * the current word. */
    while (!end_line) {
	char_mb_len = parse_mbchar(answer + statusbar_x, char_mb, NULL);

	/* If we've found it, stop moving forward through the current
	 * line. */
	if (!is_word_mbchar(char_mb, allow_punct))
	    break;

	/* If we haven't found it, then we've started on a word, so set
	 * started_on_word to TRUE. */
	started_on_word = TRUE;

	if (answer[statusbar_x] == '\0')
	    end_line = TRUE;
	else
	    statusbar_x += char_mb_len;
    }

    /* Move forward until we find the first letter of the next word. */
    if (answer[statusbar_x] == '\0')
	end_line = TRUE;
    else
	statusbar_x += char_mb_len;

    while (!end_line) {
	char_mb_len = parse_mbchar(answer + statusbar_x, char_mb, NULL);

	/* If we've found it, stop moving forward through the current
	 * line. */
	if (is_word_mbchar(char_mb, allow_punct))
	    break;

	if (answer[statusbar_x] == '\0')
	    end_line = TRUE;
	else
	    statusbar_x += char_mb_len;
    }

    free(char_mb);

506
507
    statusbar_pww = statusbar_xplustabs();

508
    if (need_statusbar_update(pww_save))
509
	update_statusbar_line(answer, statusbar_x);
510

511
512
513
514
    /* Return whether we started on a word. */
    return started_on_word;
}

515
/* Move to the previous word in the prompt text.  If allow_punct is
516
517
518
519
 * TRUE, treat punctuation as part of a word.  Return TRUE if we started
 * on a word, and FALSE otherwise. */
bool do_statusbar_prev_word(bool allow_punct)
{
520
    size_t pww_save = statusbar_pww;
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
    char *char_mb;
    int char_mb_len;
    bool begin_line = FALSE, started_on_word = FALSE;

    assert(answer != NULL);

    char_mb = charalloc(mb_cur_max());

    /* Move backward until we find the character before the first letter
     * of the current word. */
    while (!begin_line) {
	char_mb_len = parse_mbchar(answer + statusbar_x, char_mb, NULL);

	/* If we've found it, stop moving backward through the current
	 * line. */
	if (!is_word_mbchar(char_mb, allow_punct))
	    break;

	/* If we haven't found it, then we've started on a word, so set
	 * started_on_word to TRUE. */
	started_on_word = TRUE;

	if (statusbar_x == 0)
	    begin_line = TRUE;
	else
	    statusbar_x = move_mbleft(answer, statusbar_x);
    }

    /* Move backward until we find the last letter of the previous
     * word. */
    if (statusbar_x == 0)
	begin_line = TRUE;
    else
	statusbar_x = move_mbleft(answer, statusbar_x);

    while (!begin_line) {
	char_mb_len = parse_mbchar(answer + statusbar_x, char_mb, NULL);

	/* If we've found it, stop moving backward through the current
	 * line. */
	if (is_word_mbchar(char_mb, allow_punct))
	    break;

	if (statusbar_x == 0)
	    begin_line = TRUE;
	else
	    statusbar_x = move_mbleft(answer, statusbar_x);
    }

    /* If we've found it, move backward until we find the character
     * before the first letter of the previous word. */
    if (!begin_line) {
	if (statusbar_x == 0)
	    begin_line = TRUE;
	else
	    statusbar_x = move_mbleft(answer, statusbar_x);

	while (!begin_line) {
	    char_mb_len = parse_mbchar(answer + statusbar_x, char_mb,
		NULL);

	    /* If we've found it, stop moving backward through the
	     * current line. */
	    if (!is_word_mbchar(char_mb, allow_punct))
		break;

	    if (statusbar_x == 0)
		begin_line = TRUE;
	    else
		statusbar_x = move_mbleft(answer, statusbar_x);
	}

	/* If we've found it, move forward to the first letter of the
	 * previous word. */
	if (!begin_line)
	    statusbar_x += char_mb_len;
    }

    free(char_mb);

601
602
    statusbar_pww = statusbar_xplustabs();

603
    if (need_statusbar_update(pww_save))
604
	update_statusbar_line(answer, statusbar_x);
605

606
607
608
    /* Return whether we started on a word. */
    return started_on_word;
}
609
#endif /* !NANO_TINY */
610

611
612
/* Get verbatim input.  Set got_enter to TRUE if we got the Enter key as
 * part of the verbatim input. */
613
614
615
616
617
618
619
620
621
622
623
624
void do_statusbar_verbatim_input(bool *got_enter)
{
    int *kbinput;
    size_t kbinput_len, i;
    char *output;

    *got_enter = FALSE;

    /* Read in all the verbatim characters. */
    kbinput = get_verbatim_kbinput(bottomwin, &kbinput_len);

    /* Display all the verbatim characters at once, not filtering out
625
     * control characters. */
626
627
628
629
630
631
    output = charalloc(kbinput_len + 1);

    for (i = 0; i < kbinput_len; i++)
	output[i] = (char)kbinput[i];
    output[i] = '\0';

632
    do_statusbar_output(output, kbinput_len, got_enter, TRUE);
633
634
635
636

    free(output);
}

637

638
/* Return the placewewant associated with statusbar_x, i.e. the
639
640
641
642
643
644
645
646
647
648
649
650
651
652
 * zero-based column position of the cursor.  The value will be no
 * smaller than statusbar_x. */
size_t statusbar_xplustabs(void)
{
    return strnlenpt(answer, statusbar_x);
}

/* nano scrolls horizontally within a line in chunks.  This function
 * returns the column number of the first character displayed in the
 * statusbar prompt when the cursor is at the given column with the
 * prompt ending at start_col.  Note that (0 <= column -
 * get_statusbar_page_start(column) < COLS). */
size_t get_statusbar_page_start(size_t start_col, size_t column)
{
653
    if (column == start_col || column < COLS - 1 || COLS == start_col + 1)
654
655
656
657
658
659
	return 0;
    else
	return column - start_col - (column - start_col) % (COLS -
		start_col - 1);
}

660
661
662
/* Put the cursor in the statusbar prompt at statusbar_x. */
void reset_statusbar_cursor(void)
{
663
    size_t start_col = strlenpt(prompt) + 2;
664
665
    size_t xpt = statusbar_xplustabs();

666
    wmove(bottomwin, 0, start_col + xpt -
667
668
669
	get_statusbar_page_start(start_col, start_col + xpt));
}

670
671
/* Repaint the statusbar when getting a character in
 * get_prompt_string().  The statusbar text line will be displayed
672
 * starting with curranswer[index]. */
673
void update_statusbar_line(const char *curranswer, size_t index)
674
{
675
    size_t start_col, page_start;
676
677
    char *expanded;

678
    assert(prompt != NULL && index <= strlen(curranswer));
679

680
    start_col = strlenpt(prompt) + 2;
681
682
    index = strnlenpt(curranswer, index);
    page_start = get_statusbar_page_start(start_col, start_col + index);
683

684
685
686
    if (interface_color_pair[TITLE_BAR].bright)
	wattron(bottomwin, A_BOLD);
    wattron(bottomwin, interface_color_pair[TITLE_BAR].pairnum);
687
688
689
690
691
692
693

    blank_statusbar();

    mvwaddnstr(bottomwin, 0, 0, prompt, actual_x(prompt, COLS - 2));
    waddch(bottomwin, ':');
    waddch(bottomwin, (page_start == 0) ? ' ' : '$');

694
695
    expanded = display_string(curranswer, page_start, COLS - start_col -
	1, FALSE);
696
697
698
    waddstr(bottomwin, expanded);
    free(expanded);

699
700
    wattroff(bottomwin, A_BOLD);
    wattroff(bottomwin, interface_color_pair[TITLE_BAR].pairnum);
701
    statusbar_pww = statusbar_xplustabs();
702
    reset_statusbar_cursor();
703
    wnoutrefresh(bottomwin);
704
705
}

706
707
/* Return TRUE if we need an update after moving the cursor, and FALSE
 * otherwise.  We need an update if pww_save and statusbar_pww are on
708
 * different pages. */
709
bool need_statusbar_update(size_t pww_save)
710
{
711
    size_t start_col = strlenpt(prompt) + 2;
712

713
    return get_statusbar_page_start(start_col, start_col + pww_save) !=
714
715
716
	get_statusbar_page_start(start_col, start_col + statusbar_pww);
}

717
718
719
720
721
722
723
724
/* Unconditionally redraw the entire screen, and then refresh it using
 * refresh_func(). */
void total_statusbar_refresh(void (*refresh_func)(void))
{
    total_redraw();
    refresh_func();
}

725
726
/* Get a string of input at the statusbar prompt.  This should only be
 * called from do_prompt(). */
727
functionptrtype get_prompt_string(int *actual, bool allow_tabs,
728
729
#ifndef DISABLE_TABCOMP
	bool allow_files,
730
	bool *list,
731
732
#endif
	const char *curranswer,
733
#ifndef DISABLE_HISTORIES
734
735
	filestruct **history_list,
#endif
736
	void (*refresh_func)(void))
737
{
738
    int kbinput = ERR;
739
    bool ran_func, finished;
740
    size_t curranswer_len;
741
    functionptrtype func;
742
743
744
745
#ifndef DISABLE_TABCOMP
    bool tabbed = FALSE;
	/* Whether we've pressed Tab. */
#endif
746
#ifndef DISABLE_HISTORIES
747
748
749
750
751
752
753
754
755
756
757
758
    char *history = NULL;
	/* The current history string. */
    char *magichistory = NULL;
	/* The temporary string typed at the bottom of the history, if
	 * any. */
#ifndef DISABLE_TABCOMP
    int last_kbinput = ERR;
	/* The key we pressed before the current key. */
    size_t complete_len = 0;
	/* The length of the original string that we're trying to
	 * tab complete, if any. */
#endif
759
#endif /* !DISABLE_HISTORIES */
760
761
762
763

    answer = mallocstrcpy(answer, curranswer);
    curranswer_len = strlen(answer);

764
765
766
767
768
769
770
771
772
773
    /* If reset_statusbar_x is TRUE, restore statusbar_x and
     * statusbar_pww to what they were before this prompt.  Then, if
     * statusbar_x is uninitialized or past the end of curranswer, put
     * statusbar_x at the end of the string and update statusbar_pww
     * based on it.  We do these things so that the cursor position
     * stays at the right place if a prompt-changing toggle is pressed,
     * or if this prompt was started from another prompt and we cancel
     * out of it. */
    if (reset_statusbar_x) {
	statusbar_x = old_statusbar_x;
774
	statusbar_pww = old_pww;
775
776
777
    }

    if (statusbar_x == (size_t)-1 || statusbar_x > curranswer_len) {
778
	statusbar_x = curranswer_len;
779
780
	statusbar_pww = statusbar_xplustabs();
    }
781

782
#ifdef DEBUG
783
    fprintf(stderr, "get_prompt_string: answer = \"%s\", statusbar_x = %lu\n", answer, (unsigned long) statusbar_x);
784
#endif
785

786
    update_statusbar_line(answer, statusbar_x);
787
788
789
790
791
792
793

    /* Refresh the edit window and the statusbar before getting
     * input. */
    wnoutrefresh(edit);
    wnoutrefresh(bottomwin);

    /* If we're using restricted mode, we aren't allowed to change the
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
794
795
796
797
     * name of the current file once it has one, because that would
     * allow writing to files not specified on the command line.  In
     * this case, disable all keys that would change the text if the
     * filename isn't blank and we're at the "Write File" prompt. */
798
    while (TRUE) {
799
	kbinput = do_statusbar_input(&ran_func, &finished, refresh_func);
800
801
	assert(statusbar_x <= strlen(answer));

802
803
804
805
806
807
808
809
#ifndef NANO_TINY
    if (kbinput == KEY_WINCH) {
	refresh_func();
	update_statusbar_line(answer, statusbar_x);
	continue;
    }
#endif

810
	func = func_from_key(&kbinput);
811

812
	if (func == do_cancel || func == do_enter)
813
	    break;
814

815
#ifndef DISABLE_TABCOMP
816
	if (func != do_tab)
817
818
	    tabbed = FALSE;

819
	if (func == do_tab) {
820
#ifndef DISABLE_HISTORIES
821
822
823
	    if (history_list != NULL) {
		if (last_kbinput != sc_seq_or(do_tab, NANO_CONTROL_I))
		    complete_len = strlen(answer);
824

825
826
		if (complete_len > 0) {
		    answer = mallocstrcpy(answer,
827
				get_history_completion(history_list,
828
829
830
831
					answer, complete_len));
		    statusbar_x = strlen(answer);
		}
	    } else
832
#endif
833
834
835
	    if (allow_tabs)
		answer = input_tab(answer, allow_files, &statusbar_x,
				   &tabbed, refresh_func, list);
836

837
	    update_statusbar_line(answer, statusbar_x);
838
	} else
839
#endif /* !DISABLE_TABCOMP */
840
#ifndef DISABLE_HISTORIES
841
	if (func == get_history_older_void) {
842
843
844
845
846
847
848
849
850
851
852
853
	    if (history_list != NULL) {
		/* If we're scrolling up at the bottom of the history list
		 * and answer isn't blank, save answer in magichistory. */
		if ((*history_list)->next == NULL && answer[0] != '\0')
		    magichistory = mallocstrcpy(magichistory, answer);

		/* Get the older search from the history list and save it in
		 * answer.  If there is no older search, don't do anything. */
		if ((history = get_history_older(history_list)) != NULL) {
		    answer = mallocstrcpy(answer, history);
		    statusbar_x = strlen(answer);
		}
854

855
		update_statusbar_line(answer, statusbar_x);
856

857
858
859
860
		/* This key has a shortcut-list entry when it's used to
		 * move to an older search, which means that finished has
		 * been set to TRUE.  Set it back to FALSE here, so that
		 * we aren't kicked out of the statusbar prompt. */
861
		finished = FALSE;
862
	    }
863
	} else if (func == get_history_newer_void) {
864
865
866
867
868
869
870
	    if (history_list != NULL) {
		/* Get the newer search from the history list and save it in
		 * answer.  If there is no newer search, don't do anything. */
		if ((history = get_history_newer(history_list)) != NULL) {
		    answer = mallocstrcpy(answer, history);
		    statusbar_x = strlen(answer);
		}
871

872
873
874
875
876
		/* If, after scrolling down, we're at the bottom of the
		 * history list, answer is blank, and magichistory is set,
		 * save magichistory in answer. */
		if ((*history_list)->next == NULL &&
		    *answer == '\0' && magichistory != NULL) {
877
878
879
			answer = mallocstrcpy(answer, magichistory);
			statusbar_x = strlen(answer);
		    }
880

881
		update_statusbar_line(answer, statusbar_x);
882

883
884
885
886
887
888
		/* This key has a shortcut-list entry when it's used to
		 * move to a newer search, which means that finished has
		 * been set to TRUE.  Set it back to FALSE here, so that
		 * we aren't kicked out of the statusbar prompt. */
		finished = FALSE;
	    }
889
	} else
890
#endif /* !DISABLE_HISTORIES */
891
	if (func == do_help_void) {
892
	    update_statusbar_line(answer, statusbar_x);
893

894
895
896
897
898
899
	    /* This key has a shortcut-list entry when it's used to go to
	     * the help browser or display a message indicating that help
	     * is disabled, which means that finished has been set to TRUE.
	     * Set it back to FALSE here, so that we aren't kicked out of
	     * the statusbar prompt. */
	    finished = FALSE;
900
901
	}

902
903
	/* If we have a shortcut with an associated function, break out if
	 * we're finished after running or trying to run the function. */
904
905
906
	if (finished)
	    break;

907
#if !defined(DISABLE_HISTORIES) && !defined(DISABLE_TABCOMP)
908
909
910
	last_kbinput = kbinput;
#endif

911
	reset_statusbar_cursor();
912
	wnoutrefresh(bottomwin);
913
    }
914

915
#ifndef DISABLE_HISTORIES
916
917
    /* Set the current position in the history list to the bottom,
     * and free magichistory if we need to. */
918
919
920
    if (history_list != NULL) {
	history_reset(*history_list);

921
	free(magichistory);
922
923
924
    }
#endif

925
926
927
928
    /* We've finished putting in an answer or run a normal shortcut's
     * associated function, so reset statusbar_x and statusbar_pww.  If
     * we've finished putting in an answer, reset the statusbar cursor
     * position too. */
929
    if (func) {
930
	if (func == do_cancel || func == do_enter || ran_func) {
931
932
	    statusbar_x = old_statusbar_x;
	    statusbar_pww = old_pww;
933

934
935
	    if (!ran_func)
		reset_statusbar_x = TRUE;
936
937
938
    /* Otherwise, we're still putting in an answer or a shortcut with
     * an associated function, so leave the statusbar cursor position
     * alone. */
939
940
941
	} else
	    reset_statusbar_x = FALSE;
    }
942

943
    *actual = kbinput;
944
945

    return func;
946
947
}

948
949
950
951
/* Ask a question on the statusbar.  The prompt will be stored in the
 * static prompt, which should be NULL initially, and the answer will be
 * stored in the answer global.  Returns -1 on aborted enter, -2 on a
 * blank string, and 0 otherwise, the valid shortcut key caught.
952
953
954
 * curranswer is any editable text that we want to put up by default,
 * and refresh_func is the function we want to call to refresh the edit
 * window.
955
956
 *
 * The allow_tabs parameter indicates whether we should allow tabs to be
957
958
959
960
961
962
963
 * interpreted.  The allow_files parameter indicates whether we should
 * allow all files (as opposed to just directories) to be tab
 * completed. */
int do_prompt(bool allow_tabs,
#ifndef DISABLE_TABCOMP
	bool allow_files,
#endif
964
	int menu, const char *curranswer,
965
#ifndef DISABLE_HISTORIES
966
967
	filestruct **history_list,
#endif
968
	void (*refresh_func)(void), const char *msg, ...)
969
970
971
{
    va_list ap;
    int retval;
972
    functionptrtype func;
973
974
975
976
#ifndef DISABLE_TABCOMP
    bool list = FALSE;
#endif

977
978
    /* prompt has been freed and set to NULL unless the user resized
     * while at the statusbar prompt. */
979
    free(prompt);
980

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
981
    prompt = charalloc(((COLS - 4) * mb_cur_max()) + 1);
982

983
    bottombars(menu);
984
985
986
987
988
989

    va_start(ap, msg);
    vsnprintf(prompt, (COLS - 4) * mb_cur_max(), msg, ap);
    va_end(ap);
    null_at(&prompt, actual_x(prompt, COLS - 4));

990
    func = get_prompt_string(&retval, allow_tabs,
991
992
#ifndef DISABLE_TABCOMP
	allow_files,
993
	&list,
994
995
#endif
	curranswer,
996
#ifndef DISABLE_HISTORIES
997
	history_list,
998
#endif
999
	refresh_func);
1000

1001
1002
1003
    free(prompt);
    prompt = NULL;

1004
1005
    /* We're done with the prompt, so save the statusbar cursor
     * position. */
1006
    old_statusbar_x = statusbar_x;
1007
    old_pww = statusbar_pww;
1008

1009
1010
    /* If we left the prompt via Cancel or Enter, set the return value
     * properly. */
1011
    if (func == do_cancel)
1012
	retval = -1;
1013
    else if (func == do_enter)
1014
	retval = (*answer == '\0') ? -2 : 0;
1015

1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
    blank_statusbar();
    wnoutrefresh(bottomwin);

#ifdef DEBUG
    fprintf(stderr, "answer = \"%s\"\n", answer);
#endif

#ifndef DISABLE_TABCOMP
    /* If we've done tab completion, there might be a list of filename
     * matches on the edit window at this point.  Make sure that they're
     * cleared off. */
    if (list)
1028
	refresh_func();
1029
1030
1031
1032
1033
#endif

    return retval;
}

1034
/* This function forces a reset of the statusbar cursor position.  It
1035
 * should be called when we get out of all statusbar prompts. */
1036
void do_prompt_abort(void)
1037
{
1038
1039
1040
1041
    /* Uninitialize the old cursor position in answer. */
    old_statusbar_x = (size_t)-1;
    old_pww = (size_t)-1;

1042
    reset_statusbar_x = TRUE;
1043
}
1044
1045
1046
1047

/* Ask a simple Yes/No (and optionally All) question, specified in msg,
 * on the statusbar.  Return 1 for Yes, 0 for No, 2 for All (if all is
 * TRUE when passed in), and -1 for Cancel. */
1048
int do_yesno_prompt(bool all, const char *msg)
1049
1050
1051
1052
1053
{
    int ok = -2, width = 16;
    const char *yesstr;		/* String of Yes characters accepted. */
    const char *nostr;		/* Same for No. */
    const char *allstr;		/* And All, surprise! */
1054
    int oldmenu = currmenu;
1055
1056
1057
1058
1059
1060

    assert(msg != NULL);

    /* yesstr, nostr, and allstr are strings of any length.  Each string
     * consists of all single-byte characters accepted as valid
     * characters for that value.  The first value will be the one
1061
1062
     * displayed in the shortcuts. */
    /* TRANSLATORS: For the next three strings, if possible, specify
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1063
1064
     * the single-byte shortcuts for both your language and English.
     * For example, in French: "OoYy" for "Oui". */
1065
1066
1067
1068
    yesstr = _("Yy");
    nostr = _("Nn");
    allstr = _("Aa");

1069
1070
1071
1072
1073
1074
1075
    do {
	int kbinput;
	functionptrtype func;
#ifndef DISABLE_MOUSE
	int mouse_x, mouse_y;
#endif

1076
1077
	if (!ISSET(NO_HELP)) {
	    char shortstr[3];
1078
		/* Temporary string for (translated) " Y", " N" and " A". */
1079

1080
1081
	    if (COLS < 32)
		width = COLS / 2;
1082

1083
1084
	    /* Clear the shortcut list from the bottom of the screen. */
	    blank_bottombars();
1085

1086
1087
1088
1089
	    /* Now show the ones for "Yes", "No", "Cancel" and maybe "All". */
	    sprintf(shortstr, " %c", yesstr[0]);
	    wmove(bottomwin, 1, 0);
	    onekey(shortstr, _("Yes"), width);
1090

1091
1092
	    if (all) {
		shortstr[1] = allstr[0];
1093
		wmove(bottomwin, 1, width);
1094
1095
		onekey(shortstr, _("All"), width);
	    }
1096

1097
	    shortstr[1] = nostr[0];
1098
	    wmove(bottomwin, 2, 0);
1099
	    onekey(shortstr, _("No"), width);
1100

1101
	    wmove(bottomwin, 2, width);
1102
1103
	    onekey("^C", _("Cancel"), width);
	}
1104

1105
1106
1107
	if (interface_color_pair[TITLE_BAR].bright)
	    wattron(bottomwin, A_BOLD);
	wattron(bottomwin, interface_color_pair[TITLE_BAR].pairnum);
1108

1109
1110
	blank_statusbar();
	mvwaddnstr(bottomwin, 0, 0, msg, actual_x(msg, COLS - 1));
1111

1112
1113
	wattroff(bottomwin, A_BOLD);
	wattroff(bottomwin, interface_color_pair[TITLE_BAR].pairnum);
1114

1115
1116
1117
	/* Refresh edit window and statusbar before getting input. */
	wnoutrefresh(edit);
	wnoutrefresh(bottomwin);
1118

1119
	currmenu = MYESNO;
1120
	kbinput = get_kbinput(bottomwin);
1121
1122
1123
1124
1125
1126

#ifndef NANO_TINY
	if (kbinput == KEY_WINCH)
	    continue;
#endif

1127
	func = func_from_key(&kbinput);
1128

1129
	if (func == do_cancel)
1130
	    ok = -1;
1131
#ifndef DISABLE_MOUSE
1132
	else if (kbinput == KEY_MOUSE) {
1133
1134
		/* We can click on the Yes/No/All shortcut list to
		 * select an answer. */
1135
		if (get_mouseinput(&mouse_x, &mouse_y, FALSE) == 0 &&
1136
			wmouse_trafo(bottomwin, &mouse_y, &mouse_x,
1137
1138
			FALSE) && mouse_x < (width * 2) &&
			mouse_y > 0) {
1139
		    int x = mouse_x / width;
1140
1141
1142
			/* Calculate the x-coordinate relative to the
			 * two columns of the Yes/No/All shortcuts in
			 * bottomwin. */
1143
		    int y = mouse_y - 1;
1144
1145
			/* Calculate the y-coordinate relative to the
			 * beginning of the Yes/No/All shortcuts in
1146
			 * bottomwin, i.e. with the sizes of topwin,
1147
1148
1149
			 * edit, and the first line of bottomwin
			 * subtracted out. */

1150
		    assert(0 <= x && x <= 1 && 0 <= y && y <= 1);
1151

1152
1153
1154
		    /* x == 0 means they clicked Yes or No.  y == 0
		     * means Yes or All. */
		    ok = -2 * x * y + x - y + 1;
1155

1156
1157
1158
		    if (ok == 2 && !all)
			ok = -2;
		}
1159
	}
1160
#endif /* !DISABLE_MOUSE */
1161
	else if (func == total_refresh) {
1162
1163
1164
	    total_redraw();
	    continue;
	} else {
1165
1166
1167
1168
1169
1170
1171
1172
		/* Look for the kbinput in the Yes, No and (optionally)
		 * All strings. */
		if (strchr(yesstr, kbinput) != NULL)
		    ok = 1;
		else if (strchr(nostr, kbinput) != NULL)
		    ok = 0;
		else if (all && strchr(allstr, kbinput) != NULL)
		    ok = 2;
1173
1174
1175
	}
    } while (ok == -2);

1176
    currmenu = oldmenu;
1177
1178
    return ok;
}