nano

nano with my custom patches
git clone git://bsandro.tech/nano
Log | Files | Refs | README | LICENSE

prompt.c (24285B)


      1 /**************************************************************************
      2  *   prompt.c  --  This file is part of GNU nano.                         *
      3  *                                                                        *
      4  *   Copyright (C) 1999-2011, 2013-2025 Free Software Foundation, Inc.    *
      5  *   Copyright (C) 2016, 2018, 2020, 2022 Benno Schulenberg               *
      6  *                                                                        *
      7  *   GNU nano is free software: you can redistribute it and/or modify     *
      8  *   it under the terms of the GNU General Public License as published    *
      9  *   by the Free Software Foundation, either version 3 of the License,    *
     10  *   or (at your option) any later version.                               *
     11  *                                                                        *
     12  *   GNU nano is distributed in the hope that it will be useful,          *
     13  *   but WITHOUT ANY WARRANTY; without even the implied warranty          *
     14  *   of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.              *
     15  *   See the GNU 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, see https://gnu.org/licenses/.     *
     19  *                                                                        *
     20  **************************************************************************/
     21 
     22 #include "prototypes.h"
     23 
     24 #include <string.h>
     25 
     26 static char *prompt = NULL;
     27 		/* The prompt string used for status-bar questions. */
     28 static size_t typing_x = HIGHEST_POSITIVE;
     29 		/* The cursor position in answer. */
     30 
     31 /* Move to the beginning of the answer. */
     32 void do_statusbar_home(void)
     33 {
     34 	typing_x = 0;
     35 }
     36 
     37 /* Move to the end of the answer. */
     38 void do_statusbar_end(void)
     39 {
     40 	typing_x = strlen(answer);
     41 }
     42 
     43 #ifndef NANO_TINY
     44 /* Move to the previous word in the answer. */
     45 void do_statusbar_prev_word(void)
     46 {
     47 	bool seen_a_word = FALSE, step_forward = FALSE;
     48 
     49 	/* Move backward until we pass over the start of a word. */
     50 	while (typing_x != 0) {
     51 		typing_x = step_left(answer, typing_x);
     52 
     53 		if (is_word_char(answer + typing_x, FALSE))
     54 			seen_a_word = TRUE;
     55 #ifdef ENABLE_UTF8
     56 		else if (is_zerowidth(answer + typing_x))
     57 			; /* skip */
     58 #endif
     59 		else if (seen_a_word) {
     60 			/* This is space now: we've overshot the start of the word. */
     61 			step_forward = TRUE;
     62 			break;
     63 		}
     64 	}
     65 
     66 	if (step_forward)
     67 		/* Move one character forward again to sit on the start of the word. */
     68 		typing_x = step_right(answer, typing_x);
     69 }
     70 
     71 /* Move to the next word in the answer. */
     72 void do_statusbar_next_word(void)
     73 {
     74 	bool seen_space = !is_word_char(answer + typing_x, FALSE);
     75 	bool seen_word = !seen_space;
     76 
     77 	/* Move forward until we reach either the end or the start of a word,
     78 	 * depending on whether the AFTER_ENDS flag is set or not. */
     79 	while (answer[typing_x] != '\0') {
     80 		typing_x = step_right(answer, typing_x);
     81 
     82 		if (ISSET(AFTER_ENDS)) {
     83 			/* If this is a word character, continue; else it's a separator,
     84 			 * and if we've already seen a word, then it's a word end. */
     85 			if (is_word_char(answer + typing_x, FALSE))
     86 				seen_word = TRUE;
     87 #ifdef ENABLE_UTF8
     88 			else if (is_zerowidth(answer + typing_x))
     89 				; /* skip */
     90 #endif
     91 			else if (seen_word)
     92 				break;
     93 		} else {
     94 #ifdef ENABLE_UTF8
     95 			if (is_zerowidth(answer + typing_x))
     96 				; /* skip */
     97 			else
     98 #endif
     99 			/* If this is not a word character, then it's a separator; else
    100 			 * if we've already seen a separator, then it's a word start. */
    101 			if (!is_word_char(answer + typing_x, FALSE))
    102 				seen_space = TRUE;
    103 			else if (seen_space)
    104 				break;
    105 		}
    106 	}
    107 }
    108 #endif /* !NANO_TINY */
    109 
    110 /* Move left one character in the answer. */
    111 void do_statusbar_left(void)
    112 {
    113 	if (typing_x > 0) {
    114 		typing_x = step_left(answer, typing_x);
    115 #ifdef ENABLE_UTF8
    116 		while (typing_x > 0 && is_zerowidth(answer + typing_x))
    117 			typing_x = step_left(answer, typing_x);
    118 #endif
    119 	}
    120 }
    121 
    122 /* Move right one character in the answer. */
    123 void do_statusbar_right(void)
    124 {
    125 	if (answer[typing_x] != '\0') {
    126 		typing_x = step_right(answer, typing_x);
    127 #ifdef ENABLE_UTF8
    128 		while (answer[typing_x] != '\0' && is_zerowidth(answer + typing_x))
    129 			typing_x = step_right(answer, typing_x);
    130 #endif
    131 	}
    132 }
    133 
    134 /* Backspace over one character in the answer. */
    135 void do_statusbar_backspace(void)
    136 {
    137 	if (typing_x > 0) {
    138 		size_t was_x = typing_x;
    139 
    140 		typing_x = step_left(answer, typing_x);
    141 		memmove(answer + typing_x, answer + was_x, strlen(answer) - was_x + 1);
    142 	}
    143 }
    144 
    145 /* Delete one character in the answer. */
    146 void do_statusbar_delete(void)
    147 {
    148 	if (answer[typing_x] != '\0') {
    149 		int charlen = char_length(answer + typing_x);
    150 
    151 		memmove(answer + typing_x, answer + typing_x + charlen,
    152 						strlen(answer) - typing_x - charlen + 1);
    153 #ifdef ENABLE_UTF8
    154 		if (is_zerowidth(answer + typing_x))
    155 			do_statusbar_delete();
    156 #endif
    157 	}
    158 }
    159 
    160 /* Zap the part of the answer after the cursor, or the whole answer. */
    161 void lop_the_answer(void)
    162 {
    163 	if (answer[typing_x] == '\0')
    164 		typing_x = 0;
    165 
    166 	answer[typing_x] = '\0';
    167 }
    168 
    169 #ifndef NANO_TINY
    170 /* Copy the current answer (if any) into the cutbuffer. */
    171 void copy_the_answer(void)
    172 {
    173 	if (*answer) {
    174 		free_lines(cutbuffer);
    175 		cutbuffer = make_new_node(NULL);
    176 		cutbuffer->data = copy_of(answer);
    177 		typing_x = 0;
    178 	}
    179 }
    180 
    181 /* Paste the first line of the cutbuffer into the current answer. */
    182 void paste_into_answer(void)
    183 {
    184 	size_t pastelen = strlen(cutbuffer->data);
    185 
    186 	answer = nrealloc(answer, strlen(answer) + pastelen + 1);
    187 	memmove(answer + typing_x + pastelen, answer + typing_x,
    188 								strlen(answer) - typing_x + 1);
    189 	strncpy(answer + typing_x, cutbuffer->data, pastelen);
    190 
    191 	typing_x += pastelen;
    192 }
    193 #endif
    194 
    195 #ifdef ENABLE_MOUSE
    196 /* Handle a mouse click on the status-bar prompt or the shortcut list. */
    197 int do_statusbar_mouse(void)
    198 {
    199 	int click_row, click_col;
    200 	int retval = get_mouseinput(&click_row, &click_col, TRUE);
    201 
    202 	/* We can click on the status-bar window text to move the cursor. */
    203 	if (retval == 0 && wmouse_trafo(footwin, &click_row, &click_col, FALSE)) {
    204 		size_t start_col = breadth(prompt) + 2;
    205 
    206 		/* Move to where the click occurred. */
    207 		if (click_row == 0 && click_col >= start_col)
    208 			typing_x = actual_x(answer,
    209 							get_statusbar_page_start(start_col, start_col +
    210 							wideness(answer, typing_x)) + click_col - start_col);
    211 	}
    212 
    213 	return retval;
    214 }
    215 #endif
    216 
    217 /* Insert the given short burst of bytes into the answer. */
    218 void inject_into_answer(char *burst, size_t count)
    219 {
    220 	/* First encode any embedded NUL byte as 0x0A. */
    221 	for (size_t index = 0; index < count; index++)
    222 		if (burst[index] == '\0')
    223 			burst[index] = '\n';
    224 
    225 	answer = nrealloc(answer, strlen(answer) + count + 1);
    226 	memmove(answer + typing_x + count, answer + typing_x,
    227 								strlen(answer) - typing_x + 1);
    228 	strncpy(answer + typing_x, burst, count);
    229 
    230 	typing_x += count;
    231 }
    232 
    233 /* Get a verbatim keystroke and insert it into the answer. */
    234 void do_statusbar_verbatim_input(void)
    235 {
    236 	size_t count = 1;
    237 	char *bytes;
    238 
    239 	bytes = get_verbatim_kbinput(footwin, &count);
    240 
    241 	if (0 < count && count < 999)
    242 		inject_into_answer(bytes, count);
    243 	else if (count == 0)
    244 		beep();
    245 
    246 	free(bytes);
    247 }
    248 
    249 /* Add the given input to the input buffer when it's a normal byte,
    250  * and inject the gathered bytes into the answer when ready. */
    251 void absorb_character(int input, functionptrtype function)
    252 {
    253 	static char *puddle = NULL;
    254 		/* The input buffer. */
    255 	static size_t capacity = 8;
    256 		/* The size of the input buffer; gets doubled whenever needed. */
    257 	static size_t depth = 0;
    258 		/* The length of the input buffer. */
    259 
    260 	/* If not a command, discard anything that is not a normal character byte.
    261 	 * Apart from that, only accept input when not in restricted mode, or when
    262 	 * not at the "Write File" prompt, or when there is no filename yet. */
    263 	if (!function) {
    264 		if ((input < 0x20 && input != '\t') || meta_key || input > 0xFF)
    265 			beep();
    266 		else if (!ISSET(RESTRICTED) || currmenu != MWRITEFILE ||
    267 						openfile->filename[0] == '\0') {
    268 			/* When the input buffer (plus room for terminating NUL) is full,
    269 			 * extend it; otherwise, if it does not exist yet, create it. */
    270 			if (depth + 1 == capacity) {
    271 				capacity = 2 * capacity;
    272 				puddle = nrealloc(puddle, capacity);
    273 			} else if (!puddle)
    274 				puddle = nmalloc(capacity);
    275 
    276 			puddle[depth++] = (char)input;
    277 		}
    278 	}
    279 
    280 	/* If there are gathered bytes and we have a command or no other key codes
    281 	 * are waiting, it's time to insert these bytes into the answer. */
    282 	if (depth > 0 && (function || waiting_keycodes() == 0)) {
    283 		puddle[depth] = '\0';
    284 		inject_into_answer(puddle, depth);
    285 		depth = 0;
    286 	}
    287 }
    288 
    289 /* Handle any editing shortcut, and return TRUE when handled. */
    290 bool handle_editing(functionptrtype function)
    291 {
    292 	if (function == do_left)
    293 		do_statusbar_left();
    294 	else if (function == do_right)
    295 		do_statusbar_right();
    296 #ifndef NANO_TINY
    297 	else if (function == to_prev_word)
    298 		do_statusbar_prev_word();
    299 	else if (function == to_next_word)
    300 		do_statusbar_next_word();
    301 #endif
    302 	else if (function == do_home)
    303 		do_statusbar_home();
    304 	else if (function == do_end)
    305 		do_statusbar_end();
    306 	/* When in restricted mode at the "Write File" prompt and the
    307 	 * filename isn't blank, disallow any input and deletion. */
    308 	else if (ISSET(RESTRICTED) && currmenu == MWRITEFILE &&
    309 							openfile->filename[0] != '\0' &&
    310 							(function == do_verbatim_input ||
    311 							function == do_delete || function == do_backspace ||
    312 							function == cut_text || function == paste_text))
    313 		;
    314 	else if (function == do_verbatim_input)
    315 		do_statusbar_verbatim_input();
    316 	else if (function == do_delete)
    317 		do_statusbar_delete();
    318 	else if (function == do_backspace)
    319 		do_statusbar_backspace();
    320 	else if (function == cut_text)
    321 		lop_the_answer();
    322 #ifndef NANO_TINY
    323 	else if (function == copy_text)
    324 		copy_the_answer();
    325 	else if (function == paste_text) {
    326 		if (cutbuffer != NULL)
    327 			paste_into_answer();
    328 	}
    329 #endif
    330 	else
    331 		return FALSE;
    332 
    333 	/* Don't handle any handled function again. */
    334 	return TRUE;
    335 }
    336 
    337 /* Return the column number of the first character of the answer that is
    338  * displayed in the status bar when the cursor is at the given column,
    339  * with the available room for the answer starting at base.  Note that
    340  * (0 <= column - get_statusbar_page_start(column) < COLS). */
    341 size_t get_statusbar_page_start(size_t base, size_t column)
    342 {
    343 	if (column == base || column < COLS - 1)
    344 		return 0;
    345 	else if (COLS > base + 2)
    346 		return column - base - 1 - (column - base - 1) % (COLS - base - 2);
    347 	else
    348 		return column - 2;
    349 }
    350 
    351 /* Reinitialize the cursor position in the answer. */
    352 void put_cursor_at_end_of_answer(void)
    353 {
    354 	typing_x = HIGHEST_POSITIVE;
    355 }
    356 
    357 /* Redraw the prompt bar and place the cursor at the right spot. */
    358 void draw_the_promptbar(void)
    359 {
    360 	size_t base = breadth(prompt) + 2;
    361 	size_t column = base + wideness(answer, typing_x);
    362 	size_t the_page, end_page;
    363 	char *expanded;
    364 
    365 	the_page = get_statusbar_page_start(base, column);
    366 	end_page = get_statusbar_page_start(base, base + breadth(answer) - 1);
    367 
    368 	/* Color the prompt bar over its full width. */
    369 	wattron(footwin, interface_color_pair[PROMPT_BAR]);
    370 	mvwprintw(footwin, 0, 0, "%*s", COLS, " ");
    371 
    372 	mvwaddstr(footwin, 0, 0, prompt);
    373 	waddch(footwin, ':');
    374 	waddch(footwin, (the_page == 0) ? ' ' : '<');
    375 
    376 	expanded = display_string(answer, the_page, COLS - base, FALSE, TRUE);
    377 	waddstr(footwin, expanded);
    378 	free(expanded);
    379 
    380 	if (the_page < end_page && base + breadth(answer) - the_page > COLS)
    381 		mvwaddch(footwin, 0, COLS - 1, '>');
    382 
    383 	wattroff(footwin, interface_color_pair[PROMPT_BAR]);
    384 
    385 #if defined(NCURSES_VERSION_PATCH) && (NCURSES_VERSION_PATCH < 20210220)
    386 	/* Work around a cursor-misplacement bug -- https://sv.gnu.org/bugs/?59808. */
    387 	if (ISSET(NO_HELP)) {
    388 		wmove(footwin, 0, 0);
    389 		wrefresh(footwin);
    390 	}
    391 #endif
    392 
    393 	/* Place the cursor at the right spot. */
    394 	wmove(footwin, 0, column - the_page);
    395 
    396 	wnoutrefresh(footwin);
    397 }
    398 
    399 #ifndef NANO_TINY
    400 /* Remove or add the pipe character at the answer's head. */
    401 void add_or_remove_pipe_symbol_from_answer(void)
    402 {
    403 	if (answer[0] == '|') {
    404 		memmove(answer, answer + 1, strlen(answer));
    405 		if (typing_x > 0)
    406 			typing_x--;
    407 	} else {
    408 		answer = nrealloc(answer, strlen(answer) + 2);
    409 		memmove(answer + 1, answer, strlen(answer) + 1);
    410 		answer[0] = '|';
    411 		typing_x++;
    412 	}
    413 }
    414 #endif
    415 
    416 /* Get a string of input at the status-bar prompt. */
    417 functionptrtype acquire_an_answer(int *actual, bool *listed,
    418 					linestruct **history_list, void (*refresh_func)(void))
    419 {
    420 #ifdef ENABLE_HISTORIES
    421 	char *stored_string = NULL;
    422 		/* Whatever the answer was before the user foraged into history. */
    423 #ifdef ENABLE_TABCOMP
    424 	bool previous_was_tab = FALSE;
    425 		/* Whether the previous keystroke was an attempt at tab completion. */
    426 	size_t fragment_length = 0;
    427 		/* The length of the fragment that the user tries to tab complete. */
    428 #endif
    429 #endif
    430 #ifndef NANO_TINY
    431 	bool bracketed_paste = FALSE;
    432 #endif
    433 	const keystruct *shortcut;
    434 	functionptrtype function;
    435 	int input;
    436 
    437 	if (typing_x > strlen(answer))
    438 		typing_x = strlen(answer);
    439 
    440 	while (TRUE) {
    441 		draw_the_promptbar();
    442 
    443 		/* Read in one keystroke. */
    444 		input = get_kbinput(footwin, VISIBLE);
    445 
    446 #ifndef NANO_TINY
    447 		/* If the window size changed, go reformat the prompt string. */
    448 		if (input == THE_WINDOW_RESIZED) {
    449 			refresh_func();  /* Only needed when in file browser. */
    450 			*actual = THE_WINDOW_RESIZED;
    451 #ifdef ENABLE_HISTORIES
    452 			free(stored_string);
    453 #endif
    454 			return NULL;
    455 		}
    456 		if (input == START_OF_PASTE || input == END_OF_PASTE)
    457 			bracketed_paste = (input == START_OF_PASTE);
    458 #endif
    459 #ifdef ENABLE_MOUSE
    460 		/* For a click on a shortcut, read in the resulting keycode. */
    461 		if (input == KEY_MOUSE && do_statusbar_mouse() == 1)
    462 			input = get_kbinput(footwin, BLIND);
    463 		if (input == KEY_MOUSE)
    464 			continue;
    465 #endif
    466 
    467 		/* Check for a shortcut in the current list. */
    468 		shortcut = get_shortcut(input);
    469 		function = (shortcut ? shortcut->func : NULL);
    470 #ifndef NANO_TINY
    471 		/* Tabs in an external paste are not commands. */
    472 		if (input == '\t' && bracketed_paste)
    473 			function = NULL;
    474 #endif
    475 		/* When it's a normal character, add it to the answer. */
    476 		absorb_character(input, function);
    477 
    478 #ifndef NANO_TINY
    479 		/* Ignore any commands inside an external paste. */
    480 		if (bracketed_paste) {
    481 			if (function && function != do_nothing)
    482 				beep();
    483 			continue;
    484 		}
    485 #endif
    486 
    487 		if (function == do_cancel || function == do_enter)
    488 			break;
    489 
    490 #ifdef ENABLE_TABCOMP
    491 		if (function == do_tab) {
    492 #ifdef ENABLE_HISTORIES
    493 			if (history_list != NULL) {
    494 				if (!previous_was_tab)
    495 					fragment_length = strlen(answer);
    496 
    497 				if (fragment_length > 0) {
    498 					answer = get_history_completion(history_list,
    499 													answer, fragment_length);
    500 					typing_x = strlen(answer);
    501 				}
    502 			} else
    503 #endif
    504 			/* Allow tab completion of filenames, but not in restricted mode. */
    505 			if ((currmenu & (MINSERTFILE|MWRITEFILE|MGOTODIR)) && !ISSET(RESTRICTED))
    506 				answer = input_tab(answer, &typing_x, refresh_func, listed);
    507 		} else
    508 #endif
    509 #ifdef ENABLE_HISTORIES
    510 		if (function == get_older_item && history_list != NULL) {
    511 			/* If this is the first step into history, start at the bottom. */
    512 			if (stored_string == NULL)
    513 				reset_history_pointer_for(*history_list);
    514 
    515 			/* When moving up from the bottom, remember the current answer. */
    516 			if ((*history_list)->next == NULL)
    517 				stored_string = mallocstrcpy(stored_string, answer);
    518 
    519 			/* If there is an older item, move to it and copy its string. */
    520 			if ((*history_list)->prev != NULL) {
    521 				*history_list = (*history_list)->prev;
    522 				answer = mallocstrcpy(answer, (*history_list)->data);
    523 				typing_x = strlen(answer);
    524 			}
    525 		} else if (function == get_newer_item && history_list != NULL) {
    526 			/* If there is a newer item, move to it and copy its string. */
    527 			if ((*history_list)->next != NULL) {
    528 				*history_list = (*history_list)->next;
    529 				answer = mallocstrcpy(answer, (*history_list)->data);
    530 				typing_x = strlen(answer);
    531 			}
    532 
    533 			/* When at the bottom of the history list, restore the old answer. */
    534 			if ((*history_list)->next == NULL && stored_string && *answer == '\0') {
    535 				answer = mallocstrcpy(answer, stored_string);
    536 				typing_x = strlen(answer);
    537 			}
    538 		} else
    539 #endif /* ENABLE_HISTORIES */
    540 		if (function == do_help || function == full_refresh)
    541 			function();
    542 #ifndef NANO_TINY
    543 		else if (function == do_toggle && shortcut->toggle == NO_HELP) {
    544 			TOGGLE(NO_HELP);
    545 			window_init();
    546 			focusing = FALSE;
    547 			refresh_func();
    548 			bottombars(currmenu);
    549 		} else if (function == do_nothing)
    550 			;
    551 #endif
    552 #ifdef ENABLE_NANORC
    553 		else if (function == (functionptrtype)implant)
    554 			implant(shortcut->expansion);
    555 #endif
    556 		else if (function && !handle_editing(function)) {
    557 			/* When it's a permissible shortcut, run it and done. */
    558 			if (!ISSET(VIEW_MODE) || !changes_something(function)) {
    559 #ifndef NANO_TINY
    560 				/* When invoking a tool at the Execute prompt, stash an "answer". */
    561 				if (currmenu == MEXECUTE)
    562 					foretext = mallocstrcpy(foretext, answer);
    563 #endif
    564 				function();
    565 				break;
    566 			} else
    567 				beep();
    568 		}
    569 
    570 #if defined(ENABLE_HISTORIES) && defined(ENABLE_TABCOMP)
    571 		previous_was_tab = (function == do_tab);
    572 #endif
    573 	}
    574 
    575 #ifndef NANO_TINY
    576 	/* When an external command was run, clear a possibly stashed answer. */
    577 	if (currmenu == MEXECUTE && function == do_enter)
    578 		*foretext = '\0';
    579 #endif
    580 #ifdef ENABLE_HISTORIES
    581 	/* If the history pointer was moved, point it at the bottom again. */
    582 	if (stored_string != NULL) {
    583 		reset_history_pointer_for(*history_list);
    584 		free(stored_string);
    585 	}
    586 #endif
    587 
    588 	*actual = input;
    589 
    590 	return function;
    591 }
    592 
    593 /* Ask a question on the status bar.  Return 0 when text was entered,
    594  * -1 for a cancelled entry, -2 for a blank string, and the relevant
    595  * keycode when a valid shortcut key was pressed.  The 'provided'
    596  * parameter is the default answer for when simply Enter is typed. */
    597 int do_prompt(int menu, const char *provided, linestruct **history_list,
    598 				void (*refresh_func)(void), const char *msg, ...)
    599 {
    600 	functionptrtype function = NULL;
    601 	bool listed = FALSE;
    602 	va_list ap;
    603 	int retval;
    604 	/* Save a possible current status-bar x position and prompt. */
    605 	size_t was_typing_x = typing_x;
    606 	char *saved_prompt = prompt;
    607 
    608 	bottombars(menu);
    609 
    610 	if (answer != provided)
    611 		answer = mallocstrcpy(answer, provided);
    612 
    613 #ifndef NANO_TINY
    614   redo_theprompt:
    615 #endif
    616 	prompt = nmalloc((COLS * MAXCHARLEN) + 1);
    617 	va_start(ap, msg);
    618 	vsnprintf(prompt, COLS * MAXCHARLEN, msg, ap);
    619 	va_end(ap);
    620 	/* Reserve five columns for colon plus angles plus answer, ":<aa>". */
    621 	prompt[actual_x(prompt, (COLS < 5) ? 0 : COLS - 5)] = '\0';
    622 
    623 	lastmessage = VACUUM;
    624 
    625 	function = acquire_an_answer(&retval, &listed, history_list, refresh_func);
    626 	free(prompt);
    627 
    628 #ifndef NANO_TINY
    629 	if (retval == THE_WINDOW_RESIZED)
    630 		goto redo_theprompt;
    631 #endif
    632 
    633 	/* Restore a possible previous prompt and maybe the typing position. */
    634 	prompt = saved_prompt;
    635 	if (function == do_cancel || function == do_enter ||
    636 #ifdef ENABLE_BROWSER
    637 				function == to_first_file || function == to_last_file ||
    638 #endif
    639 				function == to_first_line || function == to_last_line)
    640 		typing_x = was_typing_x;
    641 
    642 	/* Set the proper return value for Cancel and Enter. */
    643 	if (function == do_cancel)
    644 		retval = -1;
    645 	else if (function == do_enter)
    646 		retval = (*answer == '\0') ? -2 : 0;
    647 
    648 	if (lastmessage == VACUUM)
    649 		wipe_statusbar();
    650 
    651 #ifdef ENABLE_TABCOMP
    652 	/* If possible filename completions are still listed, clear them off. */
    653 	if (listed)
    654 		refresh_func();
    655 #endif
    656 
    657 	return retval;
    658 }
    659 
    660 #define UNDECIDED  -2
    661 
    662 /* Ask a simple Yes/No (and optionally All) question on the status bar
    663  * and return the choice -- either YES or NO or ALL or CANCEL. */
    664 int ask_user(bool withall, const char *question)
    665 {
    666 	int choice = UNDECIDED;
    667 	int width = 16;
    668 	/* TRANSLATORS: For the next three strings, specify the starting letters
    669 	 * of the translations for "Yes"/"No"/"All".  The first letter of each of
    670 	 * these strings MUST be a single-byte letter; others may be multi-byte. */
    671 	const char *yesstr = _("Yy");
    672 	const char *nostr = _("Nn");
    673 	const char *allstr = _("Aa");
    674 	const keystruct *shortcut;
    675 	functionptrtype function;
    676 
    677 	while (choice == UNDECIDED) {
    678 #ifdef ENABLE_NLS
    679 		char letter[MAXCHARLEN + 1];
    680 		int index = 0;
    681 #endif
    682 		int kbinput;
    683 
    684 		if (!ISSET(NO_HELP)) {
    685 			char shortstr[MAXCHARLEN + 2];
    686 				/* Temporary string for (translated) " Y", " N" and " A". */
    687 			const keystruct *cancelshortcut = first_sc_for(MYESNO, do_cancel);
    688 				/* The keystroke that is bound to the Cancel function. */
    689 
    690 			if (COLS < 32)
    691 				width = COLS / 2;
    692 
    693 			/* Clear the shortcut list from the bottom of the screen. */
    694 			blank_bottombars();
    695 
    696 			/* Now show the ones for "Yes", "No", "Cancel" and maybe "All". */
    697 			sprintf(shortstr, " %c", yesstr[0]);
    698 			wmove(footwin, 1, 0);
    699 			post_one_key(shortstr, _("Yes"), width);
    700 
    701 			shortstr[1] = nostr[0];
    702 			wmove(footwin, 2, 0);
    703 			post_one_key(shortstr, _("No"), width);
    704 
    705 			if (withall) {
    706 				shortstr[1] = allstr[0];
    707 				wmove(footwin, 1, width);
    708 				post_one_key(shortstr, _("All"), width);
    709 			}
    710 
    711 			wmove(footwin, 2, width);
    712 			post_one_key(cancelshortcut->keystr, _("Cancel"), width);
    713 		}
    714 
    715 		/* Color the prompt bar over its full width and display the question. */
    716 		wattron(footwin, interface_color_pair[PROMPT_BAR]);
    717 		mvwprintw(footwin, 0, 0, "%*s", COLS, " ");
    718 		mvwaddnstr(footwin, 0, 0, question, actual_x(question, COLS - 1));
    719 		wattroff(footwin, interface_color_pair[PROMPT_BAR]);
    720 		wnoutrefresh(footwin);
    721 
    722 		currmenu = MYESNO;
    723 
    724 		/* When not replacing, show the cursor while waiting for a key. */
    725 		kbinput = get_kbinput(footwin, !withall);
    726 
    727 #ifndef NANO_TINY
    728 		if (kbinput == THE_WINDOW_RESIZED)
    729 			continue;
    730 
    731 		/* Accept first character of an external paste and ignore the rest. */
    732 		if (kbinput == START_OF_PASTE) {
    733 			kbinput = get_kbinput(footwin, BLIND);
    734 			while (get_kbinput(footwin, BLIND) != END_OF_PASTE)
    735 				;
    736 		}
    737 #endif
    738 
    739 #ifdef ENABLE_NLS
    740 		letter[index++] = (unsigned char)kbinput;
    741 #ifdef ENABLE_UTF8
    742 		/* If the received code is a UTF-8 starter byte, get also the
    743 		 * continuation bytes and assemble them into one letter. */
    744 		if (0xC0 <= kbinput && kbinput <= 0xF7 && using_utf8) {
    745 			int extras = (kbinput / 16) % 4 + (kbinput <= 0xCF ? 1 : 0);
    746 
    747 			while (extras <= waiting_keycodes() && extras-- > 0)
    748 				letter[index++] = (unsigned char)get_kbinput(footwin, !withall);
    749 		}
    750 #endif
    751 		letter[index] = '\0';
    752 
    753 		/* See if the typed letter is in the Yes, No, or All strings. */
    754 		if (strstr(yesstr, letter) != NULL)
    755 			choice = YES;
    756 		else if (strstr(nostr, letter) != NULL)
    757 			choice = NO;
    758 		else if (withall && strstr(allstr, letter) != NULL)
    759 			choice = ALL;
    760 		else
    761 #endif /* ENABLE_NLS */
    762 		if (strchr("Yy", kbinput) != NULL)
    763 			choice = YES;
    764 		else if (strchr("Nn", kbinput) != NULL)
    765 			choice = NO;
    766 		else if (withall && strchr("Aa", kbinput) != NULL)
    767 			choice = ALL;
    768 
    769 		if (choice != UNDECIDED)
    770 			break;
    771 
    772 		shortcut = get_shortcut(kbinput);
    773 		function = (shortcut ? shortcut->func : NULL);
    774 
    775 		if (function == do_cancel)
    776 			choice = CANCEL;
    777 		else if (function == full_refresh)
    778 			full_refresh();
    779 #ifndef NANO_TINY
    780 		else if (function == do_toggle && shortcut->toggle == NO_HELP) {
    781 			TOGGLE(NO_HELP);
    782 			window_init();
    783 			titlebar(NULL);
    784 			focusing = FALSE;
    785 			edit_refresh();
    786 			focusing = TRUE;
    787 		}
    788 #endif
    789 		/* Interpret ^N as "No", to allow exiting in anger, and ^Q or ^X too. */
    790 		else if (kbinput == '\x0E' || (kbinput == '\x11' && !ISSET(MODERN_BINDINGS)) ||
    791 									  (kbinput == '\x18' && ISSET(MODERN_BINDINGS))) {
    792 			choice = NO;
    793 			if (kbinput != '\x0E')  /* ^X^Q makes nano exit with an error. */
    794 				final_status = 2;
    795 		/* Also, interpret ^Y as "Yes, and  ^A as "All". */
    796 		} else if (kbinput == '\x19')
    797 			choice = YES;
    798 		else if (kbinput == '\x01' && withall)
    799 			choice = ALL;
    800 #ifdef ENABLE_MOUSE
    801 		else if (kbinput == KEY_MOUSE) {
    802 			int mouse_x, mouse_y;
    803 			/* We can click on the Yes/No/All shortcuts to select an answer. */
    804 			if (get_mouseinput(&mouse_y, &mouse_x, FALSE) == 0 &&
    805 						wmouse_trafo(footwin, &mouse_y, &mouse_x, FALSE) &&
    806 						mouse_x < (width * 2) && mouse_y > 0) {
    807 				int x = mouse_x / width;
    808 				int y = mouse_y - 1;
    809 
    810 				/* x == 0 means Yes or No, y == 0 means Yes or All. */
    811 				choice = -2 * x * y + x - y + 1;
    812 
    813 				if (choice == ALL && !withall)
    814 					choice = UNDECIDED;
    815 			}
    816 		}
    817 #endif
    818 		else
    819 			beep();
    820 	}
    821 
    822 	return choice;
    823 }