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 }