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