1/* $Id: prompt.c,v 1.75.2.1 2007/04/21 18:53:38 dolorous Exp $ */
2/**************************************************************************
3 *   prompt.c                                                             *
4 *                                                                        *
5 *   Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 Chris Allegretta    *
6 *   Copyright (C) 2005, 2006, 2007 David Lawrence Ramsey                 *
7 *   This program is free software; you can redistribute it and/or modify *
8 *   it under the terms of the GNU General Public License as published by *
9 *   the Free Software Foundation; either version 2, or (at your option)  *
10 *   any later version.                                                   *
11 *                                                                        *
12 *   This program is distributed in the hope that it will be useful, but  *
13 *   WITHOUT ANY WARRANTY; without even the implied warranty of           *
14 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU    *
15 *   General Public License for more details.                             *
16 *                                                                        *
17 *   You should have received a copy of the GNU General Public License    *
18 *   along with this program; if not, write to the Free Software          *
19 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA            *
20 *   02110-1301, USA.                                                     *
21 *                                                                        *
22 **************************************************************************/
23
24#include "proto.h"
25
26#include <stdio.h>
27#include <stdarg.h>
28#include <string.h>
29
30static char *prompt = NULL;
31	/* The prompt string used for statusbar questions. */
32static size_t statusbar_x = (size_t)-1;
33	/* The cursor position in answer. */
34static size_t statusbar_pww = (size_t)-1;
35	/* The place we want in answer. */
36static size_t old_statusbar_x = (size_t)-1;
37	/* The old cursor position in answer, if any. */
38static size_t old_pww = (size_t)-1;
39	/* The old place we want in answer, if any. */
40static bool reset_statusbar_x = FALSE;
41	/* Should we reset the cursor position at the statusbar
42	 * prompt? */
43
44/* Read in a character, interpret it as a shortcut or toggle if
45 * necessary, and return it.  Set meta_key to TRUE if the character is a
46 * meta sequence, set func_key to TRUE if the character is a function
47 * key, set s_or_t to TRUE if the character is a shortcut or toggle
48 * key, set ran_func to TRUE if we ran a function associated with a
49 * shortcut key, and set finished to TRUE if we're done after running
50 * or trying to run a function associated with a shortcut key.  If
51 * allow_funcs is FALSE, don't actually run any functions associated
52 * with shortcut keys.  refresh_func is the function we will call to
53 * refresh the edit window. */
54int do_statusbar_input(bool *meta_key, bool *func_key, bool *s_or_t,
55	bool *ran_func, bool *finished, bool allow_funcs, void
56	(*refresh_func)(void))
57{
58    int input;
59	/* The character we read in. */
60    static int *kbinput = NULL;
61	/* The input buffer. */
62    static size_t kbinput_len = 0;
63	/* The length of the input buffer. */
64    const shortcut *s;
65    bool have_shortcut;
66
67    *s_or_t = FALSE;
68    *ran_func = FALSE;
69    *finished = FALSE;
70
71    /* Read in a character. */
72    input = get_kbinput(bottomwin, meta_key, func_key);
73
74#ifndef DISABLE_MOUSE
75    if (allow_funcs) {
76	/* If we got a mouse click and it was on a shortcut, read in the
77	 * shortcut character. */
78	if (*func_key && input == KEY_MOUSE) {
79	    if (do_statusbar_mouse())
80		input = get_kbinput(bottomwin, meta_key, func_key);
81	    else {
82		*meta_key = FALSE;
83		*func_key = FALSE;
84		input = ERR;
85	    }
86	}
87    }
88#endif
89
90    /* Check for a shortcut in the current list. */
91    s = get_shortcut(currshortcut, &input, meta_key, func_key);
92
93    /* If we got a shortcut from the current list, or a "universal"
94     * statusbar prompt shortcut, set have_shortcut to TRUE. */
95    have_shortcut = (s != NULL || input == NANO_TAB_KEY || input ==
96	NANO_ENTER_KEY || input == NANO_REFRESH_KEY || input ==
97	NANO_HOME_KEY || input == NANO_END_KEY || input ==
98	NANO_BACK_KEY || input == NANO_FORWARD_KEY || input ==
99	NANO_BACKSPACE_KEY || input == NANO_DELETE_KEY || input ==
100	NANO_CUT_KEY ||
101#ifndef NANO_TINY
102	input == NANO_NEXTWORD_KEY ||
103#endif
104	(*meta_key && (
105#ifndef NANO_TINY
106	input == NANO_PREVWORD_KEY || input == NANO_BRACKET_KEY ||
107#endif
108	input == NANO_VERBATIM_KEY)));
109
110    /* Set s_or_t to TRUE if we got a shortcut. */
111    *s_or_t = have_shortcut;
112
113    /* If we got a non-high-bit control key, a meta key sequence, or a
114     * function key, and it's not a shortcut or toggle, throw it out. */
115    if (!*s_or_t) {
116	if (is_ascii_cntrl_char(input) || *meta_key || *func_key) {
117	    beep();
118	    *meta_key = FALSE;
119	    *func_key = FALSE;
120	    input = ERR;
121	}
122    }
123
124    if (allow_funcs) {
125	/* If we got a character, and it isn't a shortcut or toggle,
126	 * it's a normal text character.  Display the warning if we're
127	 * in view mode, or add the character to the input buffer if
128	 * we're not. */
129	if (input != ERR && !*s_or_t) {
130	    /* If we're using restricted mode, the filename isn't blank,
131	     * and we're at the "Write File" prompt, disable text
132	     * input. */
133	    if (!ISSET(RESTRICTED) || openfile->filename[0] == '\0' ||
134		currshortcut != writefile_list) {
135		kbinput_len++;
136		kbinput = (int *)nrealloc(kbinput, kbinput_len *
137			sizeof(int));
138		kbinput[kbinput_len - 1] = input;
139	    }
140	}
141
142	/* If we got a shortcut, or if there aren't any other characters
143	 * waiting after the one we read in, we need to display all the
144	 * characters in the input buffer if it isn't empty. */
145	 if (*s_or_t || get_key_buffer_len() == 0) {
146	    if (kbinput != NULL) {
147		/* Display all the characters in the input buffer at
148		 * once, filtering out control characters. */
149		char *output = charalloc(kbinput_len + 1);
150		size_t i;
151		bool got_enter;
152			/* Whether we got the Enter key. */
153
154		for (i = 0; i < kbinput_len; i++)
155		    output[i] = (char)kbinput[i];
156		output[i] = '\0';
157
158		do_statusbar_output(output, kbinput_len, &got_enter,
159			FALSE);
160
161		free(output);
162
163		/* Empty the input buffer. */
164		kbinput_len = 0;
165		free(kbinput);
166		kbinput = NULL;
167	    }
168	}
169
170	if (have_shortcut) {
171	    switch (input) {
172		/* Handle the "universal" statusbar prompt shortcuts. */
173		case NANO_TAB_KEY:
174		case NANO_ENTER_KEY:
175		    break;
176		case NANO_REFRESH_KEY:
177		    total_statusbar_refresh(refresh_func);
178		    break;
179		case NANO_CUT_KEY:
180		    /* If we're using restricted mode, the filename
181		     * isn't blank, and we're at the "Write File"
182		     * prompt, disable Cut. */
183		    if (!ISSET(RESTRICTED) || openfile->filename[0] ==
184			'\0' || currshortcut != writefile_list)
185			do_statusbar_cut_text();
186		    break;
187		case NANO_FORWARD_KEY:
188		    do_statusbar_right();
189		    break;
190		case NANO_BACK_KEY:
191		    do_statusbar_left();
192		    break;
193#ifndef NANO_TINY
194		case NANO_NEXTWORD_KEY:
195		    do_statusbar_next_word(FALSE);
196		    break;
197		case NANO_PREVWORD_KEY:
198		    if (*meta_key)
199			do_statusbar_prev_word(FALSE);
200		    break;
201#endif
202		case NANO_HOME_KEY:
203		    do_statusbar_home();
204		    break;
205		case NANO_END_KEY:
206		    do_statusbar_end();
207		    break;
208#ifndef NANO_TINY
209		case NANO_BRACKET_KEY:
210		    if (*meta_key)
211			do_statusbar_find_bracket();
212		    break;
213#endif
214		case NANO_VERBATIM_KEY:
215		    if (*meta_key) {
216			/* If we're using restricted mode, the filename
217			 * isn't blank, and we're at the "Write File"
218			 * prompt, disable verbatim input. */
219			if (!ISSET(RESTRICTED) ||
220				openfile->filename[0] == '\0' ||
221				currshortcut != writefile_list) {
222			    bool got_enter;
223				/* Whether we got the Enter key. */
224
225			    do_statusbar_verbatim_input(&got_enter);
226
227			    /* If we got the Enter key, remove it from
228			     * the input buffer, set input to the key
229			     * value for Enter, and set finished to TRUE
230			     * to indicate that we're done. */
231			    if (got_enter) {
232				get_input(NULL, 1);
233				input = NANO_ENTER_KEY;
234				*finished = TRUE;
235			    }
236			}
237		    }
238		    break;
239		case NANO_DELETE_KEY:
240		    /* If we're using restricted mode, the filename
241		     * isn't blank, and we're at the "Write File"
242		     * prompt, disable Delete. */
243		    if (!ISSET(RESTRICTED) || openfile->filename[0] ==
244			'\0' || currshortcut != writefile_list)
245			do_statusbar_delete();
246		    break;
247		case NANO_BACKSPACE_KEY:
248		    /* If we're using restricted mode, the filename
249		     * isn't blank, and we're at the "Write File"
250		     * prompt, disable Backspace. */
251		    if (!ISSET(RESTRICTED) || openfile->filename[0] ==
252			'\0' || currshortcut != writefile_list)
253			do_statusbar_backspace();
254		    break;
255		/* Handle the normal statusbar prompt shortcuts, setting
256		 * ran_func to TRUE if we try to run their associated
257		 * functions and setting finished to TRUE to indicate
258		 * that we're done after running or trying to run their
259		 * associated functions. */
260		default:
261		    if (s->func != NULL) {
262			*ran_func = TRUE;
263			if (!ISSET(VIEW_MODE) || s->viewok)
264			    s->func();
265		    }
266		    *finished = TRUE;
267	    }
268	}
269    }
270
271    return input;
272}
273
274#ifndef DISABLE_MOUSE
275/* Handle a mouse click on the statusbar prompt or the shortcut list. */
276bool do_statusbar_mouse(void)
277{
278    int mouse_x, mouse_y;
279    bool retval = get_mouseinput(&mouse_x, &mouse_y, TRUE);
280
281    if (!retval) {
282	/* We can click in the statusbar window text to move the
283	 * cursor. */
284	if (wenclose(bottomwin, mouse_y, mouse_x)) {
285	    size_t start_col;
286
287	    assert(prompt != NULL);
288
289	    start_col = strlenpt(prompt) + 1;
290
291	    /* Subtract out the sizes of topwin and edit. */
292	    mouse_y -= (2 - no_more_space()) + editwinrows;
293
294	    /* Move to where the click occurred. */
295	    if (mouse_x > start_col && mouse_y == 0) {
296		size_t pww_save = statusbar_pww;
297
298		statusbar_x = actual_x(answer,
299			get_statusbar_page_start(start_col, start_col +
300			statusbar_xplustabs()) + mouse_x - start_col -
301			1);
302		statusbar_pww = statusbar_xplustabs();
303
304		if (need_statusbar_horizontal_update(pww_save))
305		    update_statusbar_line(answer, statusbar_x);
306	    }
307	}
308    }
309
310    return retval;
311}
312#endif
313
314/* The user typed output_len multibyte characters.  Add them to the
315 * statusbar prompt, setting got_enter to TRUE if we get a newline, and
316 * filtering out all ASCII control characters if allow_cntrls is
317 * TRUE. */
318void do_statusbar_output(char *output, size_t output_len, bool
319	*got_enter, bool allow_cntrls)
320{
321    size_t answer_len, i = 0;
322    char *char_buf = charalloc(mb_cur_max());
323    int char_buf_len;
324
325    assert(answer != NULL);
326
327    answer_len = strlen(answer);
328    *got_enter = FALSE;
329
330    while (i < output_len) {
331	/* If allow_cntrls is TRUE, convert nulls and newlines
332	 * properly. */
333	if (allow_cntrls) {
334	    /* Null to newline, if needed. */
335	    if (output[i] == '\0')
336		output[i] = '\n';
337	    /* Newline to Enter, if needed. */
338	    else if (output[i] == '\n') {
339		/* Set got_enter to TRUE to indicate that we got the
340		 * Enter key, put back the rest of the characters in
341		 * output so that they can be parsed and output again,
342		 * and get out. */
343		*got_enter = TRUE;
344		unparse_kbinput(output + i, output_len - i);
345		return;
346	    }
347	}
348
349	/* Interpret the next multibyte character. */
350	char_buf_len = parse_mbchar(output + i, char_buf, NULL);
351
352	i += char_buf_len;
353
354	/* If allow_cntrls is FALSE, filter out an ASCII control
355	 * character. */
356	if (!allow_cntrls && is_ascii_cntrl_char(*(output + i -
357		char_buf_len)))
358	    continue;
359
360	/* More dangerousness fun =) */
361	answer = charealloc(answer, answer_len + (char_buf_len * 2));
362
363	assert(statusbar_x <= answer_len);
364
365	charmove(answer + statusbar_x + char_buf_len,
366		answer + statusbar_x, answer_len - statusbar_x +
367		char_buf_len);
368	strncpy(answer + statusbar_x, char_buf, char_buf_len);
369	answer_len += char_buf_len;
370
371	statusbar_x += char_buf_len;
372    }
373
374    free(char_buf);
375
376    statusbar_pww = statusbar_xplustabs();
377
378    update_statusbar_line(answer, statusbar_x);
379}
380
381/* Move to the beginning of the prompt text.  If the SMART_HOME flag is
382 * set, move to the first non-whitespace character of the prompt text if
383 * we're not already there, or to the beginning of the prompt text if we
384 * are. */
385void do_statusbar_home(void)
386{
387    size_t pww_save = statusbar_pww;
388
389#ifndef NANO_TINY
390    if (ISSET(SMART_HOME)) {
391	size_t statusbar_x_save = statusbar_x;
392
393	statusbar_x = indent_length(answer);
394
395	if (statusbar_x == statusbar_x_save ||
396		statusbar_x == strlen(answer))
397	    statusbar_x = 0;
398
399	statusbar_pww = statusbar_xplustabs();
400    } else {
401#endif
402	statusbar_x = 0;
403	statusbar_pww = statusbar_xplustabs();
404#ifndef NANO_TINY
405    }
406#endif
407
408    if (need_statusbar_horizontal_update(pww_save))
409	update_statusbar_line(answer, statusbar_x);
410}
411
412/* Move to the end of the prompt text. */
413void do_statusbar_end(void)
414{
415    size_t pww_save = statusbar_pww;
416
417    statusbar_x = strlen(answer);
418    statusbar_pww = statusbar_xplustabs();
419
420    if (need_statusbar_horizontal_update(pww_save))
421	update_statusbar_line(answer, statusbar_x);
422}
423
424/* Move left one character. */
425void do_statusbar_left(void)
426{
427    if (statusbar_x > 0) {
428	size_t pww_save = statusbar_pww;
429
430	statusbar_x = move_mbleft(answer, statusbar_x);
431	statusbar_pww = statusbar_xplustabs();
432
433	if (need_statusbar_horizontal_update(pww_save))
434	    update_statusbar_line(answer, statusbar_x);
435    }
436}
437
438/* Move right one character. */
439void do_statusbar_right(void)
440{
441    if (statusbar_x < strlen(answer)) {
442	size_t pww_save = statusbar_pww;
443
444	statusbar_x = move_mbright(answer, statusbar_x);
445	statusbar_pww = statusbar_xplustabs();
446
447	if (need_statusbar_horizontal_update(pww_save))
448	    update_statusbar_line(answer, statusbar_x);
449    }
450}
451
452/* Backspace over one character. */
453void do_statusbar_backspace(void)
454{
455    if (statusbar_x > 0) {
456	do_statusbar_left();
457	do_statusbar_delete();
458    }
459}
460
461/* Delete one character. */
462void do_statusbar_delete(void)
463{
464    statusbar_pww = statusbar_xplustabs();
465
466    if (answer[statusbar_x] != '\0') {
467	int char_buf_len = parse_mbchar(answer + statusbar_x, NULL,
468		NULL);
469	size_t line_len = strlen(answer + statusbar_x);
470
471	assert(statusbar_x < strlen(answer));
472
473	charmove(answer + statusbar_x, answer + statusbar_x +
474		char_buf_len, strlen(answer) - statusbar_x -
475		char_buf_len + 1);
476
477	null_at(&answer, statusbar_x + line_len - char_buf_len);
478
479	update_statusbar_line(answer, statusbar_x);
480    }
481}
482
483/* Move text from the prompt into oblivion. */
484void do_statusbar_cut_text(void)
485{
486    assert(answer != NULL);
487
488#ifndef NANO_TINY
489    if (ISSET(CUT_TO_END))
490	null_at(&answer, statusbar_x);
491    else {
492#endif
493	null_at(&answer, 0);
494	statusbar_x = 0;
495	statusbar_pww = statusbar_xplustabs();
496#ifndef NANO_TINY
497    }
498#endif
499
500    update_statusbar_line(answer, statusbar_x);
501}
502
503#ifndef NANO_TINY
504/* Move to the next word in the prompt text.  If allow_punct is TRUE,
505 * treat punctuation as part of a word.  Return TRUE if we started on a
506 * word, and FALSE otherwise. */
507bool do_statusbar_next_word(bool allow_punct)
508{
509    size_t pww_save = statusbar_pww;
510    char *char_mb;
511    int char_mb_len;
512    bool end_line = FALSE, started_on_word = FALSE;
513
514    assert(answer != NULL);
515
516    char_mb = charalloc(mb_cur_max());
517
518    /* Move forward until we find the character after the last letter of
519     * the current word. */
520    while (!end_line) {
521	char_mb_len = parse_mbchar(answer + statusbar_x, char_mb, NULL);
522
523	/* If we've found it, stop moving forward through the current
524	 * line. */
525	if (!is_word_mbchar(char_mb, allow_punct))
526	    break;
527
528	/* If we haven't found it, then we've started on a word, so set
529	 * started_on_word to TRUE. */
530	started_on_word = TRUE;
531
532	if (answer[statusbar_x] == '\0')
533	    end_line = TRUE;
534	else
535	    statusbar_x += char_mb_len;
536    }
537
538    /* Move forward until we find the first letter of the next word. */
539    if (answer[statusbar_x] == '\0')
540	end_line = TRUE;
541    else
542	statusbar_x += char_mb_len;
543
544    while (!end_line) {
545	char_mb_len = parse_mbchar(answer + statusbar_x, char_mb, NULL);
546
547	/* If we've found it, stop moving forward through the current
548	 * line. */
549	if (is_word_mbchar(char_mb, allow_punct))
550	    break;
551
552	if (answer[statusbar_x] == '\0')
553	    end_line = TRUE;
554	else
555	    statusbar_x += char_mb_len;
556    }
557
558    free(char_mb);
559
560    statusbar_pww = statusbar_xplustabs();
561
562    if (need_statusbar_horizontal_update(pww_save))
563	update_statusbar_line(answer, statusbar_x);
564
565    /* Return whether we started on a word. */
566    return started_on_word;
567}
568
569/* Move to the previous word in the prompt text.  If allow_punct is
570 * TRUE, treat punctuation as part of a word.  Return TRUE if we started
571 * on a word, and FALSE otherwise. */
572bool do_statusbar_prev_word(bool allow_punct)
573{
574    size_t pww_save = statusbar_pww;
575    char *char_mb;
576    int char_mb_len;
577    bool begin_line = FALSE, started_on_word = FALSE;
578
579    assert(answer != NULL);
580
581    char_mb = charalloc(mb_cur_max());
582
583    /* Move backward until we find the character before the first letter
584     * of the current word. */
585    while (!begin_line) {
586	char_mb_len = parse_mbchar(answer + statusbar_x, char_mb, NULL);
587
588	/* If we've found it, stop moving backward through the current
589	 * line. */
590	if (!is_word_mbchar(char_mb, allow_punct))
591	    break;
592
593	/* If we haven't found it, then we've started on a word, so set
594	 * started_on_word to TRUE. */
595	started_on_word = TRUE;
596
597	if (statusbar_x == 0)
598	    begin_line = TRUE;
599	else
600	    statusbar_x = move_mbleft(answer, statusbar_x);
601    }
602
603    /* Move backward until we find the last letter of the previous
604     * word. */
605    if (statusbar_x == 0)
606	begin_line = TRUE;
607    else
608	statusbar_x = move_mbleft(answer, statusbar_x);
609
610    while (!begin_line) {
611	char_mb_len = parse_mbchar(answer + statusbar_x, char_mb, NULL);
612
613	/* If we've found it, stop moving backward through the current
614	 * line. */
615	if (is_word_mbchar(char_mb, allow_punct))
616	    break;
617
618	if (statusbar_x == 0)
619	    begin_line = TRUE;
620	else
621	    statusbar_x = move_mbleft(answer, statusbar_x);
622    }
623
624    /* If we've found it, move backward until we find the character
625     * before the first letter of the previous word. */
626    if (!begin_line) {
627	if (statusbar_x == 0)
628	    begin_line = TRUE;
629	else
630	    statusbar_x = move_mbleft(answer, statusbar_x);
631
632	while (!begin_line) {
633	    char_mb_len = parse_mbchar(answer + statusbar_x, char_mb,
634		NULL);
635
636	    /* If we've found it, stop moving backward through the
637	     * current line. */
638	    if (!is_word_mbchar(char_mb, allow_punct))
639		break;
640
641	    if (statusbar_x == 0)
642		begin_line = TRUE;
643	    else
644		statusbar_x = move_mbleft(answer, statusbar_x);
645	}
646
647	/* If we've found it, move forward to the first letter of the
648	 * previous word. */
649	if (!begin_line)
650	    statusbar_x += char_mb_len;
651    }
652
653    free(char_mb);
654
655    statusbar_pww = statusbar_xplustabs();
656
657    if (need_statusbar_horizontal_update(pww_save))
658	update_statusbar_line(answer, statusbar_x);
659
660    /* Return whether we started on a word. */
661    return started_on_word;
662}
663#endif /* !NANO_TINY */
664
665/* Get verbatim input.  Set got_enter to TRUE if we got the Enter key as
666 * part of the verbatim input. */
667void do_statusbar_verbatim_input(bool *got_enter)
668{
669    int *kbinput;
670    size_t kbinput_len, i;
671    char *output;
672
673    *got_enter = FALSE;
674
675    /* Read in all the verbatim characters. */
676    kbinput = get_verbatim_kbinput(bottomwin, &kbinput_len);
677
678    /* Display all the verbatim characters at once, not filtering out
679     * control characters. */
680    output = charalloc(kbinput_len + 1);
681
682    for (i = 0; i < kbinput_len; i++)
683	output[i] = (char)kbinput[i];
684    output[i] = '\0';
685
686    do_statusbar_output(output, kbinput_len, got_enter, TRUE);
687
688    free(output);
689}
690
691#ifndef NANO_TINY
692/* Search for a match to one of the two characters in bracket_set.  If
693 * reverse is TRUE, search backwards for the leftmost bracket.
694 * Otherwise, search forwards for the rightmost bracket.  Return TRUE if
695 * we found a match, and FALSE otherwise. */
696bool find_statusbar_bracket_match(bool reverse, const char
697	*bracket_set)
698{
699    const char *rev_start = NULL, *found = NULL;
700
701    assert(mbstrlen(bracket_set) == 2);
702
703    /* rev_start might end up 1 character before the start or after the
704     * end of the line.  This won't be a problem because we'll skip over
705     * it below in that case. */
706    rev_start = reverse ? answer + (statusbar_x - 1) : answer +
707	(statusbar_x + 1);
708
709    while (TRUE) {
710	/* Look for either of the two characters in bracket_set.
711	 * rev_start can be 1 character before the start or after the
712	 * end of the line.  In either case, just act as though no match
713	 * is found. */
714	found = ((rev_start > answer && *(rev_start - 1) == '\0') ||
715		rev_start < answer) ? NULL : (reverse ?
716		mbrevstrpbrk(answer, bracket_set, rev_start) :
717		mbstrpbrk(rev_start, bracket_set));
718
719	/* We've found a potential match. */
720	if (found != NULL)
721	    break;
722
723	/* We've reached the start or end of the statusbar text, so
724	 * get out. */
725	return FALSE;
726    }
727
728    /* We've definitely found something. */
729    statusbar_x = found - answer;
730    statusbar_pww = statusbar_xplustabs();
731
732    return TRUE;
733}
734
735/* Search for a match to the bracket at the current cursor position, if
736 * there is one. */
737void do_statusbar_find_bracket(void)
738{
739    size_t statusbar_x_save, pww_save;
740    const char *ch;
741	/* The location in matchbrackets of the bracket at the current
742	 * cursor position. */
743    int ch_len;
744	/* The length of ch in bytes. */
745    const char *wanted_ch;
746	/* The location in matchbrackets of the bracket complementing
747	 * the bracket at the current cursor position. */
748    int wanted_ch_len;
749	/* The length of wanted_ch in bytes. */
750    char *bracket_set;
751	/* The pair of characters in ch and wanted_ch. */
752    size_t i;
753	/* Generic loop variable. */
754    size_t matchhalf;
755	/* The number of single-byte characters in one half of
756	 * matchbrackets. */
757    size_t mbmatchhalf;
758	/* The number of multibyte characters in one half of
759	 * matchbrackets. */
760    size_t count = 1;
761	/* The initial bracket count. */
762    bool reverse;
763	/* The direction we search. */
764    char *found_ch;
765	/* The character we find. */
766
767    assert(mbstrlen(matchbrackets) % 2 == 0);
768
769    ch = answer + statusbar_x;
770
771    if (ch == '\0' || (ch = mbstrchr(matchbrackets, ch)) == NULL)
772	return;
773
774    /* Save where we are. */
775    statusbar_x_save = statusbar_x;
776    pww_save = statusbar_pww;
777
778    /* If we're on an opening bracket, which must be in the first half
779     * of matchbrackets, we want to search forwards for a closing
780     * bracket.  If we're on a closing bracket, which must be in the
781     * second half of matchbrackets, we want to search backwards for an
782     * opening bracket. */
783    matchhalf = 0;
784    mbmatchhalf = mbstrlen(matchbrackets) / 2;
785
786    for (i = 0; i < mbmatchhalf; i++)
787	matchhalf += parse_mbchar(matchbrackets + matchhalf, NULL,
788		NULL);
789
790    reverse = ((ch - matchbrackets) >= matchhalf);
791
792    /* If we're on an opening bracket, set wanted_ch to the character
793     * that's matchhalf characters after ch.  If we're on a closing
794     * bracket, set wanted_ch to the character that's matchhalf
795     * characters before ch. */
796    wanted_ch = ch;
797
798    while (mbmatchhalf > 0) {
799	if (reverse)
800	    wanted_ch = matchbrackets + move_mbleft(matchbrackets,
801		wanted_ch - matchbrackets);
802	else
803	    wanted_ch += move_mbright(wanted_ch, 0);
804
805	mbmatchhalf--;
806    }
807
808    ch_len = parse_mbchar(ch, NULL, NULL);
809    wanted_ch_len = parse_mbchar(wanted_ch, NULL, NULL);
810
811    /* Fill bracket_set in with the values of ch and wanted_ch. */
812    bracket_set = charalloc((mb_cur_max() * 2) + 1);
813    strncpy(bracket_set, ch, ch_len);
814    strncpy(bracket_set + ch_len, wanted_ch, wanted_ch_len);
815    null_at(&bracket_set, ch_len + wanted_ch_len);
816
817    found_ch = charalloc(mb_cur_max() + 1);
818
819    while (TRUE) {
820	if (find_statusbar_bracket_match(reverse, bracket_set)) {
821	    /* If we found an identical bracket, increment count.  If we
822	     * found a complementary bracket, decrement it. */
823	    parse_mbchar(answer + statusbar_x, found_ch, NULL);
824	    count += (strncmp(found_ch, ch, ch_len) == 0) ? 1 : -1;
825
826	    /* If count is zero, we've found a matching bracket.  Update
827	     * the statusbar prompt and get out. */
828	    if (count == 0) {
829		if (need_statusbar_horizontal_update(pww_save))
830		    update_statusbar_line(answer, statusbar_x);
831		break;
832	    }
833	} else {
834	    /* We didn't find either an opening or closing bracket.
835	     * Restore where we were, and get out. */
836	    statusbar_x = statusbar_x_save;
837	    statusbar_pww = pww_save;
838	    break;
839	}
840    }
841
842    /* Clean up. */
843    free(bracket_set);
844    free(found_ch);
845}
846#endif /* !NANO_TINY */
847
848/* Return the placewewant associated with statusbar_x, i.e. the
849 * zero-based column position of the cursor.  The value will be no
850 * smaller than statusbar_x. */
851size_t statusbar_xplustabs(void)
852{
853    return strnlenpt(answer, statusbar_x);
854}
855
856/* nano scrolls horizontally within a line in chunks.  This function
857 * returns the column number of the first character displayed in the
858 * statusbar prompt when the cursor is at the given column with the
859 * prompt ending at start_col.  Note that (0 <= column -
860 * get_statusbar_page_start(column) < COLS). */
861size_t get_statusbar_page_start(size_t start_col, size_t column)
862{
863    if (column == start_col || column < COLS - 1)
864	return 0;
865    else
866	return column - start_col - (column - start_col) % (COLS -
867		start_col - 1);
868}
869
870/* Put the cursor in the statusbar prompt at statusbar_x. */
871void reset_statusbar_cursor(void)
872{
873    size_t start_col = strlenpt(prompt) + 1;
874    size_t xpt = statusbar_xplustabs();
875
876    wmove(bottomwin, 0, start_col + 1 + xpt -
877	get_statusbar_page_start(start_col, start_col + xpt));
878}
879
880/* Repaint the statusbar when getting a character in
881 * get_prompt_string().  The statusbar text line will be displayed
882 * starting with curranswer[index]. */
883void update_statusbar_line(const char *curranswer, size_t index)
884{
885    size_t start_col, page_start;
886    char *expanded;
887
888    assert(prompt != NULL && index <= strlen(curranswer));
889
890    start_col = strlenpt(prompt) + 1;
891    index = strnlenpt(curranswer, index);
892    page_start = get_statusbar_page_start(start_col, start_col + index);
893
894    wattron(bottomwin, reverse_attr);
895
896    blank_statusbar();
897
898    mvwaddnstr(bottomwin, 0, 0, prompt, actual_x(prompt, COLS - 2));
899    waddch(bottomwin, ':');
900    waddch(bottomwin, (page_start == 0) ? ' ' : '$');
901
902    expanded = display_string(curranswer, page_start, COLS - start_col -
903	1, FALSE);
904    waddstr(bottomwin, expanded);
905    free(expanded);
906
907    reset_statusbar_cursor();
908
909    wattroff(bottomwin, reverse_attr);
910
911    wnoutrefresh(bottomwin);
912}
913
914/* Return TRUE if we need an update after moving horizontally, and FALSE
915 * otherwise.  We need one if pww_save and statusbar_pww are on
916 * different pages. */
917bool need_statusbar_horizontal_update(size_t pww_save)
918{
919    size_t start_col = strlenpt(prompt) + 1;
920
921    return get_statusbar_page_start(start_col, start_col + pww_save) !=
922	get_statusbar_page_start(start_col, start_col + statusbar_pww);
923}
924
925/* Unconditionally redraw the entire screen, and then refresh it using
926 * refresh_func(). */
927void total_statusbar_refresh(void (*refresh_func)(void))
928{
929    total_redraw();
930    refresh_func();
931}
932
933/* Get a string of input at the statusbar prompt.  This should only be
934 * called from do_prompt(). */
935int get_prompt_string(bool allow_tabs,
936#ifndef DISABLE_TABCOMP
937	bool allow_files,
938#endif
939	const char *curranswer,
940#ifndef NANO_TINY
941	filestruct **history_list,
942#endif
943	void (*refresh_func)(void), const shortcut *s
944#ifndef DISABLE_TABCOMP
945	, bool *list
946#endif
947	)
948{
949    int kbinput = ERR;
950    bool meta_key, func_key, s_or_t, ran_func, finished;
951    size_t curranswer_len;
952#ifndef DISABLE_TABCOMP
953    bool tabbed = FALSE;
954	/* Whether we've pressed Tab. */
955#endif
956#ifndef NANO_TINY
957    char *history = NULL;
958	/* The current history string. */
959    char *magichistory = NULL;
960	/* The temporary string typed at the bottom of the history, if
961	 * any. */
962#ifndef DISABLE_TABCOMP
963    int last_kbinput = ERR;
964	/* The key we pressed before the current key. */
965    size_t complete_len = 0;
966	/* The length of the original string that we're trying to
967	 * tab complete, if any. */
968#endif
969#endif /* !NANO_TINY */
970
971    answer = mallocstrcpy(answer, curranswer);
972    curranswer_len = strlen(answer);
973
974    /* If reset_statusbar_x is TRUE, restore statusbar_x and
975     * statusbar_pww to what they were before this prompt.  Then, if
976     * statusbar_x is uninitialized or past the end of curranswer, put
977     * statusbar_x at the end of the string and update statusbar_pww
978     * based on it.  We do these things so that the cursor position
979     * stays at the right place if a prompt-changing toggle is pressed,
980     * or if this prompt was started from another prompt and we cancel
981     * out of it. */
982    if (reset_statusbar_x) {
983	statusbar_x = old_statusbar_x;
984	statusbar_pww = old_pww;
985    }
986
987    if (statusbar_x == (size_t)-1 || statusbar_x > curranswer_len) {
988	statusbar_x = curranswer_len;
989	statusbar_pww = statusbar_xplustabs();
990    }
991
992    currshortcut = s;
993
994    update_statusbar_line(answer, statusbar_x);
995
996    /* Refresh the edit window and the statusbar before getting
997     * input. */
998    wnoutrefresh(edit);
999    wnoutrefresh(bottomwin);
1000
1001    /* If we're using restricted mode, we aren't allowed to change the
1002     * name of the current file once it has one, because that would
1003     * allow writing to files not specified on the command line.  In
1004     * this case, disable all keys that would change the text if the
1005     * filename isn't blank and we're at the "Write File" prompt. */
1006    while ((kbinput = do_statusbar_input(&meta_key, &func_key, &s_or_t,
1007	&ran_func, &finished, TRUE, refresh_func)) != NANO_CANCEL_KEY &&
1008	kbinput != NANO_ENTER_KEY) {
1009	assert(statusbar_x <= strlen(answer));
1010
1011#ifndef DISABLE_TABCOMP
1012	if (kbinput != NANO_TAB_KEY)
1013	    tabbed = FALSE;
1014#endif
1015
1016	switch (kbinput) {
1017#ifndef DISABLE_TABCOMP
1018#ifndef NANO_TINY
1019	    case NANO_TAB_KEY:
1020		if (history_list != NULL) {
1021		    if (last_kbinput != NANO_TAB_KEY)
1022			complete_len = strlen(answer);
1023
1024		    if (complete_len > 0) {
1025			answer = mallocstrcpy(answer,
1026				get_history_completion(history_list,
1027				answer, complete_len));
1028			statusbar_x = strlen(answer);
1029		    }
1030		} else
1031#endif /* !NANO_TINY */
1032		if (allow_tabs)
1033		    answer = input_tab(answer, allow_files,
1034			&statusbar_x, &tabbed, refresh_func, list);
1035
1036		update_statusbar_line(answer, statusbar_x);
1037		break;
1038#endif /* !DISABLE_TABCOMP */
1039#ifndef NANO_TINY
1040	    case NANO_PREVLINE_KEY:
1041		if (history_list != NULL) {
1042		    /* If we're scrolling up at the bottom of the
1043		     * history list and answer isn't blank, save answer
1044		     * in magichistory. */
1045		    if ((*history_list)->next == NULL &&
1046			answer[0] != '\0')
1047			magichistory = mallocstrcpy(magichistory,
1048				answer);
1049
1050		    /* Get the older search from the history list and
1051		     * save it in answer.  If there is no older search,
1052		     * don't do anything. */
1053		    if ((history =
1054			get_history_older(history_list)) != NULL) {
1055			answer = mallocstrcpy(answer, history);
1056			statusbar_x = strlen(answer);
1057		    }
1058
1059		    update_statusbar_line(answer, statusbar_x);
1060
1061		    /* This key has a shortcut list entry when it's used
1062		     * to move to an older search, which means that
1063		     * finished has been set to TRUE.  Set it back to
1064		     * FALSE here, so that we aren't kicked out of the
1065		     * statusbar prompt. */
1066		    finished = FALSE;
1067		}
1068		break;
1069	    case NANO_NEXTLINE_KEY:
1070		if (history_list != NULL) {
1071		    /* Get the newer search from the history list and
1072		     * save it in answer.  If there is no newer search,
1073		     * don't do anything. */
1074		    if ((history =
1075			get_history_newer(history_list)) != NULL) {
1076			answer = mallocstrcpy(answer, history);
1077			statusbar_x = strlen(answer);
1078		    }
1079
1080		    /* If, after scrolling down, we're at the bottom of
1081		     * the history list, answer is blank, and
1082		     * magichistory is set, save magichistory in
1083		     * answer. */
1084		    if ((*history_list)->next == NULL &&
1085			answer[0] == '\0' && magichistory != NULL) {
1086			answer = mallocstrcpy(answer, magichistory);
1087			statusbar_x = strlen(answer);
1088		    }
1089
1090		    update_statusbar_line(answer, statusbar_x);
1091
1092		    /* This key has a shortcut list entry when it's used
1093		     * to move to a newer search, which means that
1094		     * finished has been set to TRUE.  Set it back to
1095		     * FALSE here, so that we aren't kicked out of the
1096		     * statusbar prompt. */
1097		    finished = FALSE;
1098		}
1099		break;
1100#endif /* !NANO_TINY */
1101	    case NANO_HELP_KEY:
1102		update_statusbar_line(answer, statusbar_x);
1103
1104		/* This key has a shortcut list entry when it's used to
1105		 * go to the help browser or display a message
1106		 * indicating that help is disabled, which means that
1107		 * finished has been set to TRUE.  Set it back to FALSE
1108		 * here, so that we aren't kicked out of the statusbar
1109		 * prompt. */
1110		finished = FALSE;
1111		break;
1112	}
1113
1114	/* If we have a shortcut with an associated function, break out
1115	 * if we're finished after running or trying to run the
1116	 * function. */
1117	if (finished)
1118	    break;
1119
1120#if !defined(NANO_TINY) && !defined(DISABLE_TABCOMP)
1121	last_kbinput = kbinput;
1122#endif
1123
1124	reset_statusbar_cursor();
1125    }
1126
1127#ifndef NANO_TINY
1128    /* Set the current position in the history list to the bottom and
1129     * free magichistory, if we need to. */
1130    if (history_list != NULL) {
1131	history_reset(*history_list);
1132
1133	if (magichistory != NULL)
1134	    free(magichistory);
1135    }
1136#endif
1137
1138    /* We've finished putting in an answer or run a normal shortcut's
1139     * associated function, so reset statusbar_x and statusbar_pww.  If
1140     * we've finished putting in an answer, reset the statusbar cursor
1141     * position too. */
1142    if (kbinput == NANO_CANCEL_KEY || kbinput == NANO_ENTER_KEY ||
1143	ran_func) {
1144	statusbar_x = old_statusbar_x;
1145	statusbar_pww = old_pww;
1146
1147	if (!ran_func)
1148	    reset_statusbar_x = TRUE;
1149    /* Otherwise, we're still putting in an answer or a shortcut with
1150     * an associated function, so leave the statusbar cursor position
1151     * alone. */
1152    } else
1153	reset_statusbar_x = FALSE;
1154
1155    return kbinput;
1156}
1157
1158/* Ask a question on the statusbar.  The prompt will be stored in the
1159 * static prompt, which should be NULL initially, and the answer will be
1160 * stored in the answer global.  Returns -1 on aborted enter, -2 on a
1161 * blank string, and 0 otherwise, the valid shortcut key caught.
1162 * curranswer is any editable text that we want to put up by default,
1163 * and refresh_func is the function we want to call to refresh the edit
1164 * window.
1165 *
1166 * The allow_tabs parameter indicates whether we should allow tabs to be
1167 * interpreted.  The allow_files parameter indicates whether we should
1168 * allow all files (as opposed to just directories) to be tab
1169 * completed. */
1170int do_prompt(bool allow_tabs,
1171#ifndef DISABLE_TABCOMP
1172	bool allow_files,
1173#endif
1174	const shortcut *s, const char *curranswer,
1175#ifndef NANO_TINY
1176	filestruct **history_list,
1177#endif
1178	void (*refresh_func)(void), const char *msg, ...)
1179{
1180    va_list ap;
1181    int retval;
1182#ifndef DISABLE_TABCOMP
1183    bool list = FALSE;
1184#endif
1185
1186    /* prompt has been freed and set to NULL unless the user resized
1187     * while at the statusbar prompt. */
1188    if (prompt != NULL)
1189	free(prompt);
1190
1191    prompt = charalloc(((COLS - 4) * mb_cur_max()) + 1);
1192
1193    bottombars(s);
1194
1195    va_start(ap, msg);
1196    vsnprintf(prompt, (COLS - 4) * mb_cur_max(), msg, ap);
1197    va_end(ap);
1198    null_at(&prompt, actual_x(prompt, COLS - 4));
1199
1200    retval = get_prompt_string(allow_tabs,
1201#ifndef DISABLE_TABCOMP
1202	allow_files,
1203#endif
1204	curranswer,
1205#ifndef NANO_TINY
1206	history_list,
1207#endif
1208	refresh_func, s
1209#ifndef DISABLE_TABCOMP
1210	, &list
1211#endif
1212	);
1213
1214    free(prompt);
1215    prompt = NULL;
1216
1217    /* We're done with the prompt, so save the statusbar cursor
1218     * position. */
1219    old_statusbar_x = statusbar_x;
1220    old_pww = statusbar_pww;
1221
1222    /* If we left the prompt via Cancel or Enter, set the return value
1223     * properly. */
1224    switch (retval) {
1225	case NANO_CANCEL_KEY:
1226	    retval = -1;
1227	    break;
1228	case NANO_ENTER_KEY:
1229	    retval = (answer[0] == '\0') ? -2 : 0;
1230	    break;
1231    }
1232
1233    blank_statusbar();
1234    wnoutrefresh(bottomwin);
1235
1236#ifdef DEBUG
1237    fprintf(stderr, "answer = \"%s\"\n", answer);
1238#endif
1239
1240#ifndef DISABLE_TABCOMP
1241    /* If we've done tab completion, there might be a list of filename
1242     * matches on the edit window at this point.  Make sure that they're
1243     * cleared off. */
1244    if (list)
1245	refresh_func();
1246#endif
1247
1248    return retval;
1249}
1250
1251/* This function forces a reset of the statusbar cursor position.  It
1252 * should be called when we get out of all statusbar prompts. */
1253void do_prompt_abort(void)
1254{
1255    /* Uninitialize the old cursor position in answer. */
1256    old_statusbar_x = (size_t)-1;
1257    old_pww = (size_t)-1;
1258
1259    reset_statusbar_x = TRUE;
1260}
1261
1262/* Ask a simple Yes/No (and optionally All) question, specified in msg,
1263 * on the statusbar.  Return 1 for Yes, 0 for No, 2 for All (if all is
1264 * TRUE when passed in), and -1 for Cancel. */
1265int do_yesno_prompt(bool all, const char *msg)
1266{
1267    int ok = -2, width = 16;
1268    const char *yesstr;		/* String of Yes characters accepted. */
1269    const char *nostr;		/* Same for No. */
1270    const char *allstr;		/* And All, surprise! */
1271
1272    assert(msg != NULL);
1273
1274    /* yesstr, nostr, and allstr are strings of any length.  Each string
1275     * consists of all single-byte characters accepted as valid
1276     * characters for that value.  The first value will be the one
1277     * displayed in the shortcuts. */
1278    /* TRANSLATORS: For the next three strings, if possible, specify
1279     * the single-byte shortcuts for both your language and English.
1280     * For example, in French: "OoYy" for "Oui". */
1281    yesstr = _("Yy");
1282    nostr = _("Nn");
1283    allstr = _("Aa");
1284
1285    if (!ISSET(NO_HELP)) {
1286	char shortstr[3];
1287		/* Temp string for Yes, No, All. */
1288
1289	if (COLS < 32)
1290	    width = COLS / 2;
1291
1292	/* Clear the shortcut list from the bottom of the screen. */
1293	blank_bottombars();
1294
1295	sprintf(shortstr, " %c", yesstr[0]);
1296	wmove(bottomwin, 1, 0);
1297	onekey(shortstr, _("Yes"), width);
1298
1299	if (all) {
1300	    wmove(bottomwin, 1, width);
1301	    shortstr[1] = allstr[0];
1302	    onekey(shortstr, _("All"), width);
1303	}
1304
1305	wmove(bottomwin, 2, 0);
1306	shortstr[1] = nostr[0];
1307	onekey(shortstr, _("No"), width);
1308
1309	wmove(bottomwin, 2, 16);
1310	onekey("^C", _("Cancel"), width);
1311    }
1312
1313    wattron(bottomwin, reverse_attr);
1314
1315    blank_statusbar();
1316    mvwaddnstr(bottomwin, 0, 0, msg, actual_x(msg, COLS - 1));
1317
1318    wattroff(bottomwin, reverse_attr);
1319
1320    /* Refresh the edit window and the statusbar before getting
1321     * input. */
1322    wnoutrefresh(edit);
1323    wnoutrefresh(bottomwin);
1324
1325    do {
1326	int kbinput;
1327	bool meta_key, func_key;
1328#ifndef DISABLE_MOUSE
1329	int mouse_x, mouse_y;
1330#endif
1331
1332	kbinput = get_kbinput(bottomwin, &meta_key, &func_key);
1333
1334	switch (kbinput) {
1335	    case NANO_CANCEL_KEY:
1336		ok = -1;
1337		break;
1338#ifndef DISABLE_MOUSE
1339	    case KEY_MOUSE:
1340		get_mouseinput(&mouse_x, &mouse_y, FALSE);
1341
1342		if (wenclose(bottomwin, mouse_y, mouse_x) &&
1343			!ISSET(NO_HELP) && mouse_x < (width * 2) &&
1344			mouse_y - (2 - no_more_space()) -
1345			editwinrows - 1 >= 0) {
1346		    int x = mouse_x / width;
1347			/* Calculate the x-coordinate relative to the
1348			 * two columns of the Yes/No/All shortcuts in
1349			 * bottomwin. */
1350		    int y = mouse_y - (2 - no_more_space()) -
1351			editwinrows - 1;
1352			/* Calculate the y-coordinate relative to the
1353			 * beginning of the Yes/No/All shortcuts in
1354			 * bottomwin, i.e. with the sizes of topwin,
1355			 * edit, and the first line of bottomwin
1356			 * subtracted out. */
1357
1358		    assert(0 <= x && x <= 1 && 0 <= y && y <= 1);
1359
1360		    /* x == 0 means they clicked Yes or No.  y == 0
1361		     * means Yes or All. */
1362		    ok = -2 * x * y + x - y + 1;
1363
1364		    if (ok == 2 && !all)
1365			ok = -2;
1366		}
1367		break;
1368#endif /* !DISABLE_MOUSE */
1369	    case NANO_REFRESH_KEY:
1370		total_redraw();
1371		continue;
1372	    default:
1373		/* Look for the kbinput in the Yes, No and (optionally)
1374		 * All strings. */
1375		if (strchr(yesstr, kbinput) != NULL)
1376		    ok = 1;
1377		else if (strchr(nostr, kbinput) != NULL)
1378		    ok = 0;
1379		else if (all && strchr(allstr, kbinput) != NULL)
1380		    ok = 2;
1381	}
1382    } while (ok == -2);
1383
1384    return ok;
1385}
1386