diff options
Diffstat (limited to 'src/prompt.c')
-rw-r--r-- | src/prompt.c | 1363 |
1 files changed, 1363 insertions, 0 deletions
diff --git a/src/prompt.c b/src/prompt.c new file mode 100644 index 0000000..0be25f5 --- /dev/null +++ b/src/prompt.c @@ -0,0 +1,1363 @@ +/* $Id: prompt.c 4527 2011-02-07 14:45:56Z astyanax $ */ +/************************************************************************** + * prompt.c * + * * + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, * + * 2008, 2009 Free Software Foundation, Inc. * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3, or (at your option) * + * any later version. * + * * + * This program is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * + * 02110-1301, USA. * + * * + **************************************************************************/ + +#include "proto.h" + +#include <stdio.h> +#include <stdarg.h> +#include <string.h> + +static char *prompt = NULL; + /* The prompt string used for statusbar questions. */ +static size_t statusbar_x = (size_t)-1; + /* The cursor position in answer. */ +static size_t statusbar_pww = (size_t)-1; + /* The place we want in answer. */ +static size_t old_statusbar_x = (size_t)-1; + /* The old cursor position in answer, if any. */ +static size_t old_pww = (size_t)-1; + /* The old place we want in answer, if any. */ +static bool reset_statusbar_x = FALSE; + /* Should we reset the cursor position at the statusbar + * prompt? */ + +/* Read in a character, interpret it as a shortcut or toggle if + * necessary, and return it. Set meta_key to TRUE if the character is a + * meta sequence, set func_key to TRUE if the character is a function + * key, set have_shortcut to TRUE if the character is a shortcut + * key, set ran_func to TRUE if we ran a function associated with a + * shortcut key, and set finished to TRUE if we're done after running + * or trying to run a function associated with a shortcut key. If + * allow_funcs is FALSE, don't actually run any functions associated + * with shortcut keys. refresh_func is the function we will call to + * refresh the edit window. */ +int do_statusbar_input(bool *meta_key, bool *func_key, bool *have_shortcut, + bool *ran_func, bool *finished, bool allow_funcs, void + (*refresh_func)(void)) +{ + int input; + /* The character we read in. */ + static int *kbinput = NULL; + /* The input buffer. */ + static size_t kbinput_len = 0; + /* The length of the input buffer. */ + const sc *s; + const subnfunc *f; + + *have_shortcut = FALSE; + *ran_func = FALSE; + *finished = FALSE; + + /* Read in a character. */ + input = get_kbinput(bottomwin, meta_key, func_key); + +#ifndef DISABLE_MOUSE + if (allow_funcs) { + /* If we got a mouse click and it was on a shortcut, read in the + * shortcut character. */ + if (*func_key && input == KEY_MOUSE) { + if (do_statusbar_mouse() == 1) + input = get_kbinput(bottomwin, meta_key, func_key); + else { + *meta_key = FALSE; + *func_key = FALSE; + input = ERR; + } + } + } +#endif + + /* Check for a shortcut in the current list. */ + s = get_shortcut(currmenu, &input, meta_key, func_key); + + /* If we got a shortcut from the current list, or a "universal" + * statusbar prompt shortcut, set have_shortcut to TRUE. */ + *have_shortcut = (s != NULL); + + /* If we got a non-high-bit control key, a meta key sequence, or a + * function key, and it's not a shortcut or toggle, throw it out. */ + if (!*have_shortcut) { + if (is_ascii_cntrl_char(input) || *meta_key || *func_key) { + beep(); + *meta_key = FALSE; + *func_key = FALSE; + input = ERR; + } + } + + if (allow_funcs) { + /* If we got a character, and it isn't a shortcut or toggle, + * it's a normal text character. Display the warning if we're + * in view mode, or add the character to the input buffer if + * we're not. */ + if (input != ERR && !*have_shortcut) { + /* If we're using restricted mode, the filename isn't blank, + * and we're at the "Write File" prompt, disable text + * input. */ + if (!ISSET(RESTRICTED) || openfile->filename[0] == '\0' || + currmenu != MWRITEFILE) { + kbinput_len++; + kbinput = (int *)nrealloc(kbinput, kbinput_len * + sizeof(int)); + kbinput[kbinput_len - 1] = input; + } + } + + /* If we got a shortcut, or if there aren't any other characters + * waiting after the one we read in, we need to display all the + * characters in the input buffer if it isn't empty. */ + if (*have_shortcut || get_key_buffer_len() == 0) { + if (kbinput != NULL) { + /* Display all the characters in the input buffer at + * once, filtering out control characters. */ + char *output = charalloc(kbinput_len + 1); + size_t i; + bool got_enter; + /* Whether we got the Enter key. */ + + for (i = 0; i < kbinput_len; i++) + output[i] = (char)kbinput[i]; + output[i] = '\0'; + + do_statusbar_output(output, kbinput_len, &got_enter, + FALSE); + + free(output); + + /* Empty the input buffer. */ + kbinput_len = 0; + free(kbinput); + kbinput = NULL; + } + } + + if (*have_shortcut) { + if (s->scfunc == do_tab || s->scfunc == do_enter_void) + ; + else if (s->scfunc == total_refresh) + total_statusbar_refresh(refresh_func); + else if (s->scfunc == do_cut_text_void) { + /* If we're using restricted mode, the filename + * isn't blank, and we're at the "Write File" + * prompt, disable Cut. */ + if (!ISSET(RESTRICTED) || openfile->filename[0] == + '\0' || currmenu != MWRITEFILE) + do_statusbar_cut_text(); + } else if (s->scfunc == do_right) + do_statusbar_right(); + else if (s->scfunc == do_left) + do_statusbar_left(); + +#ifndef NANO_TINY + else if (s->scfunc == do_next_word_void) + do_statusbar_next_word(FALSE); + else if (s->scfunc == do_prev_word_void) + do_statusbar_prev_word(FALSE); +#endif + else if (s->scfunc == do_home) + do_statusbar_home(); + else if (s->scfunc == do_end) + do_statusbar_end(); + +#ifndef NANO_TINY + else if (s->scfunc == do_find_bracket) + do_statusbar_find_bracket(); +#endif + else if (s->scfunc == do_verbatim_input) { + /* If we're using restricted mode, the filename + * isn't blank, and we're at the "Write File" + * prompt, disable verbatim input. */ + if (!ISSET(RESTRICTED) || + openfile->filename[0] == '\0' || + currmenu != MWRITEFILE) { + bool got_enter; + /* Whether we got the Enter key. */ + + do_statusbar_verbatim_input(&got_enter); + + /* If we got the Enter key, remove it from + * the input buffer, set input to the key + * value for Enter, and set finished to TRUE + * to indicate that we're done. */ + if (got_enter) { + get_input(NULL, 1); + input = sc_seq_or(do_enter_void, 0); + *finished = TRUE; + } + } + } else if (s->scfunc == do_delete) { + /* If we're using restricted mode, the filename + * isn't blank, and we're at the "Write File" + * prompt, disable Delete. */ + if (!ISSET(RESTRICTED) || openfile->filename[0] == + '\0' || currmenu != MWRITEFILE) + do_statusbar_delete(); + } else if (s->scfunc == do_backspace) { + /* If we're using restricted mode, the filename + * isn't blank, and we're at the "Write File" + * prompt, disable Backspace. */ + if (!ISSET(RESTRICTED) || openfile->filename[0] == + '\0' || currmenu != MWRITEFILE) + do_statusbar_backspace(); + } else { + /* Handle the normal statusbar prompt shortcuts, setting + * ran_func to TRUE if we try to run their associated + * functions and setting finished to TRUE to indicate + * that we're done after running or trying to run their + * associated functions. */ + + f = sctofunc((sc *) s); + if (s->scfunc != 0 && s->execute == TRUE) { + *ran_func = TRUE; + if (f && (!ISSET(VIEW_MODE) || (f->viewok))) + f->scfunc(); + } + *finished = TRUE; + } + } + } + + return input; +} + +#ifndef DISABLE_MOUSE +/* Handle a mouse click on the statusbar prompt or the shortcut list. */ +int do_statusbar_mouse(void) +{ + int mouse_x, mouse_y; + int retval = get_mouseinput(&mouse_x, &mouse_y, TRUE); + + /* We can click on the statusbar window text to move the cursor. */ + if (retval == 0 && wmouse_trafo(bottomwin, &mouse_y, &mouse_x, + FALSE)) { + size_t start_col; + + assert(prompt != NULL); + + start_col = strlenpt(prompt) + 2; + + /* Move to where the click occurred. */ + if (mouse_x >= start_col && mouse_y == 0) { + size_t pww_save = statusbar_pww; + + statusbar_x = actual_x(answer, + get_statusbar_page_start(start_col, start_col + + statusbar_xplustabs()) + mouse_x - start_col); + statusbar_pww = statusbar_xplustabs(); + + if (need_statusbar_horizontal_update(pww_save)) + update_statusbar_line(answer, statusbar_x); + } + } + + return retval; +} +#endif + +/* The user typed output_len multibyte characters. Add them to the + * statusbar prompt, setting got_enter to TRUE if we get a newline, and + * filtering out all ASCII control characters if allow_cntrls is + * TRUE. */ +void do_statusbar_output(char *output, size_t output_len, bool + *got_enter, bool allow_cntrls) +{ + size_t answer_len, i = 0; + char *char_buf = charalloc(mb_cur_max()); + int char_buf_len; + + assert(answer != NULL); + + answer_len = strlen(answer); + *got_enter = FALSE; + + while (i < output_len) { + /* If allow_cntrls is TRUE, convert nulls and newlines + * properly. */ + if (allow_cntrls) { + /* Null to newline, if needed. */ + if (output[i] == '\0') + output[i] = '\n'; + /* Newline to Enter, if needed. */ + else if (output[i] == '\n') { + /* Set got_enter to TRUE to indicate that we got the + * Enter key, put back the rest of the characters in + * output so that they can be parsed and output again, + * and get out. */ + *got_enter = TRUE; + unparse_kbinput(output + i, output_len - i); + return; + } + } + + /* Interpret the next multibyte character. */ + char_buf_len = parse_mbchar(output + i, char_buf, NULL); + + i += char_buf_len; + + /* If allow_cntrls is FALSE, filter out an ASCII control + * character. */ + if (!allow_cntrls && is_ascii_cntrl_char(*(output + i - + char_buf_len))) + continue; + + /* More dangerousness fun =) */ + answer = charealloc(answer, answer_len + (char_buf_len * 2)); + + assert(statusbar_x <= answer_len); + + charmove(answer + statusbar_x + char_buf_len, + answer + statusbar_x, answer_len - statusbar_x + + char_buf_len); + strncpy(answer + statusbar_x, char_buf, char_buf_len); + answer_len += char_buf_len; + + statusbar_x += char_buf_len; + } + + free(char_buf); + + statusbar_pww = statusbar_xplustabs(); + + update_statusbar_line(answer, statusbar_x); +} + +/* Move to the beginning of the prompt text. If the SMART_HOME flag is + * set, move to the first non-whitespace character of the prompt text if + * we're not already there, or to the beginning of the prompt text if we + * are. */ +void do_statusbar_home(void) +{ + size_t pww_save = statusbar_pww; + +#ifndef NANO_TINY + if (ISSET(SMART_HOME)) { + size_t statusbar_x_save = statusbar_x; + + statusbar_x = indent_length(answer); + + if (statusbar_x == statusbar_x_save || + statusbar_x == strlen(answer)) + statusbar_x = 0; + + statusbar_pww = statusbar_xplustabs(); + } else { +#endif + statusbar_x = 0; + statusbar_pww = statusbar_xplustabs(); +#ifndef NANO_TINY + } +#endif + + if (need_statusbar_horizontal_update(pww_save)) + update_statusbar_line(answer, statusbar_x); +} + +/* Move to the end of the prompt text. */ +void do_statusbar_end(void) +{ + size_t pww_save = statusbar_pww; + + statusbar_x = strlen(answer); + statusbar_pww = statusbar_xplustabs(); + + if (need_statusbar_horizontal_update(pww_save)) + update_statusbar_line(answer, statusbar_x); +} + +/* Move left one character. */ +void do_statusbar_left(void) +{ + if (statusbar_x > 0) { + size_t pww_save = statusbar_pww; + + statusbar_x = move_mbleft(answer, statusbar_x); + statusbar_pww = statusbar_xplustabs(); + + if (need_statusbar_horizontal_update(pww_save)) + update_statusbar_line(answer, statusbar_x); + } +} + +/* Move right one character. */ +void do_statusbar_right(void) +{ + if (statusbar_x < strlen(answer)) { + size_t pww_save = statusbar_pww; + + statusbar_x = move_mbright(answer, statusbar_x); + statusbar_pww = statusbar_xplustabs(); + + if (need_statusbar_horizontal_update(pww_save)) + update_statusbar_line(answer, statusbar_x); + } +} + +/* Backspace over one character. */ +void do_statusbar_backspace(void) +{ + if (statusbar_x > 0) { + do_statusbar_left(); + do_statusbar_delete(); + } +} + +/* Delete one character. */ +void do_statusbar_delete(void) +{ + statusbar_pww = statusbar_xplustabs(); + + if (answer[statusbar_x] != '\0') { + int char_buf_len = parse_mbchar(answer + statusbar_x, NULL, + NULL); + size_t line_len = strlen(answer + statusbar_x); + + assert(statusbar_x < strlen(answer)); + + charmove(answer + statusbar_x, answer + statusbar_x + + char_buf_len, strlen(answer) - statusbar_x - + char_buf_len + 1); + + null_at(&answer, statusbar_x + line_len - char_buf_len); + + update_statusbar_line(answer, statusbar_x); + } +} + +/* Move text from the prompt into oblivion. */ +void do_statusbar_cut_text(void) +{ + assert(answer != NULL); + +#ifndef NANO_TINY + if (ISSET(CUT_TO_END)) + null_at(&answer, statusbar_x); + else { +#endif + null_at(&answer, 0); + statusbar_x = 0; + statusbar_pww = statusbar_xplustabs(); +#ifndef NANO_TINY + } +#endif + + update_statusbar_line(answer, statusbar_x); +} + +#ifndef NANO_TINY +/* Move to the next word in the prompt text. If allow_punct is TRUE, + * treat punctuation as part of a word. Return TRUE if we started on a + * word, and FALSE otherwise. */ +bool do_statusbar_next_word(bool allow_punct) +{ + size_t pww_save = statusbar_pww; + char *char_mb; + int char_mb_len; + bool end_line = FALSE, started_on_word = FALSE; + + assert(answer != NULL); + + char_mb = charalloc(mb_cur_max()); + + /* Move forward until we find the character after the last letter of + * the current word. */ + while (!end_line) { + char_mb_len = parse_mbchar(answer + statusbar_x, char_mb, NULL); + + /* If we've found it, stop moving forward through the current + * line. */ + if (!is_word_mbchar(char_mb, allow_punct)) + break; + + /* If we haven't found it, then we've started on a word, so set + * started_on_word to TRUE. */ + started_on_word = TRUE; + + if (answer[statusbar_x] == '\0') + end_line = TRUE; + else + statusbar_x += char_mb_len; + } + + /* Move forward until we find the first letter of the next word. */ + if (answer[statusbar_x] == '\0') + end_line = TRUE; + else + statusbar_x += char_mb_len; + + while (!end_line) { + char_mb_len = parse_mbchar(answer + statusbar_x, char_mb, NULL); + + /* If we've found it, stop moving forward through the current + * line. */ + if (is_word_mbchar(char_mb, allow_punct)) + break; + + if (answer[statusbar_x] == '\0') + end_line = TRUE; + else + statusbar_x += char_mb_len; + } + + free(char_mb); + + statusbar_pww = statusbar_xplustabs(); + + if (need_statusbar_horizontal_update(pww_save)) + update_statusbar_line(answer, statusbar_x); + + /* Return whether we started on a word. */ + return started_on_word; +} + +/* Move to the previous word in the prompt text. If allow_punct is + * TRUE, treat punctuation as part of a word. Return TRUE if we started + * on a word, and FALSE otherwise. */ +bool do_statusbar_prev_word(bool allow_punct) +{ + size_t pww_save = statusbar_pww; + char *char_mb; + int char_mb_len; + bool begin_line = FALSE, started_on_word = FALSE; + + assert(answer != NULL); + + char_mb = charalloc(mb_cur_max()); + + /* Move backward until we find the character before the first letter + * of the current word. */ + while (!begin_line) { + char_mb_len = parse_mbchar(answer + statusbar_x, char_mb, NULL); + + /* If we've found it, stop moving backward through the current + * line. */ + if (!is_word_mbchar(char_mb, allow_punct)) + break; + + /* If we haven't found it, then we've started on a word, so set + * started_on_word to TRUE. */ + started_on_word = TRUE; + + if (statusbar_x == 0) + begin_line = TRUE; + else + statusbar_x = move_mbleft(answer, statusbar_x); + } + + /* Move backward until we find the last letter of the previous + * word. */ + if (statusbar_x == 0) + begin_line = TRUE; + else + statusbar_x = move_mbleft(answer, statusbar_x); + + while (!begin_line) { + char_mb_len = parse_mbchar(answer + statusbar_x, char_mb, NULL); + + /* If we've found it, stop moving backward through the current + * line. */ + if (is_word_mbchar(char_mb, allow_punct)) + break; + + if (statusbar_x == 0) + begin_line = TRUE; + else + statusbar_x = move_mbleft(answer, statusbar_x); + } + + /* If we've found it, move backward until we find the character + * before the first letter of the previous word. */ + if (!begin_line) { + if (statusbar_x == 0) + begin_line = TRUE; + else + statusbar_x = move_mbleft(answer, statusbar_x); + + while (!begin_line) { + char_mb_len = parse_mbchar(answer + statusbar_x, char_mb, + NULL); + + /* If we've found it, stop moving backward through the + * current line. */ + if (!is_word_mbchar(char_mb, allow_punct)) + break; + + if (statusbar_x == 0) + begin_line = TRUE; + else + statusbar_x = move_mbleft(answer, statusbar_x); + } + + /* If we've found it, move forward to the first letter of the + * previous word. */ + if (!begin_line) + statusbar_x += char_mb_len; + } + + free(char_mb); + + statusbar_pww = statusbar_xplustabs(); + + if (need_statusbar_horizontal_update(pww_save)) + update_statusbar_line(answer, statusbar_x); + + /* Return whether we started on a word. */ + return started_on_word; +} +#endif /* !NANO_TINY */ + +/* Get verbatim input. Set got_enter to TRUE if we got the Enter key as + * part of the verbatim input. */ +void do_statusbar_verbatim_input(bool *got_enter) +{ + int *kbinput; + size_t kbinput_len, i; + char *output; + + *got_enter = FALSE; + + /* Read in all the verbatim characters. */ + kbinput = get_verbatim_kbinput(bottomwin, &kbinput_len); + + /* Display all the verbatim characters at once, not filtering out + * control characters. */ + output = charalloc(kbinput_len + 1); + + for (i = 0; i < kbinput_len; i++) + output[i] = (char)kbinput[i]; + output[i] = '\0'; + + do_statusbar_output(output, kbinput_len, got_enter, TRUE); + + free(output); +} + +#ifndef NANO_TINY +/* Search for a match to one of the two characters in bracket_set. If + * reverse is TRUE, search backwards for the leftmost bracket. + * Otherwise, search forwards for the rightmost bracket. Return TRUE if + * we found a match, and FALSE otherwise. */ +bool find_statusbar_bracket_match(bool reverse, const char + *bracket_set) +{ + const char *rev_start = NULL, *found = NULL; + + assert(mbstrlen(bracket_set) == 2); + + /* rev_start might end up 1 character before the start or after the + * end of the line. This won't be a problem because we'll skip over + * it below in that case. */ + rev_start = reverse ? answer + (statusbar_x - 1) : answer + + (statusbar_x + 1); + + while (TRUE) { + /* Look for either of the two characters in bracket_set. + * rev_start can be 1 character before the start or after the + * end of the line. In either case, just act as though no match + * is found. */ + found = ((rev_start > answer && *(rev_start - 1) == '\0') || + rev_start < answer) ? NULL : (reverse ? + mbrevstrpbrk(answer, bracket_set, rev_start) : + mbstrpbrk(rev_start, bracket_set)); + + /* We've found a potential match. */ + if (found != NULL) + break; + + /* We've reached the start or end of the statusbar text, so + * get out. */ + return FALSE; + } + + /* We've definitely found something. */ + statusbar_x = found - answer; + statusbar_pww = statusbar_xplustabs(); + + return TRUE; +} + +/* Search for a match to the bracket at the current cursor position, if + * there is one. */ +void do_statusbar_find_bracket(void) +{ + size_t statusbar_x_save, pww_save; + const char *ch; + /* The location in matchbrackets of the bracket at the current + * cursor position. */ + int ch_len; + /* The length of ch in bytes. */ + const char *wanted_ch; + /* The location in matchbrackets of the bracket complementing + * the bracket at the current cursor position. */ + int wanted_ch_len; + /* The length of wanted_ch in bytes. */ + char *bracket_set; + /* The pair of characters in ch and wanted_ch. */ + size_t i; + /* Generic loop variable. */ + size_t matchhalf; + /* The number of single-byte characters in one half of + * matchbrackets. */ + size_t mbmatchhalf; + /* The number of multibyte characters in one half of + * matchbrackets. */ + size_t count = 1; + /* The initial bracket count. */ + bool reverse; + /* The direction we search. */ + char *found_ch; + /* The character we find. */ + + assert(mbstrlen(matchbrackets) % 2 == 0); + + ch = answer + statusbar_x; + + if (ch == '\0' || (ch = mbstrchr(matchbrackets, ch)) == NULL) + return; + + /* Save where we are. */ + statusbar_x_save = statusbar_x; + pww_save = statusbar_pww; + + /* If we're on an opening bracket, which must be in the first half + * of matchbrackets, we want to search forwards for a closing + * bracket. If we're on a closing bracket, which must be in the + * second half of matchbrackets, we want to search backwards for an + * opening bracket. */ + matchhalf = 0; + mbmatchhalf = mbstrlen(matchbrackets) / 2; + + for (i = 0; i < mbmatchhalf; i++) + matchhalf += parse_mbchar(matchbrackets + matchhalf, NULL, + NULL); + + reverse = ((ch - matchbrackets) >= matchhalf); + + /* If we're on an opening bracket, set wanted_ch to the character + * that's matchhalf characters after ch. If we're on a closing + * bracket, set wanted_ch to the character that's matchhalf + * characters before ch. */ + wanted_ch = ch; + + while (mbmatchhalf > 0) { + if (reverse) + wanted_ch = matchbrackets + move_mbleft(matchbrackets, + wanted_ch - matchbrackets); + else + wanted_ch += move_mbright(wanted_ch, 0); + + mbmatchhalf--; + } + + ch_len = parse_mbchar(ch, NULL, NULL); + wanted_ch_len = parse_mbchar(wanted_ch, NULL, NULL); + + /* Fill bracket_set in with the values of ch and wanted_ch. */ + bracket_set = charalloc((mb_cur_max() * 2) + 1); + strncpy(bracket_set, ch, ch_len); + strncpy(bracket_set + ch_len, wanted_ch, wanted_ch_len); + null_at(&bracket_set, ch_len + wanted_ch_len); + + found_ch = charalloc(mb_cur_max() + 1); + + while (TRUE) { + if (find_statusbar_bracket_match(reverse, bracket_set)) { + /* If we found an identical bracket, increment count. If we + * found a complementary bracket, decrement it. */ + parse_mbchar(answer + statusbar_x, found_ch, NULL); + count += (strncmp(found_ch, ch, ch_len) == 0) ? 1 : -1; + + /* If count is zero, we've found a matching bracket. Update + * the statusbar prompt and get out. */ + if (count == 0) { + if (need_statusbar_horizontal_update(pww_save)) + update_statusbar_line(answer, statusbar_x); + break; + } + } else { + /* We didn't find either an opening or closing bracket. + * Restore where we were, and get out. */ + statusbar_x = statusbar_x_save; + statusbar_pww = pww_save; + break; + } + } + + /* Clean up. */ + free(bracket_set); + free(found_ch); +} +#endif /* !NANO_TINY */ + +/* Return the placewewant associated with statusbar_x, i.e. the + * zero-based column position of the cursor. The value will be no + * smaller than statusbar_x. */ +size_t statusbar_xplustabs(void) +{ + return strnlenpt(answer, statusbar_x); +} + +/* nano scrolls horizontally within a line in chunks. This function + * returns the column number of the first character displayed in the + * statusbar prompt when the cursor is at the given column with the + * prompt ending at start_col. Note that (0 <= column - + * get_statusbar_page_start(column) < COLS). */ +size_t get_statusbar_page_start(size_t start_col, size_t column) +{ + if (column == start_col || column < COLS - 1) + return 0; + else + return column - start_col - (column - start_col) % (COLS - + start_col - 1); +} + +/* Put the cursor in the statusbar prompt at statusbar_x. */ +void reset_statusbar_cursor(void) +{ + size_t start_col = strlenpt(prompt) + 2; + size_t xpt = statusbar_xplustabs(); + + wmove(bottomwin, 0, start_col + xpt - + get_statusbar_page_start(start_col, start_col + xpt)); +} + +/* Repaint the statusbar when getting a character in + * get_prompt_string(). The statusbar text line will be displayed + * starting with curranswer[index]. */ +void update_statusbar_line(const char *curranswer, size_t index) +{ + size_t start_col, page_start; + char *expanded; + + assert(prompt != NULL && index <= strlen(curranswer)); + + start_col = strlenpt(prompt) + 2; + index = strnlenpt(curranswer, index); + page_start = get_statusbar_page_start(start_col, start_col + index); + + wattron(bottomwin, reverse_attr); + + blank_statusbar(); + + mvwaddnstr(bottomwin, 0, 0, prompt, actual_x(prompt, COLS - 2)); + waddch(bottomwin, ':'); + waddch(bottomwin, (page_start == 0) ? ' ' : '$'); + + expanded = display_string(curranswer, page_start, COLS - start_col - + 1, FALSE); + waddstr(bottomwin, expanded); + free(expanded); + + wattroff(bottomwin, reverse_attr); + statusbar_pww = statusbar_xplustabs(); + reset_statusbar_cursor(); + wnoutrefresh(bottomwin); +} + +/* Return TRUE if we need an update after moving horizontally, and FALSE + * otherwise. We need one if pww_save and statusbar_pww are on + * different pages. */ +bool need_statusbar_horizontal_update(size_t pww_save) +{ + size_t start_col = strlenpt(prompt) + 2; + + return get_statusbar_page_start(start_col, start_col + pww_save) != + get_statusbar_page_start(start_col, start_col + statusbar_pww); +} + +/* Unconditionally redraw the entire screen, and then refresh it using + * refresh_func(). */ +void total_statusbar_refresh(void (*refresh_func)(void)) +{ + total_redraw(); + refresh_func(); +} + +/* Get a string of input at the statusbar prompt. This should only be + * called from do_prompt(). */ +const sc *get_prompt_string(int *actual, bool allow_tabs, +#ifndef DISABLE_TABCOMP + bool allow_files, +#endif + const char *curranswer, + bool *meta_key, bool *func_key, +#ifndef NANO_TINY + filestruct **history_list, +#endif + void (*refresh_func)(void), int menu +#ifndef DISABLE_TABCOMP + , bool *list +#endif + ) +{ + int kbinput = ERR; + bool have_shortcut, ran_func, finished; + size_t curranswer_len; + const sc *s; +#ifndef DISABLE_TABCOMP + bool tabbed = FALSE; + /* Whether we've pressed Tab. */ +#endif +#ifndef NANO_TINY + char *history = NULL; + /* The current history string. */ + char *magichistory = NULL; + /* The temporary string typed at the bottom of the history, if + * any. */ +#ifndef DISABLE_TABCOMP + int last_kbinput = ERR; + /* The key we pressed before the current key. */ + size_t complete_len = 0; + /* The length of the original string that we're trying to + * tab complete, if any. */ +#endif +#endif /* !NANO_TINY */ + + answer = mallocstrcpy(answer, curranswer); + curranswer_len = strlen(answer); + + /* If reset_statusbar_x is TRUE, restore statusbar_x and + * statusbar_pww to what they were before this prompt. Then, if + * statusbar_x is uninitialized or past the end of curranswer, put + * statusbar_x at the end of the string and update statusbar_pww + * based on it. We do these things so that the cursor position + * stays at the right place if a prompt-changing toggle is pressed, + * or if this prompt was started from another prompt and we cancel + * out of it. */ + if (reset_statusbar_x) { + statusbar_x = old_statusbar_x; + statusbar_pww = old_pww; + } + + if (statusbar_x == (size_t)-1 || statusbar_x > curranswer_len) { + statusbar_x = curranswer_len; + statusbar_pww = statusbar_xplustabs(); + } + + currmenu = menu; + +#ifdef DEBUG +fprintf(stderr, "get_prompt_string: answer = \"%s\", statusbar_x = %lu\n", answer, (unsigned long) statusbar_x); +#endif + + update_statusbar_line(answer, statusbar_x); + + /* Refresh the edit window and the statusbar before getting + * input. */ + wnoutrefresh(edit); + wnoutrefresh(bottomwin); + + /* If we're using restricted mode, we aren't allowed to change the + * name of the current file once it has one, because that would + * allow writing to files not specified on the command line. In + * this case, disable all keys that would change the text if the + * filename isn't blank and we're at the "Write File" prompt. */ + while (1) { + kbinput = do_statusbar_input(meta_key, func_key, &have_shortcut, + &ran_func, &finished, TRUE, refresh_func); + assert(statusbar_x <= strlen(answer)); + + s = get_shortcut(currmenu, &kbinput, meta_key, func_key); + + if (s) + if (s->scfunc == do_cancel || s->scfunc == do_enter_void) + break; + +#ifndef DISABLE_TABCOMP + if (s && s->scfunc != do_tab) + tabbed = FALSE; +#endif + +#ifndef DISABLE_TABCOMP +#ifndef NANO_TINY + if (s && s->scfunc == do_tab) { + if (history_list != NULL) { + if (last_kbinput != sc_seq_or(do_tab, NANO_CONTROL_I)) + complete_len = strlen(answer); + + if (complete_len > 0) { + answer = mallocstrcpy(answer, + get_history_completion(history_list, + answer, complete_len)); + statusbar_x = strlen(answer); + } + } else +#endif /* !NANO_TINY */ + if (allow_tabs) + answer = input_tab(answer, allow_files, + &statusbar_x, &tabbed, refresh_func, list); + + update_statusbar_line(answer, statusbar_x); + } else +#endif /* !DISABLE_TABCOMP */ +#ifndef NANO_TINY + if (s && s->scfunc == get_history_older_void) { + if (history_list != NULL) { + /* If we're scrolling up at the bottom of the + * history list and answer isn't blank, save answer + * in magichistory. */ + if ((*history_list)->next == NULL && + answer[0] != '\0') + magichistory = mallocstrcpy(magichistory, + answer); + + /* Get the older search from the history list and + * save it in answer. If there is no older search, + * don't do anything. */ + if ((history = + get_history_older(history_list)) != NULL) { + answer = mallocstrcpy(answer, history); + statusbar_x = strlen(answer); + } + + update_statusbar_line(answer, statusbar_x); + + /* This key has a shortcut list entry when it's used + * to move to an older search, which means that + * finished has been set to TRUE. Set it back to + * FALSE here, so that we aren't kicked out of the + * statusbar prompt. */ + finished = FALSE; + } + } else if (s && s->scfunc == get_history_newer_void) { + if (history_list != NULL) { + /* Get the newer search from the history list and + * save it in answer. If there is no newer search, + * don't do anything. */ + if ((history = + get_history_newer(history_list)) != NULL) { + answer = mallocstrcpy(answer, history); + statusbar_x = strlen(answer); + } + + /* If, after scrolling down, we're at the bottom of + * the history list, answer is blank, and + * magichistory is set, save magichistory in + * answer. */ + if ((*history_list)->next == NULL && + *answer == '\0' && magichistory != NULL) { + answer = mallocstrcpy(answer, magichistory); + statusbar_x = strlen(answer); + } + + update_statusbar_line(answer, statusbar_x); + + /* This key has a shortcut list entry when it's used + * to move to a newer search, which means that + * finished has been set to TRUE. Set it back to + * FALSE here, so that we aren't kicked out of the + * statusbar prompt. */ + finished = FALSE; + } + } else +#endif /* !NANO_TINY */ + if (s && s->scfunc == do_help_void) { + update_statusbar_line(answer, statusbar_x); + + /* This key has a shortcut list entry when it's used to + * go to the help browser or display a message + * indicating that help is disabled, which means that + * finished has been set to TRUE. Set it back to FALSE + * here, so that we aren't kicked out of the statusbar + * prompt. */ + finished = FALSE; + } + + /* If we have a shortcut with an associated function, break out + * if we're finished after running or trying to run the + * function. */ + if (finished) + break; + +#if !defined(NANO_TINY) && !defined(DISABLE_TABCOMP) + last_kbinput = kbinput; +#endif + + reset_statusbar_cursor(); + wnoutrefresh(bottomwin); + } + + +#ifndef NANO_TINY + /* Set the current position in the history list to the bottom and + * free magichistory, if we need to. */ + if (history_list != NULL) { + history_reset(*history_list); + + if (magichistory != NULL) + free(magichistory); + } +#endif + + + /* We've finished putting in an answer or run a normal shortcut's + * associated function, so reset statusbar_x and statusbar_pww. If + * we've finished putting in an answer, reset the statusbar cursor + * position too. */ + if (s) { + if (s->scfunc == do_cancel || s->scfunc == do_enter_void || + ran_func) { + statusbar_x = old_statusbar_x; + statusbar_pww = old_pww; + + if (!ran_func) + reset_statusbar_x = TRUE; + /* Otherwise, we're still putting in an answer or a shortcut with + * an associated function, so leave the statusbar cursor position + * alone. */ + } else + reset_statusbar_x = FALSE; + } + + *actual = kbinput; + return s; +} + +/* Ask a question on the statusbar. The prompt will be stored in the + * static prompt, which should be NULL initially, and the answer will be + * stored in the answer global. Returns -1 on aborted enter, -2 on a + * blank string, and 0 otherwise, the valid shortcut key caught. + * curranswer is any editable text that we want to put up by default, + * and refresh_func is the function we want to call to refresh the edit + * window. + * + * The allow_tabs parameter indicates whether we should allow tabs to be + * interpreted. The allow_files parameter indicates whether we should + * allow all files (as opposed to just directories) to be tab + * completed. */ +int do_prompt(bool allow_tabs, +#ifndef DISABLE_TABCOMP + bool allow_files, +#endif + int menu, const char *curranswer, + bool *meta_key, bool *func_key, +#ifndef NANO_TINY + filestruct **history_list, +#endif + void (*refresh_func)(void), const char *msg, ...) +{ + va_list ap; + int retval; + const sc *s; +#ifndef DISABLE_TABCOMP + bool list = FALSE; +#endif + + /* prompt has been freed and set to NULL unless the user resized + * while at the statusbar prompt. */ + if (prompt != NULL) + free(prompt); + + prompt = charalloc(((COLS - 4) * mb_cur_max()) + 1); + + bottombars(menu); + + va_start(ap, msg); + vsnprintf(prompt, (COLS - 4) * mb_cur_max(), msg, ap); + va_end(ap); + null_at(&prompt, actual_x(prompt, COLS - 4)); + + s = get_prompt_string(&retval, allow_tabs, +#ifndef DISABLE_TABCOMP + allow_files, +#endif + curranswer, + meta_key, func_key, +#ifndef NANO_TINY + history_list, +#endif + refresh_func, menu +#ifndef DISABLE_TABCOMP + , &list +#endif + ); + + free(prompt); + prompt = NULL; + + /* We're done with the prompt, so save the statusbar cursor + * position. */ + old_statusbar_x = statusbar_x; + old_pww = statusbar_pww; + + /* If we left the prompt via Cancel or Enter, set the return value + * properly. */ + if (s && s->scfunc == do_cancel) + retval = -1; + else if (s && s->scfunc == do_enter_void) + retval = (*answer == '\0') ? -2 : 0; + + blank_statusbar(); + wnoutrefresh(bottomwin); + +#ifdef DEBUG + fprintf(stderr, "answer = \"%s\"\n", answer); +#endif + +#ifndef DISABLE_TABCOMP + /* If we've done tab completion, there might be a list of filename + * matches on the edit window at this point. Make sure that they're + * cleared off. */ + if (list) + refresh_func(); +#endif + + return retval; +} + +/* This function forces a reset of the statusbar cursor position. It + * should be called when we get out of all statusbar prompts. */ +void do_prompt_abort(void) +{ + /* Uninitialize the old cursor position in answer. */ + old_statusbar_x = (size_t)-1; + old_pww = (size_t)-1; + + reset_statusbar_x = TRUE; +} + +/* Ask a simple Yes/No (and optionally All) question, specified in msg, + * on the statusbar. Return 1 for Yes, 0 for No, 2 for All (if all is + * TRUE when passed in), and -1 for Cancel. */ +int do_yesno_prompt(bool all, const char *msg) +{ + int ok = -2, width = 16; + const char *yesstr; /* String of Yes characters accepted. */ + const char *nostr; /* Same for No. */ + const char *allstr; /* And All, surprise! */ + const sc *s; + int oldmenu = currmenu; + + assert(msg != NULL); + + /* yesstr, nostr, and allstr are strings of any length. Each string + * consists of all single-byte characters accepted as valid + * characters for that value. The first value will be the one + * displayed in the shortcuts. */ + /* TRANSLATORS: For the next three strings, if possible, specify + * the single-byte shortcuts for both your language and English. + * For example, in French: "OoYy" for "Oui". */ + yesstr = _("Yy"); + nostr = _("Nn"); + allstr = _("Aa"); + + if (!ISSET(NO_HELP)) { + char shortstr[3]; + /* Temp string for Yes, No, All. */ + + if (COLS < 32) + width = COLS / 2; + + /* Clear the shortcut list from the bottom of the screen. */ + blank_bottombars(); + + sprintf(shortstr, " %c", yesstr[0]); + wmove(bottomwin, 1, 0); + onekey(shortstr, _("Yes"), width); + + if (all) { + wmove(bottomwin, 1, width); + shortstr[1] = allstr[0]; + onekey(shortstr, _("All"), width); + } + + wmove(bottomwin, 2, 0); + shortstr[1] = nostr[0]; + onekey(shortstr, _("No"), width); + + wmove(bottomwin, 2, 16); + onekey("^C", _("Cancel"), width); + } + + wattron(bottomwin, reverse_attr); + + blank_statusbar(); + mvwaddnstr(bottomwin, 0, 0, msg, actual_x(msg, COLS - 1)); + + wattroff(bottomwin, reverse_attr); + + /* Refresh the edit window and the statusbar before getting + * input. */ + wnoutrefresh(edit); + wnoutrefresh(bottomwin); + + do { + int kbinput; + bool meta_key, func_key; +#ifndef DISABLE_MOUSE + int mouse_x, mouse_y; +#endif + + currmenu = MYESNO; + kbinput = get_kbinput(bottomwin, &meta_key, &func_key); + s = get_shortcut(currmenu, &kbinput, &meta_key, &func_key); + + if (s && s->scfunc == do_cancel) + ok = -1; +#ifndef DISABLE_MOUSE + else if (kbinput == KEY_MOUSE) { + /* We can click on the Yes/No/All shortcut list to + * select an answer. */ + if (get_mouseinput(&mouse_x, &mouse_y, FALSE) == 0 && + wmouse_trafo(bottomwin, &mouse_y, &mouse_x, + FALSE) && mouse_x < (width * 2) && + mouse_y > 0) { + int x = mouse_x / width; + /* Calculate the x-coordinate relative to the + * two columns of the Yes/No/All shortcuts in + * bottomwin. */ + int y = mouse_y - 1; + /* Calculate the y-coordinate relative to the + * beginning of the Yes/No/All shortcuts in + * bottomwin, i.e. with the sizes of topwin, + * edit, and the first line of bottomwin + * subtracted out. */ + + assert(0 <= x && x <= 1 && 0 <= y && y <= 1); + + /* x == 0 means they clicked Yes or No. y == 0 + * means Yes or All. */ + ok = -2 * x * y + x - y + 1; + + if (ok == 2 && !all) + ok = -2; + } + } +#endif /* !DISABLE_MOUSE */ + else if (s && s->scfunc == total_refresh) { + total_redraw(); + continue; + } else { + /* Look for the kbinput in the Yes, No and (optionally) + * All strings. */ + if (strchr(yesstr, kbinput) != NULL) + ok = 1; + else if (strchr(nostr, kbinput) != NULL) + ok = 0; + else if (all && strchr(allstr, kbinput) != NULL) + ok = 2; + } + } while (ok == -2); + + currmenu = oldmenu; + return ok; +} |