/* $Id$ */ /************************************************************************** * winio.c * * * * Copyright (C) 1999-2005 Chris Allegretta * * 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 2, 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. * * * **************************************************************************/ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "proto.h" static int *key_buffer = NULL; /* The default keystroke buffer, * containing all the keystrokes we have * at a given point. */ static size_t key_buffer_len = 0; /* The length of the default keystroke * buffer. */ static int statusblank = 0; /* The number of keystrokes left after * we call statusbar(), before we * actually blank the statusbar. */ static size_t statusbar_x = (size_t)-1; /* The cursor position in answer. */ static bool disable_cursorpos = FALSE; /* Should we temporarily disable * constant cursor position display? */ static bool resetstatuspos = FALSE; /* Should we reset the cursor position * at the statusbar prompt? */ /* Control character compatibility: * * - NANO_BACKSPACE_KEY is Ctrl-H, which is Backspace under ASCII, ANSI, * VT100, and VT220. * - NANO_TAB_KEY is Ctrl-I, which is Tab under ASCII, ANSI, VT100, * VT220, and VT320. * - NANO_ENTER_KEY is Ctrl-M, which is Enter under ASCII, ANSI, VT100, * VT220, and VT320. * - NANO_XON_KEY is Ctrl-Q, which is XON under ASCII, ANSI, VT100, * VT220, and VT320. * - NANO_XOFF_KEY is Ctrl-S, which is XOFF under ASCII, ANSI, VT100, * VT220, and VT320. * - NANO_CONTROL_8 is Ctrl-8 (Ctrl-?), which is Delete under ASCII, * ANSI, VT100, and VT220, and which is Backspace under VT320. * * Note: VT220 and VT320 also generate Esc [ 3 ~ for Delete. By * default, xterm assumes it's running on a VT320 and generates Ctrl-8 * (Ctrl-?) for Backspace and Esc [ 3 ~ for Delete. This causes * problems for VT100-derived terminals such as the FreeBSD console, * which expect Ctrl-H for Backspace and Ctrl-8 (Ctrl-?) for Delete, and * on which the VT320 sequences are translated by the keypad to KEY_DC * and [nothing]. We work around this conflict via the REBIND_DELETE * flag: if it's not set, we assume VT320 compatibility, and if it is, * we assume VT100 compatibility. Thanks to Lee Nelson and Wouter van * Hemel for helping work this conflict out. * * Escape sequence compatibility: * * We support escape sequences for ANSI, VT100, VT220, VT320, the Linux * console, the FreeBSD console, the Mach console (a.k.a. the Hurd * console), xterm, rxvt, and Eterm. Among these, there are several * conflicts and omissions, outlined as follows: * * - Tab on ANSI == PageUp on FreeBSD console; the former is omitted. * (Ctrl-I is also Tab on ANSI, which we already support.) * - PageDown on FreeBSD console == Center (5) on numeric keypad with * NumLock off on Linux console; the latter is omitted. (The editing * keypad key is more important to have working than the numeric * keypad key, because the latter has no value when NumLock is off.) * - F1 on FreeBSD console == the mouse key on xterm/rxvt/Eterm; the * latter is omitted. (Mouse input will only work properly if the * extended keypad value KEY_MOUSE is generated on mouse events * instead of the escape sequence.) * - F9 on FreeBSD console == PageDown on Mach console; the former is * omitted. (The editing keypad is more important to have working * than the function keys, because the functions of the former are not * arbitrary and the functions of the latter are.) * - F10 on FreeBSD console == PageUp on Mach console; the former is * omitted. (Same as above.) * - F13 on FreeBSD console == End on Mach console; the former is * omitted. (Same as above.) * - F15 on FreeBSD console == Shift-Up on rxvt/Eterm; the former is * omitted. (The arrow keys, with or without modifiers, are more * important to have working than the function keys, because the * functions of the former are not arbitrary and the functions of the * latter are.) * - F16 on FreeBSD console == Shift-Down on rxvt/Eterm; the former is * omitted. (Same as above.) * * Note that Center (5) on the numeric keypad with NumLock off can also * be the Begin key. */ #ifndef NANO_SMALL /* Reset all the input routines that rely on character sequences. */ void reset_kbinput(void) { parse_kbinput(NULL, NULL, NULL, TRUE); get_byte_kbinput(0, TRUE); get_word_kbinput(0, TRUE); } #endif /* Read in a sequence of keystrokes from win and save them in the * default keystroke buffer. This should only be called when the * default keystroke buffer is empty. */ void get_buffer(WINDOW *win) { int input; /* If the keystroke buffer isn't empty, get out. */ if (key_buffer != NULL) return; /* Read in the first character using blocking input. */ #ifndef NANO_SMALL allow_pending_sigwinch(TRUE); #endif input = wgetch(win); /* If we get ERR when using blocking input, it means that the input * source that we were using is gone, so die gracefully. */ if (input == ERR) handle_hupterm(0); #ifndef NANO_SMALL allow_pending_sigwinch(FALSE); #endif /* Increment the length of the keystroke buffer, save the value of * the keystroke in key, and set key_code to TRUE if the keystroke * is an extended keypad value or FALSE if it isn't. */ key_buffer_len++; key_buffer = (int *)nmalloc(sizeof(int)); key_buffer[0] = input; /* Read in the remaining characters using non-blocking input. */ nodelay(win, TRUE); while (TRUE) { #ifndef NANO_SMALL allow_pending_sigwinch(TRUE); #endif input = wgetch(win); /* If there aren't any more characters, stop reading. */ if (input == ERR) break; /* Otherwise, increment the length of the keystroke buffer, save * the value of the keystroke in key, and set key_code to TRUE * if the keystroke is an extended keypad value or FALSE if it * isn't. */ key_buffer_len++; key_buffer = (int *)nrealloc(key_buffer, key_buffer_len * sizeof(int)); key_buffer[key_buffer_len - 1] = input; #ifndef NANO_SMALL allow_pending_sigwinch(FALSE); #endif } /* Switch back to non-blocking input. */ nodelay(win, FALSE); #ifdef DEBUG fprintf(stderr, "get_buffer(): key_buffer_len = %lu\n", (unsigned long)key_buffer_len); #endif } /* Return the length of the default keystroke buffer. */ size_t get_buffer_len(void) { return key_buffer_len; } /* Add the contents of the keystroke buffer input to the default * keystroke buffer. */ void unget_input(int *input, size_t input_len) { #ifndef NANO_SMALL allow_pending_sigwinch(TRUE); allow_pending_sigwinch(FALSE); #endif /* If input is empty, get out. */ if (input_len == 0) return; /* If adding input would put the default keystroke buffer beyond * maximum capacity, only add enough of input to put it at maximum * capacity. */ if (key_buffer_len + input_len < key_buffer_len) input_len = (size_t)-1 - key_buffer_len; /* Add the length of input to the length of the default keystroke * buffer, and reallocate the default keystroke buffer so that it * has enough room for input. */ key_buffer_len += input_len; key_buffer = (int *)nrealloc(key_buffer, key_buffer_len * sizeof(int)); /* If the default keystroke buffer wasn't empty before, move its * beginning forward far enough so that we can add input to its * beginning. */ if (key_buffer_len > input_len) memmove(key_buffer + input_len, key_buffer, (key_buffer_len - input_len) * sizeof(int)); /* Copy input to the beginning of the default keystroke buffer. */ memcpy(key_buffer, input, input_len * sizeof(int)); } /* Put back the character stored in kbinput, putting it in byte range * beforehand. If meta_key is TRUE, put back the Escape character after * putting back kbinput. If func_key is TRUE, put back the function key * (a value outside byte range) without putting it in byte range. */ void unget_kbinput(int kbinput, bool meta_key, bool func_key) { if (!func_key) kbinput = (char)kbinput; unget_input(&kbinput, 1); if (meta_key) { kbinput = NANO_CONTROL_3; unget_input(&kbinput, 1); } } /* Try to read input_len characters from the default keystroke buffer. * If the default keystroke buffer is empty and win isn't NULL, try to * read in more characters from win and add them to the default * keystroke buffer before doing anything else. If the default * keystroke buffer is empty and win is NULL, return NULL. */ int *get_input(WINDOW *win, size_t input_len) { int *input; #ifndef NANO_SMALL allow_pending_sigwinch(TRUE); allow_pending_sigwinch(FALSE); #endif if (key_buffer_len == 0) { if (win != NULL) get_buffer(win); if (key_buffer_len == 0) return NULL; } /* If input_len is greater than the length of the default keystroke * buffer, only read the number of characters in the default * keystroke buffer. */ if (input_len > key_buffer_len) input_len = key_buffer_len; /* Subtract input_len from the length of the default keystroke * buffer, and allocate the keystroke buffer input so that it * has enough room for input_len keystrokes. */ key_buffer_len -= input_len; input = (int *)nmalloc(input_len * sizeof(int)); /* Copy input_len characters from the beginning of the default * keystroke buffer into input. */ memcpy(input, key_buffer, input_len * sizeof(int)); /* If the default keystroke buffer is empty, mark it as such. */ if (key_buffer_len == 0) { free(key_buffer); key_buffer = NULL; /* If the default keystroke buffer isn't empty, move its * beginning forward far enough back so that the keystrokes in input * are no longer at its beginning. */ } else { memmove(key_buffer, key_buffer + input_len, key_buffer_len * sizeof(int)); key_buffer = (int *)nrealloc(key_buffer, key_buffer_len * sizeof(int)); } return input; } /* Read in a single character. If it's ignored, swallow it and go on. * Otherwise, try to translate it from ASCII, meta key sequences, escape * sequences, and/or extended keypad values. Set meta_key to TRUE when * we get a meta key sequence, and set func_key to TRUE when we get an * extended keypad value. Supported extended keypad values consist of * [arrow key], Ctrl-[arrow key], Shift-[arrow key], Enter, Backspace, * the editing keypad (Insert, Delete, Home, End, PageUp, and PageDown), * the function keypad (F1-F16), and the numeric keypad with NumLock * off. Assume nodelay(win) is FALSE. */ int get_kbinput(WINDOW *win, bool *meta_key, bool *func_key) { int kbinput; /* Read in a character and interpret it. Continue doing this until * we get a recognized value or sequence. */ while ((kbinput = parse_kbinput(win, meta_key, func_key #ifndef NANO_SMALL , FALSE #endif )) == ERR); return kbinput; } /* Translate ASCII characters, extended keypad values, and escape * sequences into their corresponding key values. Set meta_key to TRUE * when we get a meta key sequence, and set func_key to TRUE when we get * a function key. Assume nodelay(win) is FALSE. */ int parse_kbinput(WINDOW *win, bool *meta_key, bool *func_key #ifndef NANO_SMALL , bool reset #endif ) { static int escapes = 0, byte_digits = 0; int *kbinput, retval = ERR; #ifndef NANO_SMALL if (reset) { escapes = 0; byte_digits = 0; return ERR; } #endif *meta_key = FALSE; *func_key = FALSE; /* Read in a character. */ while ((kbinput = get_input(win, 1)) == NULL); switch (*kbinput) { case ERR: break; case NANO_CONTROL_3: /* Increment the escape counter. */ escapes++; switch (escapes) { case 1: /* One escape: wait for more input. */ case 2: /* Two escapes: wait for more input. */ break; default: /* More than two escapes: reset the escape counter * and wait for more input. */ escapes = 0; } break; #if !defined(NANO_SMALL) && defined(KEY_RESIZE) /* Since we don't change the default SIGWINCH handler when * NANO_SMALL is defined, KEY_RESIZE is never generated. Also, * Slang and SunOS 5.7-5.9 don't support KEY_RESIZE. */ case KEY_RESIZE: break; #endif #ifdef PDCURSES case KEY_SHIFT_L: case KEY_SHIFT_R: case KEY_CONTROL_L: case KEY_CONTROL_R: case KEY_ALT_L: case KEY_ALT_R: break; #endif default: switch (escapes) { case 0: switch (*kbinput) { case NANO_CONTROL_8: retval = ISSET(REBIND_DELETE) ? NANO_DELETE_KEY : NANO_BACKSPACE_KEY; break; case KEY_DOWN: retval = NANO_NEXTLINE_KEY; break; case KEY_UP: retval = NANO_PREVLINE_KEY; break; case KEY_LEFT: retval = NANO_BACK_KEY; break; case KEY_RIGHT: retval = NANO_FORWARD_KEY; break; #ifdef KEY_HOME /* HP-UX 10 and 11 don't support KEY_HOME. */ case KEY_HOME: retval = NANO_HOME_KEY; break; #endif case KEY_BACKSPACE: retval = NANO_BACKSPACE_KEY; break; case KEY_DC: retval = ISSET(REBIND_DELETE) ? NANO_BACKSPACE_KEY : NANO_DELETE_KEY; break; case KEY_IC: retval = NANO_INSERTFILE_KEY; break; case KEY_NPAGE: retval = NANO_NEXTPAGE_KEY; break; case KEY_PPAGE: retval = NANO_PREVPAGE_KEY; break; case KEY_ENTER: retval = NANO_ENTER_KEY; break; case KEY_A1: /* Home (7) on numeric keypad * with NumLock off. */ retval = NANO_HOME_KEY; break; case KEY_A3: /* PageUp (9) on numeric keypad * with NumLock off. */ retval = NANO_PREVPAGE_KEY; break; case KEY_B2: /* Center (5) on numeric keypad * with NumLock off. */ break; case KEY_C1: /* End (1) on numeric keypad * with NumLock off. */ retval = NANO_END_KEY; break; case KEY_C3: /* PageDown (4) on numeric * keypad with NumLock off. */ retval = NANO_NEXTPAGE_KEY; break; #ifdef KEY_BEG /* Slang doesn't support KEY_BEG. */ case KEY_BEG: /* Center (5) on numeric keypad * with NumLock off. */ break; #endif #ifdef KEY_END /* HP-UX 10 and 11 don't support KEY_END. */ case KEY_END: retval = NANO_END_KEY; break; #endif #ifdef KEY_SUSPEND /* Slang doesn't support KEY_SUSPEND. */ case KEY_SUSPEND: retval = NANO_SUSPEND_KEY; break; #endif #ifdef KEY_SLEFT /* Slang doesn't support KEY_SLEFT. */ case KEY_SLEFT: retval = NANO_BACK_KEY; break; #endif #ifdef KEY_SRIGHT /* Slang doesn't support KEY_SRIGHT. */ case KEY_SRIGHT: retval = NANO_FORWARD_KEY; break; #endif default: retval = *kbinput; break; } break; case 1: /* One escape followed by a non-escape: escape * sequence mode. Reset the escape counter. If * there aren't any other keys waiting, we have a * meta key sequence, so set meta_key to TRUE and * save the lowercase version of the non-escape * character as the result. If there are other keys * waiting, we have a true escape sequence, so * interpret it. */ escapes = 0; if (get_buffer_len() == 0) { *meta_key = TRUE; retval = tolower(*kbinput); } else { int *seq; size_t seq_len; bool ignore_seq; /* Put back the non-escape character, get the * complete escape sequence, translate the * sequence into its corresponding key value, * and save that as the result. */ unget_input(kbinput, 1); seq_len = get_buffer_len(); seq = get_input(NULL, seq_len); retval = get_escape_seq_kbinput(seq, seq_len, &ignore_seq); /* If the escape sequence is unrecognized and * not ignored, put back all of its characters * except for the initial escape. */ if (retval == ERR && !ignore_seq) unget_input(seq, seq_len); free(seq); } break; case 2: /* Two escapes followed by one or more decimal * digits: byte sequence mode. If the word * sequence's range is limited to 2XX (the first * digit is in the '0' to '2' range and it's the * first digit, or it's in the '0' to '9' range and * it's not the first digit), increment the byte * sequence counter and interpret the digit. If the * byte sequence's range is not limited to 2XX, fall * through. */ if (('0' <= *kbinput && *kbinput <= '6' && byte_digits == 0) || ('0' <= *kbinput && *kbinput <= '9' && byte_digits > 0)) { int byte; byte_digits++; byte = get_byte_kbinput(*kbinput #ifndef NANO_SMALL , FALSE #endif ); if (byte != ERR) { char *byte_mb; int byte_mb_len, *seq, i; /* If we've read in a complete byte * sequence, reset the byte sequence counter * and the escape counter, and put back the * corresponding byte value. */ byte_digits = 0; escapes = 0; /* Put back the multibyte equivalent of the * byte value. */ byte_mb = make_mbchar(byte, &byte_mb_len); seq = (int *)nmalloc(byte_mb_len * sizeof(int)); for (i = 0; i < byte_mb_len; i++) seq[i] = (unsigned char)byte_mb[i]; unget_input(seq, byte_mb_len); free(byte_mb); free(seq); } } else { /* Reset the escape counter. */ escapes = 0; if (byte_digits == 0) /* Two escapes followed by a non-decimal * digit or a decimal digit that would * create a byte sequence greater than 2XX, * and we're not in the middle of a byte * sequence: control character sequence * mode. Interpret the control sequence and * save the corresponding control character * as the result. */ retval = get_control_kbinput(*kbinput); else { /* If we're in the middle of a byte * sequence, reset the byte sequence counter * and save the character we got as the * result. */ byte_digits = 0; retval = *kbinput; } } break; } } /* If we have a result and it's an extended keypad value (i.e, a * value outside of byte range), set func_key to TRUE. */ if (retval != ERR) *func_key = !is_byte(retval); #ifdef DEBUG fprintf(stderr, "parse_kbinput(): kbinput = %d, meta_key = %d, func_key = %d, escapes = %d, byte_digits = %d, retval = %d\n", *kbinput, (int)*meta_key, (int)*func_key, escapes, byte_digits, retval); #endif /* Return the result. */ return retval; } /* Translate escape sequences, most of which correspond to extended * keypad values, into their corresponding key values. These sequences * are generated when the keypad doesn't support the needed keys. If * the escape sequence is recognized but we want to ignore it, return * ERR and set ignore_seq to TRUE; if it's unrecognized, return ERR and * set ignore_seq to FALSE. Assume that Escape has already been read * in. */ int get_escape_seq_kbinput(const int *seq, size_t seq_len, bool *ignore_seq) { int retval = ERR; *ignore_seq = FALSE; if (seq_len > 1) { switch (seq[0]) { case 'O': switch (seq[1]) { case '2': if (seq_len >= 3) { switch (seq[2]) { case 'P': /* Esc O 2 P == F13 on * xterm. */ retval = KEY_F(13); break; case 'Q': /* Esc O 2 Q == F14 on * xterm. */ retval = KEY_F(14); break; case 'R': /* Esc O 2 R == F15 on * xterm. */ retval = KEY_F(15); break; case 'S': /* Esc O 2 S == F16 on * xterm. */ retval = KEY_F(16); break; } } break; case 'A': /* Esc O A == Up on VT100/VT320/xterm. */ case 'B': /* Esc O B == Down on * VT100/VT320/xterm. */ case 'C': /* Esc O C == Right on * VT100/VT320/xterm. */ case 'D': /* Esc O D == Left on * VT100/VT320/xterm. */ retval = get_escape_seq_abcd(seq[1]); break; case 'E': /* Esc O E == Center (5) on numeric keypad * with NumLock off on xterm. */ *ignore_seq = TRUE; break; case 'F': /* Esc O F == End on xterm. */ retval = NANO_END_KEY; break; case 'H': /* Esc O H == Home on xterm. */ retval = NANO_HOME_KEY; break; case 'M': /* Esc O M == Enter on numeric keypad with * NumLock off on VT100/VT220/VT320/xterm/ * Eterm. */ retval = NANO_ENTER_KEY; break; case 'P': /* Esc O P == F1 on VT100/VT220/VT320/Mach * console. */ retval = KEY_F(1); break; case 'Q': /* Esc O Q == F2 on VT100/VT220/VT320/Mach * console. */ retval = KEY_F(2); break; case 'R': /* Esc O R == F3 on VT100/VT220/VT320/Mach * console. */ retval = KEY_F(3); break; case 'S': /* Esc O S == F4 on VT100/VT220/VT320/Mach * console. */ retval = KEY_F(4); break; case 'T': /* Esc O T == F5 on Mach console. */ retval = KEY_F(5); break; case 'U': /* Esc O U == F6 on Mach console. */ retval = KEY_F(6); break; case 'V': /* Esc O V == F7 on Mach console. */ retval = KEY_F(7); break; case 'W': /* Esc O W == F8 on Mach console. */ retval = KEY_F(8); break; case 'X': /* Esc O X == F9 on Mach console. */ retval = KEY_F(9); break; case 'Y': /* Esc O Y == F10 on Mach console. */ retval = KEY_F(10); break; case 'a': /* Esc O a == Ctrl-Up on rxvt. */ case 'b': /* Esc O b == Ctrl-Down on rxvt. */ case 'c': /* Esc O c == Ctrl-Right on rxvt. */ case 'd': /* Esc O d == Ctrl-Left on rxvt. */ retval = get_escape_seq_abcd(seq[1]); break; case 'j': /* Esc O j == '*' on numeric keypad with * NumLock off on VT100/VT220/VT320/xterm/ * rxvt. */ retval = '*'; break; case 'k': /* Esc O k == '+' on numeric keypad with * NumLock off on VT100/VT220/VT320/xterm/ * rxvt. */ retval = '+'; break; case 'l': /* Esc O l == ',' on numeric keypad with * NumLock off on VT100/VT220/VT320/xterm/ * rxvt. */ retval = '+'; break; case 'm': /* Esc O m == '-' on numeric keypad with * NumLock off on VT100/VT220/VT320/xterm/ * rxvt. */ retval = '-'; break; case 'n': /* Esc O n == Delete (.) on numeric keypad * with NumLock off on VT100/VT220/VT320/ * xterm/rxvt. */ retval = NANO_DELETE_KEY; break; case 'o': /* Esc O o == '/' on numeric keypad with * NumLock off on VT100/VT220/VT320/xterm/ * rxvt. */ retval = '/'; break; case 'p': /* Esc O p == Insert (0) on numeric keypad * with NumLock off on VT100/VT220/VT320/ * rxvt. */ retval = NANO_INSERTFILE_KEY; break; case 'q': /* Esc O q == End (1) on numeric keypad * with NumLock off on VT100/VT220/VT320/ * rxvt. */ retval = NANO_END_KEY; break; case 'r': /* Esc O r == Down (2) on numeric keypad * with NumLock off on VT100/VT220/VT320/ * rxvt. */ retval = NANO_NEXTLINE_KEY; break; case 's': /* Esc O s == PageDown (3) on numeric * keypad with NumLock off on VT100/VT220/ * VT320/rxvt. */ retval = NANO_NEXTPAGE_KEY; break; case 't': /* Esc O t == Left (4) on numeric keypad * with NumLock off on VT100/VT220/VT320/ * rxvt. */ retval = NANO_BACK_KEY; break; case 'u': /* Esc O u == Center (5) on numeric keypad * with NumLock off on VT100/VT220/VT320/ * rxvt/Eterm. */ *ignore_seq = TRUE; break; case 'v': /* Esc O v == Right (6) on numeric keypad * with NumLock off on VT100/VT220/VT320/ * rxvt. */ retval = NANO_FORWARD_KEY; break; case 'w': /* Esc O w == Home (7) on numeric keypad * with NumLock off on VT100/VT220/VT320/ * rxvt. */ retval = NANO_HOME_KEY; break; case 'x': /* Esc O x == Up (8) on numeric keypad * with NumLock off on VT100/VT220/VT320/ * rxvt. */ retval = NANO_PREVLINE_KEY; break; case 'y': /* Esc O y == PageUp (9) on numeric keypad * with NumLock off on VT100/VT220/VT320/ * rxvt. */ retval = NANO_PREVPAGE_KEY; break; } break; case 'o': switch (seq[1]) { case 'a': /* Esc o a == Ctrl-Up on Eterm. */ case 'b': /* Esc o b == Ctrl-Down on Eterm. */ case 'c': /* Esc o c == Ctrl-Right on Eterm. */ case 'd': /* Esc o d == Ctrl-Left on Eterm. */ retval = get_escape_seq_abcd(seq[1]); break; } break; case '[': switch (seq[1]) { case '1': if (seq_len >= 3) { switch (seq[2]) { case '1': /* Esc [ 1 1 ~ == F1 on rxvt/ * Eterm. */ retval = KEY_F(1); break; case '2': /* Esc [ 1 2 ~ == F2 on rxvt/ * Eterm. */ retval = KEY_F(2); break; case '3': /* Esc [ 1 3 ~ == F3 on rxvt/ * Eterm. */ retval = KEY_F(3); break; case '4': /* Esc [ 1 4 ~ == F4 on rxvt/ * Eterm. */ retval = KEY_F(4); break; case '5': /* Esc [ 1 5 ~ == F5 on xterm/ * rxvt/Eterm. */ retval = KEY_F(5); break; case '7': /* Esc [ 1 7 ~ == F6 on * VT220/VT320/Linux console/ * xterm/rxvt/Eterm. */ retval = KEY_F(6); break; case '8': /* Esc [ 1 8 ~ == F7 on * VT220/VT320/Linux console/ * xterm/rxvt/Eterm. */ retval = KEY_F(7); break; case '9': /* Esc [ 1 9 ~ == F8 on * VT220/VT320/Linux console/ * xterm/rxvt/Eterm. */ retval = KEY_F(8); break; case ';': if (seq_len >= 4) { switch (seq[3]) { case '2': if (seq_len >= 5) { switch (seq[4]) { case 'A': /* Esc [ 1 ; 2 A == Shift-Up on * xterm. */ case 'B': /* Esc [ 1 ; 2 B == Shift-Down on * xterm. */ case 'C': /* Esc [ 1 ; 2 C == Shift-Right on * xterm. */ case 'D': /* Esc [ 1 ; 2 D == Shift-Left on * xterm. */ retval = get_escape_seq_abcd(seq[4]); break; } } break; case '5': if (seq_len >= 5) { switch (seq[4]) { case 'A': /* Esc [ 1 ; 5 A == Ctrl-Up on * xterm. */ case 'B': /* Esc [ 1 ; 5 B == Ctrl-Down on * xterm. */ case 'C': /* Esc [ 1 ; 5 C == Ctrl-Right on * xterm. */ case 'D': /* Esc [ 1 ; 5 D == Ctrl-Left on * xterm. */ retval = get_escape_seq_abcd(seq[4]); break; } } break; } } break; default: /* Esc [ 1 ~ == Home on * VT320/Linux console. */ retval = NANO_HOME_KEY; break; } } break; case '2': if (seq_len >= 3) { switch (seq[2]) { case '0': /* Esc [ 2 0 ~ == F9 on * VT220/VT320/Linux console/ * xterm/rxvt/Eterm. */ retval = KEY_F(9); break; case '1': /* Esc [ 2 1 ~ == F10 on * VT220/VT320/Linux console/ * xterm/rxvt/Eterm. */ retval = KEY_F(10); break; case '3': /* Esc [ 2 3 ~ == F11 on * VT220/VT320/Linux console/ * xterm/rxvt/Eterm. */ retval = KEY_F(11); break; case '4': /* Esc [ 2 4 ~ == F12 on * VT220/VT320/Linux console/ * xterm/rxvt/Eterm. */ retval = KEY_F(12); break; case '5': /* Esc [ 2 5 ~ == F13 on * VT220/VT320/Linux console/ * rxvt/Eterm. */ retval = KEY_F(13); break; case '6': /* Esc [ 2 6 ~ == F14 on * VT220/VT320/Linux console/ * rxvt/Eterm. */ retval = KEY_F(14); break; case '8': /* Esc [ 2 8 ~ == F15 on * VT220/VT320/Linux console/ * rxvt/Eterm. */ retval = KEY_F(15); break; case '9': /* Esc [ 2 9 ~ == F16 on * VT220/VT320/Linux console/ * rxvt/Eterm. */ retval = KEY_F(16); break; default: /* Esc [ 2 ~ == Insert on * VT220/VT320/Linux console/ * xterm. */ retval = NANO_INSERTFILE_KEY; break; } } break; case '3': /* Esc [ 3 ~ == Delete on VT220/VT320/ * Linux console/xterm. */ retval = NANO_DELETE_KEY; break; case '4': /* Esc [ 4 ~ == End on VT220/VT320/Linux * console/xterm. */ retval = NANO_END_KEY; break; case '5': /* Esc [ 5 ~ == PageUp on VT220/VT320/ * Linux console/xterm; Esc [ 5 ^ == * PageUp on Eterm. */ retval = NANO_PREVPAGE_KEY; break; case '6': /* Esc [ 6 ~ == PageDown on VT220/VT320/ * Linux console/xterm; Esc [ 6 ^ == * PageDown on Eterm. */ retval = NANO_NEXTPAGE_KEY; break; case '7': /* Esc [ 7 ~ == Home on rxvt. */ retval = NANO_HOME_KEY; break; case '8': /* Esc [ 8 ~ == End on rxvt. */ retval = NANO_END_KEY; break; case '9': /* Esc [ 9 == Delete on Mach console. */ retval = NANO_DELETE_KEY; break; case '@': /* Esc [ @ == Insert on Mach console. */ retval = NANO_INSERTFILE_KEY; break; case 'A': /* Esc [ A == Up on ANSI/VT220/Linux * console/FreeBSD console/Mach console/ * rxvt/Eterm. */ case 'B': /* Esc [ B == Down on ANSI/VT220/Linux * console/FreeBSD console/Mach console/ * rxvt/Eterm. */ case 'C': /* Esc [ C == Right on ANSI/VT220/Linux * console/FreeBSD console/Mach console/ * rxvt/Eterm. */ case 'D': /* Esc [ D == Left on ANSI/VT220/Linux * console/FreeBSD console/Mach console/ * rxvt/Eterm. */ retval = get_escape_seq_abcd(seq[1]); break; case 'E': /* Esc [ E == Center (5) on numeric keypad * with NumLock off on FreeBSD console. */ *ignore_seq = TRUE; break; case 'F': /* Esc [ F == End on FreeBSD * console/Eterm. */ retval = NANO_END_KEY; break; case 'G': /* Esc [ G == PageDown on FreeBSD * console. */ retval = NANO_NEXTPAGE_KEY; break; case 'H': /* Esc [ H == Home on ANSI/VT220/FreeBSD * console/Mach console/Eterm. */ retval = NANO_HOME_KEY; break; case 'I': /* Esc [ I == PageUp on FreeBSD * console. */ retval = NANO_PREVPAGE_KEY; break; case 'L': /* Esc [ L == Insert on ANSI/FreeBSD * console. */ retval = NANO_INSERTFILE_KEY; break; case 'M': /* Esc [ M == F1 on FreeBSD console. */ retval = KEY_F(1); break; case 'N': /* Esc [ N == F2 on FreeBSD console. */ retval = KEY_F(2); break; case 'O': if (seq_len >= 3) { switch (seq[2]) { case 'P': /* Esc [ O P == F1 on * xterm. */ retval = KEY_F(1); break; case 'Q': /* Esc [ O Q == F2 on * xterm. */ retval = KEY_F(2); break; case 'R': /* Esc [ O R == F3 on * xterm. */ retval = KEY_F(3); break; case 'S': /* Esc [ O S == F4 on * xterm. */ retval = KEY_F(4); break; } } else { /* Esc [ O == F3 on FreeBSD console. */ retval = KEY_F(3); } break; case 'P': /* Esc [ P == F4 on FreeBSD console. */ retval = KEY_F(4); break; case 'Q': /* Esc [ Q == F5 on FreeBSD console. */ retval = KEY_F(5); break; case 'R': /* Esc [ R == F6 on FreeBSD console. */ retval = KEY_F(6); break; case 'S': /* Esc [ S == F7 on FreeBSD console. */ retval = KEY_F(7); break; case 'T': /* Esc [ T == F8 on FreeBSD console. */ retval = KEY_F(8); break; case 'U': /* Esc [ U == PageDown on Mach console. */ retval = NANO_NEXTPAGE_KEY; break; case 'V': /* Esc [ V == PageUp on Mach console. */ retval = NANO_PREVPAGE_KEY; break; case 'W': /* Esc [ W == F11 on FreeBSD console. */ retval = KEY_F(11); break; case 'X': /* Esc [ X == F12 on FreeBSD console. */ retval = KEY_F(12); break; case 'Y': /* Esc [ Y == End on Mach console. */ retval = NANO_END_KEY; break; case 'Z': /* Esc [ Z == F14 on FreeBSD console. */ retval = KEY_F(14); break; case 'a': /* Esc [ a == Shift-Up on rxvt/Eterm. */ case 'b': /* Esc [ b == Shift-Down on rxvt/Eterm. */ case 'c': /* Esc [ c == Shift-Right on rxvt/ * Eterm. */ case 'd': /* Esc [ d == Shift-Left on rxvt/Eterm. */ retval = get_escape_seq_abcd(seq[1]); break; case '[': if (seq_len >= 3) { switch (seq[2]) { case 'A': /* Esc [ [ A == F1 on Linux * console. */ retval = KEY_F(1); break; case 'B': /* Esc [ [ B == F2 on Linux * console. */ retval = KEY_F(2); break; case 'C': /* Esc [ [ C == F3 on Linux * console. */ retval = KEY_F(3); break; case 'D': /* Esc [ [ D == F4 on Linux * console. */ retval = KEY_F(4); break; case 'E': /* Esc [ [ E == F5 on Linux * console. */ retval = KEY_F(5); break; } } break; } break; } } #ifdef DEBUG fprintf(stderr, "get_escape_seq_kbinput(): retval = %d, ignore_seq = %d\n", retval, (int)*ignore_seq); #endif return retval; } /* Return the equivalent arrow key value for the case-insensitive * letters A (up), B (down), C (right), and D (left). These are common * to many escape sequences. */ int get_escape_seq_abcd(int kbinput) { switch (tolower(kbinput)) { case 'a': return NANO_PREVLINE_KEY; case 'b': return NANO_NEXTLINE_KEY; case 'c': return NANO_FORWARD_KEY; case 'd': return NANO_BACK_KEY; default: return ERR; } } /* Translate a byte sequence: turn a three-digit decimal number from * 000 to 255 into its corresponding byte value. */ int get_byte_kbinput(int kbinput #ifndef NANO_SMALL , bool reset #endif ) { static int byte_digits = 0, byte = 0; int retval = ERR; #ifndef NANO_SMALL if (reset) { byte_digits = 0; byte = 0; return ERR; } #endif /* Increment the byte digit counter. */ byte_digits++; switch (byte_digits) { case 1: /* One digit: reset the byte sequence holder and add the * digit we got to the 100's position of the byte sequence * holder. */ byte = 0; if ('0' <= kbinput && kbinput <= '2') byte += (kbinput - '0') * 100; else /* If the character we got isn't a decimal digit, or if * it is and it would put the byte sequence out of byte * range, save it as the result. */ retval = kbinput; break; case 2: /* Two digits: add the digit we got to the 10's position of * the byte sequence holder. */ if (('0' <= kbinput && kbinput <= '5') || (byte < 200 && '6' <= kbinput && kbinput <= '9')) byte += (kbinput - '0') * 10; else /* If the character we got isn't a decimal digit, or if * it is and it would put the byte sequence out of byte * range, save it as the result. */ retval = kbinput; break; case 3: /* Three digits: add the digit we got to the 1's position of * the byte sequence holder, and save the corresponding word * value as the result. */ if (('0' <= kbinput && kbinput <= '5') || (byte < 250 && '6' <= kbinput && kbinput <= '9')) { byte += (kbinput - '0'); retval = byte; } else /* If the character we got isn't a decimal digit, or if * it is and it would put the word sequence out of word * range, save it as the result. */ retval = kbinput; break; default: /* More than three digits: save the character we got as the * result. */ retval = kbinput; break; } /* If we have a result, reset the byte digit counter and the byte * sequence holder. */ if (retval != ERR) { byte_digits = 0; byte = 0; } #ifdef DEBUG fprintf(stderr, "get_byte_kbinput(): kbinput = %d, byte_digits = %d, byte = %d, retval = %d\n", kbinput, byte_digits, byte, retval); #endif return retval; } /* Translate a word sequence: turn a four-digit hexadecimal number from * 0000 to ffff (case-insensitive) into its corresponding word value. */ int get_word_kbinput(int kbinput #ifndef NANO_SMALL , bool reset #endif ) { static int word_digits = 0, word = 0; int retval = ERR; #ifndef NANO_SMALL if (reset) { word_digits = 0; word = 0; return ERR; } #endif /* Increment the word digit counter. */ word_digits++; switch (word_digits) { case 1: /* One digit: reset the word sequence holder and add the * digit we got to the 4096's position of the word sequence * holder. */ word = 0; if ('0' <= kbinput && kbinput <= '9') word += (kbinput - '0') * 4096; else if ('a' <= tolower(kbinput) && tolower(kbinput) <= 'f') word += (tolower(kbinput) + 10 - 'a') * 4096; else /* If the character we got isn't a hexadecimal digit, or * if it is and it would put the word sequence out of * word range, save it as the result. */ retval = kbinput; break; case 2: /* Two digits: add the digit we got to the 256's position of * the word sequence holder. */ if ('0' <= kbinput && kbinput <= '9') word += (kbinput - '0') * 256; else if ('a' <= tolower(kbinput) && tolower(kbinput) <= 'f') word += (tolower(kbinput) + 10 - 'a') * 256; else /* If the character we got isn't a hexadecimal digit, or * if it is and it would put the word sequence out of * word range, save it as the result. */ retval = kbinput; break; case 3: /* Three digits: add the digit we got to the 16's position * of the word sequence holder. */ if ('0' <= kbinput && kbinput <= '9') word += (kbinput - '0') * 16; else if ('a' <= tolower(kbinput) && tolower(kbinput) <= 'f') word += (tolower(kbinput) + 10 - 'a') * 16; else /* If the character we got isn't a hexadecimal digit, or * if it is and it would put the word sequence out of * word range, save it as the result. */ retval = kbinput; break; case 4: /* Four digits: add the digit we got to the 1's position of * the word sequence holder, and save the corresponding word * value as the result. */ if ('0' <= kbinput && kbinput <= '9') { word += (kbinput - '0'); retval = word; } else if ('a' <= tolower(kbinput) && tolower(kbinput) <= 'f') { word += (tolower(kbinput) + 10 - 'a'); retval = word; } else /* If the character we got isn't a hexadecimal digit, or * if it is and it would put the word sequence out of * word range, save it as the result. */ retval = kbinput; break; default: /* More than four digits: save the character we got as the * result. */ retval = kbinput; break; } /* If we have a result, reset the word digit counter and the word * sequence holder. */ if (retval != ERR) { word_digits = 0; word = 0; } #ifdef DEBUG fprintf(stderr, "get_word_kbinput(): kbinput = %d, word_digits = %d, word = %d, retval = %d\n", kbinput, word_digits, word, retval); #endif return retval; } /* Translate a control character sequence: turn an ASCII non-control * character into its corresponding control character. */ int get_control_kbinput(int kbinput) { int retval; /* Ctrl-2 (Ctrl-Space, Ctrl-@, Ctrl-`) */ if (kbinput == '2' || kbinput == ' ' || kbinput == '@' || kbinput == '`') retval = NANO_CONTROL_SPACE; /* Ctrl-3 (Ctrl-[, Esc) to Ctrl-7 (Ctrl-_) */ else if ('3' <= kbinput && kbinput <= '7') retval = kbinput - 24; /* Ctrl-8 (Ctrl-?) */ else if (kbinput == '8' || kbinput == '?') retval = NANO_CONTROL_8; /* Ctrl-A to Ctrl-_ */ else if ('A' <= kbinput && kbinput <= '_') retval = kbinput - 64; /* Ctrl-a to Ctrl-~ */ else if ('a' <= kbinput && kbinput <= '~') retval = kbinput - 96; else retval = kbinput; #ifdef DEBUG fprintf(stderr, "get_control_kbinput(): kbinput = %d, retval = %d\n", kbinput, retval); #endif return retval; } /* Put the output-formatted characters in output back into the default * keystroke buffer, so that they can be parsed and displayed as output * again. */ void unparse_kbinput(char *output, size_t output_len) { int *input; size_t i; if (output_len == 0) return; input = (int *)nmalloc(output_len * sizeof(int)); for (i = 0; i < output_len; i++) input[i] = (int)output[i]; unget_input(input, output_len); free(input); } /* Read in a stream of characters verbatim, and return the length of the * string in kbinput_len. Assume nodelay(win) is FALSE. */ int *get_verbatim_kbinput(WINDOW *win, size_t *kbinput_len) { int *retval; /* Turn off flow control characters if necessary so that we can type * them in verbatim, and turn the keypad off so that we don't get * extended keypad values. */ if (ISSET(PRESERVE)) disable_flow_control(); keypad(win, FALSE); /* Read in a stream of characters and interpret it if possible. */ retval = parse_verbatim_kbinput(win, kbinput_len); /* Turn flow control characters back on if necessary and turn the * keypad back on now that we're done. */ if (ISSET(PRESERVE)) enable_flow_control(); keypad(win, TRUE); return retval; } /* Read in a stream of all available characters, and return the length * of the string in kbinput_len. Translate the first few characters of * the input into the corresponding word value if possible. After that, * leave the input as-is. */ int *parse_verbatim_kbinput(WINDOW *win, size_t *kbinput_len) { int *kbinput, word, *retval; /* Read in the first keystroke. */ while ((kbinput = get_input(win, 1)) == NULL); /* Check whether the first keystroke is a hexadecimal digit. */ word = get_word_kbinput(*kbinput #ifndef NANO_SMALL , FALSE #endif ); /* If the first keystroke isn't a hexadecimal digit, put back the * first keystroke. */ if (word != ERR) unget_input(kbinput, 1); /* Otherwise, read in keystrokes until we have a complete word * sequence, and put back the corresponding word value. */ else { char *word_mb; int word_mb_len, *seq, i; while (word == ERR) { while ((kbinput = get_input(win, 1)) == NULL); word = get_word_kbinput(*kbinput #ifndef NANO_SMALL , FALSE #endif ); } /* Put back the multibyte equivalent of the word value. */ word_mb = make_mbchar(word, &word_mb_len); seq = (int *)nmalloc(word_mb_len * sizeof(int)); for (i = 0; i < word_mb_len; i++) seq[i] = (unsigned char)word_mb[i]; unget_input(seq, word_mb_len); free(seq); free(word_mb); } /* Get the complete sequence, and save the characters in it as the * result. */ *kbinput_len = get_buffer_len(); retval = get_input(NULL, *kbinput_len); return retval; } #ifndef DISABLE_MOUSE /* Check for a mouse event, and if one's taken place, save the * coordinates where it took place in mouse_x and mouse_y. After that, * assuming allow_shortcuts is FALSE, if the shortcut list on the * bottom two lines of the screen is visible and the mouse event took * place on it, figure out which shortcut was clicked and put back the * equivalent keystroke(s). Return FALSE if no keystrokes were * put back, or TRUE if at least one was. Assume that KEY_MOUSE has * already been read in. */ bool get_mouseinput(int *mouse_x, int *mouse_y, bool allow_shortcuts) { MEVENT mevent; *mouse_x = -1; *mouse_y = -1; /* First, get the actual mouse event. */ if (getmouse(&mevent) == ERR) return FALSE; /* Save the screen coordinates where the mouse event took place. */ *mouse_x = mevent.x; *mouse_y = mevent.y; /* If we're allowing shortcuts, the current shortcut list is being * displayed on the last two lines of the screen, and the mouse * event took place inside it, we need to figure out which shortcut * was clicked and put back the equivalent keystroke(s) for it. */ if (allow_shortcuts && !ISSET(NO_HELP) && wenclose(bottomwin, *mouse_y, *mouse_x)) { int i, j; size_t currslen; /* The number of shortcuts in the current shortcut list. */ const shortcut *s = currshortcut; /* The actual shortcut we clicked on, starting at the first * one in the current shortcut list. */ /* Get the shortcut lists' length. */ if (currshortcut == main_list) currslen = MAIN_VISIBLE; else { currslen = length_of_list(currshortcut); /* We don't show any more shortcuts than the main list * does. */ if (currslen > MAIN_VISIBLE) currslen = MAIN_VISIBLE; } /* Calculate the width of each shortcut in the list. It's the * same for all of them. */ if (currslen < 2) i = COLS / 6; else i = COLS / ((currslen / 2) + (currslen % 2)); /* Calculate the y-coordinate relative to the beginning of * bottomwin. */ j = *mouse_y - ((2 - no_more_space()) + 1) - editwinrows; /* If we're on the statusbar, beyond the end of the shortcut * list, or beyond the end of a shortcut on the right side of * the screen, don't do anything. */ if (j < 0 || (*mouse_x / i) >= currslen) return FALSE; j = (*mouse_x / i) * 2 + j; if (j >= currslen) return FALSE; /* Go through the shortcut list to determine which shortcut was * clicked. */ for (; j > 0; j--) s = s->next; /* And put back the equivalent key. Assume that each shortcut * has, at the very least, an equivalent control key, an * equivalent primary meta key sequence, or both. */ if (s->ctrlval != NANO_NO_KEY) { unget_kbinput(s->ctrlval, FALSE, FALSE); return TRUE; } else if (s->metaval != NANO_NO_KEY) { unget_kbinput(s->metaval, TRUE, FALSE); return TRUE; } } return FALSE; } #endif /* !DISABLE_MOUSE */ const shortcut *get_shortcut(const shortcut *s_list, int *kbinput, bool *meta_key, bool *func_key) { const shortcut *s = s_list; size_t slen = length_of_list(s_list); #ifdef DEBUG fprintf(stderr, "get_shortcut(): kbinput = %d, meta_key = %d, func_key = %d\n", *kbinput, (int)*meta_key, (int)*func_key); #endif /* Check for shortcuts. */ for (; slen > 0; slen--) { /* We've found a shortcut if: * * 1. The key exists. * 2. The key is a control key in the shortcut list. * 3. meta_key is TRUE and the key is the primary or * miscellaneous meta sequence in the shortcut list. * 4. func_key is TRUE and the key is a function key in the * shortcut list. */ if (*kbinput != NANO_NO_KEY && (*kbinput == s->ctrlval || (*meta_key == TRUE && (*kbinput == s->metaval || *kbinput == s->miscval)) || (*func_key == TRUE && *kbinput == s->funcval))) { break; } s = s->next; } /* Translate the shortcut to either its control key or its meta key * equivalent. Assume that the shortcut has an equivalent control * key, an equivalent primary meta key sequence, or both. */ if (slen > 0) { if (s->ctrlval != NANO_NO_KEY) { *meta_key = FALSE; *func_key = FALSE; *kbinput = s->ctrlval; return s; } else if (s->metaval != NANO_NO_KEY) { *meta_key = TRUE; *func_key = FALSE; *kbinput = s->metaval; return s; } } return NULL; } #ifndef NANO_SMALL const toggle *get_toggle(int kbinput, bool meta_key) { const toggle *t = toggles; #ifdef DEBUG fprintf(stderr, "get_toggle(): kbinput = %d, meta_key = %d\n", kbinput, (int)meta_key); #endif /* Check for toggles. */ for (; t != NULL; t = t->next) { /* We've found a toggle if meta_key is TRUE and the key is in * the meta key toggle list. */ if (meta_key && kbinput == t->val) break; } return t; } #endif /* !NANO_SMALL */ int do_statusbar_input(bool *meta_key, bool *func_key, bool *s_or_t, bool *ran_func, bool *finished, bool allow_funcs) { 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 shortcut *s; bool have_shortcut; *s_or_t = FALSE; *ran_func = FALSE; *finished = FALSE; /* Read in a character. */ input = get_kbinput(bottomwin, meta_key, func_key); #ifndef DISABLE_MOUSE /* If we got a mouse click and it was on a shortcut, read in the * shortcut character. */ if (allow_funcs && *func_key == TRUE && input == KEY_MOUSE) { if (do_mouse()) input = get_kbinput(bottomwin, meta_key, func_key); else input = ERR; } #endif /* Check for a shortcut in the current list. */ s = get_shortcut(currshortcut, &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 || input == NANO_REFRESH_KEY || input == NANO_HOME_KEY || input == NANO_END_KEY || input == NANO_FORWARD_KEY || input == NANO_BACK_KEY || input == NANO_BACKSPACE_KEY || input == NANO_DELETE_KEY || input == NANO_CUT_KEY || #ifndef NANO_SMALL input == NANO_NEXTWORD_KEY || #endif (*meta_key == TRUE && ( #ifndef NANO_SMALL input == NANO_PREVWORD_KEY || #endif input == NANO_VERBATIM_KEY))); /* Set s_or_t to TRUE if we got a shortcut. */ *s_or_t = have_shortcut; 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 && *s_or_t == FALSE) { /* 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) || filename[0] == '\0' || currshortcut != writefile_list) { 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 (*s_or_t == TRUE || get_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) { switch (input) { /* Handle the "universal" statusbar prompt shortcuts. */ case NANO_REFRESH_KEY: total_refresh(); break; case NANO_HOME_KEY: do_statusbar_home(); break; case NANO_END_KEY: do_statusbar_end(); break; case NANO_FORWARD_KEY: do_statusbar_right(); break; case NANO_BACK_KEY: do_statusbar_left(); break; case NANO_BACKSPACE_KEY: /* If we're using restricted mode, the filename * isn't blank, and we're at the "Write File" * prompt, disable Backspace. */ if (!ISSET(RESTRICTED) || filename[0] == '\0' || currshortcut != writefile_list) do_statusbar_backspace(); break; case NANO_DELETE_KEY: /* If we're using restricted mode, the filename * isn't blank, and we're at the "Write File" * prompt, disable Delete. */ if (!ISSET(RESTRICTED) || filename[0] == '\0' || currshortcut != writefile_list) do_statusbar_delete(); break; case NANO_CUT_KEY: /* If we're using restricted mode, the filename * isn't blank, and we're at the "Write File" * prompt, disable Cut. */ if (!ISSET(RESTRICTED) || filename[0] == '\0' || currshortcut != writefile_list) do_statusbar_cut_text(); break; #ifndef NANO_SMALL case NANO_NEXTWORD_KEY: do_statusbar_next_word(FALSE); break; case NANO_PREVWORD_KEY: if (*meta_key == TRUE) do_statusbar_prev_word(FALSE); break; #endif case NANO_VERBATIM_KEY: if (*meta_key == TRUE) { /* 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) || filename[0] == '\0' || currshortcut != writefile_list) { bool got_enter; /* Whether we got the Enter key. */ do_statusbar_verbatim_input(&got_enter); /* If we got the Enter key, set input to the * key value for Enter, and set finished to * TRUE to indicate that we're done. */ if (got_enter) { input = NANO_ENTER_KEY; *finished = TRUE; } } break; } /* 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 trying to run their associated * functions. */ default: if (s->func != NULL) { *ran_func = TRUE; if (!ISSET(VIEW_MODE) || s->viewok) s->func(); } *finished = TRUE; } } } return input; } #ifndef DISABLE_MOUSE bool do_statusbar_mouse(void) { /* FIXME: If we clicked on a location in the statusbar, the cursor * should move to the location we clicked on. This functionality * should be in this function. */ int mouse_x, mouse_y; return get_mouseinput(&mouse_x, &mouse_y, TRUE); } #endif void do_statusbar_home(void) { #ifndef NANO_SMALL 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; } else #endif statusbar_x = 0; } void do_statusbar_end(void) { statusbar_x = strlen(answer); } void do_statusbar_right(void) { if (statusbar_x < strlen(answer)) statusbar_x = move_mbright(answer, statusbar_x); } void do_statusbar_left(void) { if (statusbar_x > 0) statusbar_x = move_mbleft(answer, statusbar_x); } void do_statusbar_backspace(void) { if (statusbar_x > 0) { do_statusbar_left(); do_statusbar_delete(); } } void do_statusbar_delete(void) { if (answer[statusbar_x] != '\0') { int char_buf_len = parse_mbchar(answer + statusbar_x, NULL, 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); } } /* Move text from the statusbar prompt into oblivion. */ void do_statusbar_cut_text(void) { assert(answer != NULL); #ifndef NANO_SMALL if (ISSET(CUT_TO_END)) null_at(&answer, statusbar_x); else { #endif null_at(&answer, 0); statusbar_x = 0; #ifndef NANO_SMALL } #endif } #ifndef NANO_SMALL /* Move to the next word at the statusbar prompt. 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) { char *char_mb; int char_mb_len; bool 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 (answer[statusbar_x] != '\0') { char_mb_len = parse_mbchar(answer + statusbar_x, char_mb, NULL, 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; statusbar_x += char_mb_len; } /* Move forward until we find the first letter of the next word. */ if (answer[statusbar_x] != '\0') statusbar_x += char_mb_len; while (answer[statusbar_x] != '\0') { char_mb_len = parse_mbchar(answer + statusbar_x, char_mb, NULL, NULL); /* If we've found it, stop moving forward through the current * line. */ if (is_word_mbchar(char_mb, allow_punct)) break; statusbar_x += char_mb_len; } free(char_mb); /* Return whether we started on a word. */ return started_on_word; } /* Move to the previous word at the statusbar prompt. 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) { 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, 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, 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, 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); /* Return whether we started on a word. */ return started_on_word; } #endif /* !NANO_SMALL */ 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); } /* The user typed ouuput_len multibyte characters. Add them to the * statusbar prompt, setting got_enter to TRUE if we get a newline, and * filtering out all 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 FALSE, filter out nulls and newlines, * since they're control characters. */ 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. If it's an invalid * multibyte character, interpret it as though it's a byte * character. */ char_buf_len = parse_mbchar(output + i, char_buf, NULL, NULL); i += char_buf_len; /* If allow_cntrls is FALSE, filter out a control character. */ if (!allow_cntrls && is_cntrl_mbchar(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; do_statusbar_right(); } free(char_buf); } /* Return the placewewant associated with current_x, i.e, the zero-based * column position of the cursor. The value will be no smaller than * current_x. */ size_t xplustabs(void) { return strnlenpt(current->data, current_x); } /* actual_x() gives the index in str of the character displayed at * column xplus. That is, actual_x() is the largest value such that * strnlenpt(str, actual_x(str, xplus)) <= xplus. */ size_t actual_x(const char *str, size_t xplus) { size_t i = 0; /* The position in str, returned. */ size_t length = 0; /* The screen display width to str[i]. */ assert(str != NULL); while (*str != '\0') { int str_len = parse_mbchar(str, NULL, NULL, &length); if (length > xplus) break; i += str_len; str += str_len; } return i; } /* A strlen() with tabs factored in, similar to xplustabs(). How many * columns wide are the first size characters of str? */ size_t strnlenpt(const char *str, size_t size) { size_t length = 0; /* The screen display width to str[i]. */ if (size == 0) return 0; assert(str != NULL); while (*str != '\0') { int str_len = parse_mbchar(str, NULL, NULL, &length); str += str_len; if (size <= str_len) break; size -= str_len; } return length; } /* How many columns wide is buf? */ size_t strlenpt(const char *buf) { return strnlenpt(buf, (size_t)-1); } void blank_titlebar(void) { mvwaddstr(topwin, 0, 0, hblank); } void blank_topbar(void) { if (!ISSET(MORE_SPACE)) mvwaddstr(topwin, 1, 0, hblank); } void blank_edit(void) { int i; for (i = 0; i < editwinrows; i++) mvwaddstr(edit, i, 0, hblank); } void blank_statusbar(void) { mvwaddstr(bottomwin, 0, 0, hblank); } void blank_bottombars(void) { if (!ISSET(NO_HELP)) { mvwaddstr(bottomwin, 1, 0, hblank); mvwaddstr(bottomwin, 2, 0, hblank); } } void check_statusblank(void) { if (statusblank > 0) statusblank--; if (statusblank == 0 && !ISSET(CONST_UPDATE)) { blank_statusbar(); wnoutrefresh(bottomwin); reset_cursor(); wrefresh(edit); } } /* Convert buf into a string that can be displayed on screen. The * caller wants to display buf starting with column start_col, and * extending for at most len columns. start_col is zero-based. len is * one-based, so len == 0 means you get "" returned. The returned * string is dynamically allocated, and should be freed. If dollars is * TRUE, the caller might put "$" at the beginning or end of the line if * it's too long. */ char *display_string(const char *buf, size_t start_col, size_t len, bool dollars) { size_t start_index; /* Index in buf of the first character shown. */ size_t column; /* Screen column that start_index corresponds to. */ size_t alloc_len; /* The length of memory allocated for converted. */ char *converted; /* The string we return. */ size_t index; /* Current position in converted. */ char *buf_mb = charalloc(mb_cur_max()); int buf_mb_len; bool bad_char; /* If dollars is TRUE, make room for the "$" at the end of the * line. */ if (dollars && len > 0 && strlenpt(buf) > start_col + len) len--; if (len == 0) return mallocstrcpy(NULL, ""); start_index = actual_x(buf, start_col); column = strnlenpt(buf, start_index); assert(column <= start_col); /* Allocate enough space for the entire line, accounting for a * trailing multibyte character and/or tab. */ alloc_len = (mb_cur_max() * (len + 1)) + tabsize; converted = charalloc(alloc_len + 1); index = 0; if (column < start_col || (dollars && column > 0 && buf[start_index] != '\t')) { /* We don't display all of buf[start_index] since it starts to * the left of the screen. */ buf_mb_len = parse_mbchar(buf + start_index, buf_mb, NULL, NULL); if (is_cntrl_mbchar(buf_mb)) { if (column < start_col) { char *ctrl_buf_mb = charalloc(mb_cur_max()); int ctrl_buf_mb_len, i; ctrl_buf_mb = control_mbrep(buf_mb, ctrl_buf_mb, &ctrl_buf_mb_len); for (i = 0; i < ctrl_buf_mb_len; i++) converted[index++] = ctrl_buf_mb[i]; start_col += mbwidth(ctrl_buf_mb); free(ctrl_buf_mb); start_index += buf_mb_len; } } #ifdef NANO_WIDE else if (ISSET(USE_UTF8) && mbwidth(buf_mb) > 1) { converted[index++] = ' '; start_col++; start_index += buf_mb_len; } #endif } while (index < alloc_len - 1 && buf[start_index] != '\0') { buf_mb_len = parse_mbchar(buf + start_index, buf_mb, &bad_char, NULL); if (*buf_mb == '\t') { #if !defined(NANO_SMALL) && defined(ENABLE_NANORC) if (ISSET(WHITESPACE_DISPLAY)) { int i; for (i = 0; i < whitespace_len[0]; i++) converted[index++] = whitespace[i]; } else #endif converted[index++] = ' '; start_col++; while (start_col % tabsize != 0) { converted[index++] = ' '; start_col++; } /* If buf contains a control character, interpret it. If it * contains an invalid multibyte control character, interpret * that character as though it's a normal control character. */ } else if (is_cntrl_mbchar(buf_mb)) { char *ctrl_buf_mb = charalloc(mb_cur_max()); int ctrl_buf_mb_len, i; converted[index++] = '^'; start_col++; ctrl_buf_mb = control_mbrep(buf_mb, ctrl_buf_mb, &ctrl_buf_mb_len); for (i = 0; i < ctrl_buf_mb_len; i++) converted[index++] = ctrl_buf_mb[i]; start_col += mbwidth(ctrl_buf_mb); free(ctrl_buf_mb); } else if (*buf_mb == ' ') { #if !defined(NANO_SMALL) && defined(ENABLE_NANORC) if (ISSET(WHITESPACE_DISPLAY)) { int i; for (i = whitespace_len[0]; i < whitespace_len[0] + whitespace_len[1]; i++) converted[index++] = whitespace[i]; } else #endif converted[index++] = ' '; start_col++; } else { int i; #ifdef NANO_WIDE /* If buf contains an invalid multibyte non-control * character, interpret that character as though it's a * normal non-control character. */ if (ISSET(USE_UTF8) && bad_char) { char *bad_buf_mb; int bad_buf_mb_len; bad_buf_mb = make_mbchar((unsigned char)*buf_mb, &bad_buf_mb_len); for (i = 0; i < bad_buf_mb_len; i++) converted[index++] = bad_buf_mb[i]; start_col += mbwidth(bad_buf_mb); free(bad_buf_mb); } else { #endif for (i = 0; i < buf_mb_len; i++) converted[index++] = buf[start_index + i]; start_col += mbwidth(buf_mb); #ifdef NANO_WIDE } #endif } start_index += buf_mb_len; } free(buf_mb); if (index < alloc_len - 1) converted[index] = '\0'; /* Make sure converted takes up no more than len columns. */ index = actual_x(converted, len); null_at(&converted, index); return converted; } /* Repaint the statusbar when getting a character in nanogetstr(). buf * should be no longer than max(0, COLS - 4). * * Note that we must turn on A_REVERSE here, since do_help() turns it * off! */ void nanoget_repaint(const char *buf, const char *inputbuf, size_t x) { size_t x_real = strnlenpt(inputbuf, x); int wid = COLS - strlenpt(buf) - 2; assert(x <= strlen(inputbuf)); wattron(bottomwin, A_REVERSE); blank_statusbar(); mvwaddnstr(bottomwin, 0, 0, buf, actual_x(buf, COLS - 2)); waddch(bottomwin, ':'); if (COLS > 1) waddch(bottomwin, x_real < wid ? ' ' : '$'); if (COLS > 2) { size_t page_start = x_real - x_real % wid; char *expanded = display_string(inputbuf, page_start, wid, FALSE); assert(wid > 0); assert(strlenpt(expanded) <= wid); waddstr(bottomwin, expanded); free(expanded); wmove(bottomwin, 0, COLS - wid + x_real - page_start); } else wmove(bottomwin, 0, COLS - 1); wattroff(bottomwin, A_REVERSE); } /* Get the input from the keyboard; this should only be called from * statusq(). */ int nanogetstr(bool allow_tabs, const char *buf, const char *curranswer, #ifndef NANO_SMALL filestruct **history_list, #endif const shortcut *s #ifndef DISABLE_TABCOMP , bool *list #endif ) { int kbinput; bool meta_key, func_key, s_or_t, ran_func, finished; size_t curranswer_len; #ifndef DISABLE_TABCOMP bool tabbed = FALSE; /* Whether we've pressed Tab. */ #endif #ifndef NANO_SMALL 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_SMALL */ answer = mallocstrcpy(answer, curranswer); curranswer_len = strlen(answer); /* Only put statusbar_x at the end of the string if it's * uninitialized, if it would be past the end of curranswer, or if * resetstatuspos is TRUE. Otherwise, leave it alone. This is so * the cursor position stays at the same place if a prompt-changing * toggle is pressed. */ if (statusbar_x == (size_t)-1 || statusbar_x > curranswer_len || resetstatuspos) statusbar_x = curranswer_len; currshortcut = s; nanoget_repaint(buf, answer, statusbar_x); /* Refresh the edit window and the statusbar before getting * input. */ wnoutrefresh(edit); wrefresh(bottomwin); /* If we're using restricted mode, we aren't allowed to change the * name of a 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 ((kbinput = do_statusbar_input(&meta_key, &func_key, &s_or_t, &ran_func, &finished, TRUE)) != NANO_CANCEL_KEY && kbinput != NANO_ENTER_KEY) { assert(statusbar_x <= strlen(answer)); #ifndef DISABLE_TABCOMP if (kbinput != NANO_TAB_KEY) tabbed = FALSE; #endif switch (kbinput) { case NANO_TAB_KEY: #ifndef DISABLE_TABCOMP #ifndef NANO_SMALL if (history_list != NULL) { if (last_kbinput != NANO_TAB_KEY) 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_SMALL */ if (allow_tabs) answer = input_tab(answer, &statusbar_x, &tabbed, list); #endif /* !DISABLE_TABCOMP */ break; case NANO_PREVLINE_KEY: #ifndef NANO_SMALL if (history_list != NULL) { /* If we're scrolling up at the bottom of the * history list, answer isn't blank, and * magichistory isn't set, save answer in * magichistory. */ if ((*history_list)->next == NULL && answer[0] != '\0' && magichistory == NULL) magichistory = mallocstrcpy(NULL, 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); } /* 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; } #endif /* !NANO_SMALL */ break; case NANO_NEXTLINE_KEY: #ifndef NANO_SMALL 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] == '\0' && magichistory != NULL) { answer = mallocstrcpy(answer, magichistory); statusbar_x = strlen(answer); } } #endif /* !NANO_SMALL */ break; } /* 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_SMALL) && !defined(DISABLE_TABCOMP) last_kbinput = kbinput; #endif nanoget_repaint(buf, answer, statusbar_x); wrefresh(bottomwin); } #ifndef NANO_SMALL /* Free magichistory if we need to. */ if (magichistory != NULL) free(magichistory); #endif /* We finished putting in an answer or ran a normal shortcut's * associated function, so reset statusbar_x. */ if (kbinput == NANO_CANCEL_KEY || kbinput == NANO_ENTER_KEY || ran_func) statusbar_x = (size_t)-1; return kbinput; } /* Ask a question on the statusbar. Answer will be stored in 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. * * The allow_tabs parameter indicates whether we should allow tabs to be * interpreted. */ int statusq(bool allow_tabs, const shortcut *s, const char *curranswer, #ifndef NANO_SMALL filestruct **history_list, #endif const char *msg, ...) { va_list ap; char *foo = charalloc(((COLS - 4) * mb_cur_max()) + 1); int retval; #ifndef DISABLE_TABCOMP bool list = FALSE; #endif bottombars(s); va_start(ap, msg); vsnprintf(foo, (COLS - 4) * mb_cur_max(), msg, ap); va_end(ap); null_at(&foo, actual_x(foo, COLS - 4)); retval = nanogetstr(allow_tabs, foo, curranswer, #ifndef NANO_SMALL history_list, #endif s #ifndef DISABLE_TABCOMP , &list #endif ); free(foo); resetstatuspos = FALSE; switch (retval) { case NANO_CANCEL_KEY: retval = -1; resetstatuspos = TRUE; break; case NANO_ENTER_KEY: retval = (answer[0] == '\0') ? -2 : 0; resetstatuspos = TRUE; break; } 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) edit_refresh(); #endif return retval; } void statusq_abort(void) { resetstatuspos = TRUE; } void titlebar(const char *path) { int space; /* The space we have available for display. */ size_t verlen = strlenpt(VERMSG) + 1; /* The length of the version message in columns. */ const char *prefix; /* "File:", "Dir:", or "New Buffer". Goes before filename. */ size_t prefixlen; /* The length of the prefix in columns, plus one. */ const char *state; /* "Modified", "View", or spaces the length of "Modified". * Tells the state of this buffer. */ size_t statelen = 0; /* The length of the state in columns, plus one. */ char *exppath = NULL; /* The file name, expanded for display. */ bool newfie = FALSE; /* Do we say "New Buffer"? */ bool dots = FALSE; /* Do we put an ellipsis before the path? */ assert(path != NULL || filename != NULL); assert(COLS >= 0); wattron(topwin, A_REVERSE); blank_titlebar(); if (COLS <= 5 || COLS - 5 < verlen) space = 0; else { space = COLS - 5 - verlen; /* Reserve 2/3 of the screen plus one column for after the * version message. */ if (space < COLS - (COLS / 3) + 1) space = COLS - (COLS / 3) + 1; } if (COLS > 4) { /* The version message should only take up 1/3 of the screen * minus one column. */ mvwaddnstr(topwin, 0, 2, VERMSG, actual_x(VERMSG, (COLS / 3) - 3)); waddstr(topwin, " "); } if (ISSET(MODIFIED)) state = _("Modified"); else if (ISSET(VIEW_MODE)) state = _("View"); else { if (space > 0) statelen = strnlenpt(_("Modified"), space - 1) + 1; state = &hblank[COLS - statelen]; } statelen = strnlenpt(state, COLS); /* We need a space before state. */ if ((ISSET(MODIFIED) || ISSET(VIEW_MODE)) && statelen < COLS) statelen++; assert(space >= 0); if (space == 0 || statelen >= space) goto the_end; #ifndef DISABLE_BROWSER if (path != NULL) prefix = _("DIR:"); else #endif if (filename[0] == '\0') { prefix = _("New Buffer"); newfie = TRUE; } else prefix = _("File:"); assert(statelen < space); prefixlen = strnlenpt(prefix, space - statelen); /* If newfie is FALSE, we need a space after prefix. */ if (!newfie && prefixlen + statelen < space) prefixlen++; if (path == NULL) path = filename; if (space >= prefixlen + statelen) space -= prefixlen + statelen; else space = 0; /* space is now the room we have for the file name. */ if (!newfie) { size_t lenpt = strlenpt(path), start_col; dots = (lenpt > space); if (dots) { start_col = lenpt - space + 3; space -= 3; } else start_col = 0; exppath = display_string(path, start_col, space, FALSE); } if (!dots) { size_t exppathlen = newfie ? 0 : strlenpt(exppath); /* The length of the expanded filename. */ /* There is room for the whole filename, so we center it. */ waddnstr(topwin, hblank, (space - exppathlen) / 3); waddnstr(topwin, prefix, actual_x(prefix, prefixlen)); if (!newfie) { assert(strlenpt(prefix) + 1 == prefixlen); waddch(topwin, ' '); waddstr(topwin, exppath); } } else { /* We will say something like "File: ...ename". */ waddnstr(topwin, prefix, actual_x(prefix, prefixlen)); if (space <= -3 || newfie) goto the_end; waddch(topwin, ' '); waddnstr(topwin, "...", space + 3); if (space <= 0) goto the_end; waddstr(topwin, exppath); } the_end: free(exppath); if (COLS <= 1 || statelen >= COLS - 1) mvwaddnstr(topwin, 0, 0, state, actual_x(state, COLS)); else { assert(COLS - statelen - 2 >= 0); mvwaddch(topwin, 0, COLS - statelen - 2, ' '); mvwaddnstr(topwin, 0, COLS - statelen - 1, state, actual_x(state, statelen)); } wattroff(topwin, A_REVERSE); wnoutrefresh(topwin); reset_cursor(); wrefresh(edit); } /* If modified is not already set, set it and update titlebar. */ void set_modified(void) { if (!ISSET(MODIFIED)) { SET(MODIFIED); titlebar(NULL); } } void statusbar(const char *msg, ...) { va_list ap; va_start(ap, msg); /* Curses mode is turned off. If we use wmove() now, it will muck * up the terminal settings. So we just use vfprintf(). */ if (curses_ended) { vfprintf(stderr, msg, ap); va_end(ap); return; } /* Blank out the line. */ blank_statusbar(); if (COLS >= 4) { char *bar, *foo; size_t start_x = 0, foo_len; #if !defined(NANO_SMALL) && defined(ENABLE_NANORC) bool old_whitespace = ISSET(WHITESPACE_DISPLAY); UNSET(WHITESPACE_DISPLAY); #endif bar = charalloc(mb_cur_max() * (COLS - 3)); vsnprintf(bar, mb_cur_max() * (COLS - 3), msg, ap); va_end(ap); foo = display_string(bar, 0, COLS - 4, FALSE); #if !defined(NANO_SMALL) && defined(ENABLE_NANORC) if (old_whitespace) SET(WHITESPACE_DISPLAY); #endif free(bar); foo_len = strlenpt(foo); start_x = (COLS - foo_len - 4) / 2; wmove(bottomwin, 0, start_x); wattron(bottomwin, A_REVERSE); waddstr(bottomwin, "[ "); waddstr(bottomwin, foo); free(foo); waddstr(bottomwin, " ]"); wattroff(bottomwin, A_REVERSE); wnoutrefresh(bottomwin); reset_cursor(); wrefresh(edit); /* Leave the cursor at its position in the edit window, not * in the statusbar. */ } disable_cursorpos = TRUE; /* If we're doing quick statusbar blanking, and constant cursor * position display is off, blank the statusbar after only one * keystroke. Otherwise, blank it after twenty-five keystrokes, * as Pico does. */ statusblank = #ifndef NANO_SMALL ISSET(QUICK_BLANK) && !ISSET(CONST_UPDATE) ? 1 : #endif 25; } void bottombars(const shortcut *s) { size_t i, colwidth, slen; if (ISSET(NO_HELP)) return; if (s == main_list) { slen = MAIN_VISIBLE; assert(slen <= length_of_list(s)); } else { slen = length_of_list(s); /* Don't show any more shortcuts than the main list does. */ if (slen > MAIN_VISIBLE) slen = MAIN_VISIBLE; } /* There will be this many characters per column. We need at least * 3 to display anything properly. */ colwidth = COLS / ((slen / 2) + (slen % 2)); blank_bottombars(); for (i = 0; i < slen; i++, s = s->next) { const char *keystr; char foo[4] = ""; /* Yucky sentinel values that we can't handle a better way. */ if (s->ctrlval == NANO_CONTROL_SPACE) strcpy(foo, "^ "); else if (s->ctrlval == NANO_CONTROL_8) strcpy(foo, "^?"); /* Normal values. Assume that the shortcut has an equivalent * control key, meta key sequence, or both. */ else if (s->ctrlval != NANO_NO_KEY) sprintf(foo, "^%c", s->ctrlval + 64); else if (s->metaval != NANO_NO_KEY) sprintf(foo, "M-%c", toupper(s->metaval)); keystr = foo; wmove(bottomwin, 1 + i % 2, (i / 2) * colwidth); onekey(keystr, s->desc, colwidth); } wnoutrefresh(bottomwin); reset_cursor(); wrefresh(edit); } /* Write a shortcut key to the help area at the bottom of the window. * keystroke is e.g. "^G" and desc is e.g. "Get Help". We are careful * to write at most len characters, even if len is very small and * keystroke and desc are long. Note that waddnstr(,,(size_t)-1) adds * the whole string! We do not bother padding the entry with blanks. */ void onekey(const char *keystroke, const char *desc, size_t len) { size_t keystroke_len = strlenpt(keystroke) + 1; assert(keystroke != NULL && desc != NULL); wattron(bottomwin, A_REVERSE); waddnstr(bottomwin, keystroke, actual_x(keystroke, len)); wattroff(bottomwin, A_REVERSE); if (len > keystroke_len) len -= keystroke_len; else len = 0; if (len > 0) { waddch(bottomwin, ' '); waddnstr(bottomwin, desc, actual_x(desc, len)); } } /* nano scrolls horizontally within a line in chunks. This function * returns the column number of the first character displayed in the * window when the cursor is at the given column. Note that * 0 <= column - get_page_start(column) < COLS. */ size_t get_page_start(size_t column) { assert(COLS > 0); if (column == 0 || column < COLS - 1) return 0; else if (COLS > 9) return column - 7 - (column - 7) % (COLS - 8); else if (COLS > 2) return column - (COLS - 2); else return column - (COLS - 1); /* The parentheses are necessary to avoid overflow. */ } /* Resets current_y, based on the position of current, and puts the * cursor in the edit window at (current_y, current_x). */ void reset_cursor(void) { /* If we haven't opened any files yet, put the cursor in the top * left corner of the edit window and get out. */ if (edittop == NULL || current == NULL) { wmove(edit, 0, 0); return; } current_y = current->lineno - edittop->lineno; if (current_y < editwinrows) { size_t x = xplustabs(); wmove(edit, current_y, x - get_page_start(x)); } } /* edit_add() takes care of the job of actually painting a line into the * edit window. fileptr is the line to be painted, at row yval of the * window. converted is the actual string to be written to the window, * with tabs and control characters replaced by strings of regular * characters. start is the column number of the first character of * this page. That is, the first character of converted corresponds to * character number actual_x(fileptr->data, start) of the line. */ void edit_add(const filestruct *fileptr, const char *converted, int yval, size_t start) { #if !defined(NANO_SMALL) || defined(ENABLE_COLOR) size_t startpos = actual_x(fileptr->data, start); /* The position in fileptr->data of the leftmost character * that displays at least partially on the window. */ size_t endpos = actual_x(fileptr->data, start + COLS - 1) + 1; /* The position in fileptr->data of the first character that is * completely off the window to the right. * * Note that endpos might be beyond the null terminator of the * string. */ #endif assert(fileptr != NULL && converted != NULL); assert(strlenpt(converted) <= COLS); /* Just paint the string in any case (we'll add color or reverse on * just the text that needs it). */ mvwaddstr(edit, yval, 0, converted); #ifdef ENABLE_COLOR if (colorstrings != NULL && !ISSET(NO_COLOR_SYNTAX)) { const colortype *tmpcolor = colorstrings; for (; tmpcolor != NULL; tmpcolor = tmpcolor->next) { int x_start; /* Starting column for mvwaddnstr. Zero-based. */ int paintlen; /* Number of chars to paint on this line. There are COLS * characters on a whole line. */ size_t index; /* Index in converted where we paint. */ regmatch_t startmatch; /* Match position for start_regex. */ regmatch_t endmatch; /* Match position for end_regex. */ if (tmpcolor->bright) wattron(edit, A_BOLD); wattron(edit, COLOR_PAIR(tmpcolor->pairnum)); /* Two notes about regexec(). Return value 0 means there is * a match. Also, rm_eo is the first non-matching character * after the match. */ /* First case, tmpcolor is a single-line expression. */ if (tmpcolor->end == NULL) { size_t k = 0; /* We increment k by rm_eo, to move past the end of the * last match. Even though two matches may overlap, we * want to ignore them, so that we can highlight * C-strings correctly. */ while (k < endpos) { /* Note the fifth parameter to regexec(). It says * not to match the beginning-of-line character * unless k is 0. If regexec() returns REG_NOMATCH, * there are no more matches in the line. */ if (regexec(&tmpcolor->start, &fileptr->data[k], 1, &startmatch, (k == 0) ? 0 : REG_NOTBOL) == REG_NOMATCH) break; /* Translate the match to the beginning of the * line. */ startmatch.rm_so += k; startmatch.rm_eo += k; if (startmatch.rm_so == startmatch.rm_eo) { startmatch.rm_eo++; statusbar( _("Refusing zero-length regex match")); } else if (startmatch.rm_so < endpos && startmatch.rm_eo > startpos) { if (startmatch.rm_so <= startpos) x_start = 0; else x_start = strnlenpt(fileptr->data, startmatch.rm_so) - start; index = actual_x(converted, x_start); paintlen = actual_x(converted + index, strnlenpt(fileptr->data, startmatch.rm_eo) - start - x_start); assert(0 <= x_start && 0 <= paintlen); mvwaddnstr(edit, yval, x_start, converted + index, paintlen); } k = startmatch.rm_eo; } } else { /* This is a multi-line regex. There are two steps. * First, we have to see if the beginning of the line is * colored by a start on an earlier line, and an end on * this line or later. * * We find the first line before fileptr matching the * start. If every match on that line is followed by an * end, then go to step two. Otherwise, find the next * line after start_line matching the end. If that line * is not before fileptr, then paint the beginning of * this line. */ const filestruct *start_line = fileptr->prev; /* The first line before fileptr matching start. */ regoff_t start_col; /* Where it starts in that line. */ const filestruct *end_line; while (start_line != NULL && regexec(&tmpcolor->start, start_line->data, 1, &startmatch, 0) == REG_NOMATCH) { /* If there is an end on this line, there is no need * to look for starts on earlier lines. */ if (regexec(tmpcolor->end, start_line->data, 0, NULL, 0) == 0) goto step_two; start_line = start_line->prev; } /* No start found, so skip to the next step. */ if (start_line == NULL) goto step_two; /* Now start_line is the first line before fileptr * containing a start match. Is there a start on this * line not followed by an end on this line? */ start_col = 0; while (TRUE) { start_col += startmatch.rm_so; startmatch.rm_eo -= startmatch.rm_so; if (regexec(tmpcolor->end, start_line->data + start_col + startmatch.rm_eo, 0, NULL, (start_col + startmatch.rm_eo == 0) ? 0 : REG_NOTBOL) == REG_NOMATCH) /* No end found after this start. */ break; start_col++; if (regexec(&tmpcolor->start, start_line->data + start_col, 1, &startmatch, REG_NOTBOL) == REG_NOMATCH) /* No later start on this line. */ goto step_two; } /* Indeed, there is a start not followed on this line by * an end. */ /* We have already checked that there is no end before * fileptr and after the start. Is there an end after * the start at all? We don't paint unterminated * starts. */ end_line = fileptr; while (end_line != NULL && regexec(tmpcolor->end, end_line->data, 1, &endmatch, 0) == REG_NOMATCH) end_line = end_line->next; /* No end found, or it is too early. */ if (end_line == NULL || (end_line == fileptr && endmatch.rm_eo <= startpos)) goto step_two; /* Now paint the start of fileptr. */ if (end_line != fileptr) /* If the start of fileptr is on a different line * from the end, paintlen is -1, meaning that * everything on the line gets painted. */ paintlen = -1; else /* Otherwise, paintlen is the expanded location of * the end of the match minus the expanded location * of the beginning of the page. */ paintlen = actual_x(converted, strnlenpt(fileptr->data, endmatch.rm_eo) - start); mvwaddnstr(edit, yval, 0, converted, paintlen); step_two: /* Second step, we look for starts on this line. */ start_col = 0; while (start_col < endpos) { if (regexec(&tmpcolor->start, fileptr->data + start_col, 1, &startmatch, (start_col == 0) ? 0 : REG_NOTBOL) == REG_NOMATCH || start_col + startmatch.rm_so >= endpos) /* No more starts on this line. */ break; /* Translate the match to be relative to the * beginning of the line. */ startmatch.rm_so += start_col; startmatch.rm_eo += start_col; if (startmatch.rm_so <= startpos) x_start = 0; else x_start = strnlenpt(fileptr->data, startmatch.rm_so) - start; index = actual_x(converted, x_start); if (regexec(tmpcolor->end, fileptr->data + startmatch.rm_eo, 1, &endmatch, (startmatch.rm_eo == 0) ? 0 : REG_NOTBOL) == 0) { /* Translate the end match to be relative to the * beginning of the line. */ endmatch.rm_so += startmatch.rm_eo; endmatch.rm_eo += startmatch.rm_eo; /* There is an end on this line. But does it * appear on this page, and is the match more * than zero characters long? */ if (endmatch.rm_eo > startpos && endmatch.rm_eo > startmatch.rm_so) { paintlen = actual_x(converted + index, strnlenpt(fileptr->data, endmatch.rm_eo) - start - x_start); assert(0 <= x_start && x_start < COLS); mvwaddnstr(edit, yval, x_start, converted + index, paintlen); } } else { /* There is no end on this line. But we haven't * yet looked for one on later lines. */ end_line = fileptr->next; while (end_line != NULL && regexec(tmpcolor->end, end_line->data, 0, NULL, 0) == REG_NOMATCH) end_line = end_line->next; if (end_line != NULL) { assert(0 <= x_start && x_start < COLS); mvwaddnstr(edit, yval, x_start, converted + index, -1); /* We painted to the end of the line, so * don't bother checking any more starts. */ break; } } start_col = startmatch.rm_so + 1; } } wattroff(edit, A_BOLD); wattroff(edit, COLOR_PAIR(tmpcolor->pairnum)); } } #endif /* ENABLE_COLOR */ #ifndef NANO_SMALL if (ISSET(MARK_ISSET) && (fileptr->lineno <= mark_beginbuf->lineno || fileptr->lineno <= current->lineno) && (fileptr->lineno >= mark_beginbuf->lineno || fileptr->lineno >= current->lineno)) { /* fileptr is at least partially selected. */ const filestruct *top; /* Either current or mark_beginbuf, whichever is first. */ size_t top_x; /* current_x or mark_beginx, corresponding to top. */ const filestruct *bot; size_t bot_x; int x_start; /* Starting column for mvwaddnstr. Zero-based. */ int paintlen; /* Number of chars to paint on this line. There are COLS * characters on a whole line. */ size_t index; /* Index in converted where we paint. */ mark_order(&top, &top_x, &bot, &bot_x, NULL); if (top->lineno < fileptr->lineno || top_x < startpos) top_x = startpos; if (bot->lineno > fileptr->lineno || bot_x > endpos) bot_x = endpos; /* The selected bit of fileptr is on this page. */ if (top_x < endpos && bot_x > startpos) { assert(startpos <= top_x); /* x_start is the expanded location of the beginning of the * mark minus the beginning of the page. */ x_start = strnlenpt(fileptr->data, top_x) - start; if (bot_x >= endpos) /* If the end of the mark is off the page, paintlen is * -1, meaning that everything on the line gets * painted. */ paintlen = -1; else /* Otherwise, paintlen is the expanded location of the * end of the mark minus the expanded location of the * beginning of the mark. */ paintlen = strnlenpt(fileptr->data, bot_x) - (x_start + start); /* If x_start is before the beginning of the page, shift * paintlen x_start characters to compensate, and put * x_start at the beginning of the page. */ if (x_start < 0) { paintlen += x_start; x_start = 0; } assert(x_start >= 0 && x_start <= strlen(converted)); index = actual_x(converted, x_start); if (paintlen > 0) paintlen = actual_x(converted + index, paintlen); wattron(edit, A_REVERSE); mvwaddnstr(edit, yval, x_start, converted + index, paintlen); wattroff(edit, A_REVERSE); } } #endif /* !NANO_SMALL */ } /* Just update one line in the edit buffer. This is basically a wrapper * for edit_add(). * * If fileptr != current, then index is considered 0. The line will be * displayed starting with fileptr->data[index]. Likely args are * current_x or 0. */ void update_line(const filestruct *fileptr, size_t index) { int line; /* The line in the edit window that we want to update. */ char *converted; /* fileptr->data converted to have tabs and control characters * expanded. */ size_t page_start; assert(fileptr != NULL); line = fileptr->lineno - edittop->lineno; /* We assume the line numbers are valid. Is that really true? */ assert(line < 0 || line == check_linenumbers(fileptr)); if (line < 0 || line >= editwinrows) return; /* First, blank out the line. */ mvwaddstr(edit, line, 0, hblank); /* Next, convert variables that index the line to their equivalent * positions in the expanded line. */ index = (fileptr == current) ? strnlenpt(fileptr->data, index) : 0; page_start = get_page_start(index); /* Expand the line, replacing tabs with spaces, and control * characters with their displayed forms. */ converted = display_string(fileptr->data, page_start, COLS, TRUE); /* Paint the line. */ edit_add(fileptr, converted, line, page_start); free(converted); if (page_start > 0) mvwaddch(edit, line, 0, '$'); if (strlenpt(fileptr->data) > page_start + COLS) mvwaddch(edit, line, COLS - 1, '$'); } /* Return a nonzero value if we need an update after moving * horizontally. We need one if the mark is on or if old_pww and * placewewant are on different pages. */ int need_horizontal_update(size_t old_pww) { return #ifndef NANO_SMALL ISSET(MARK_ISSET) || #endif get_page_start(old_pww) != get_page_start(placewewant); } /* Return a nonzero value if we need an update after moving vertically. * We need one if the mark is on or if old_pww and placewewant * are on different pages. */ int need_vertical_update(size_t old_pww) { return #ifndef NANO_SMALL ISSET(MARK_ISSET) || #endif get_page_start(old_pww) != get_page_start(placewewant); } /* Scroll the edit window in the given direction and the given number * of lines, and draw new lines on the blank lines left after the * scrolling. direction is the direction to scroll, either UP or DOWN, * and nlines is the number of lines to scroll. Don't redraw the old * topmost or bottommost line (where we assume current is) before * scrolling or draw the new topmost or bottommost line after scrolling * (where we assume current will be), since we don't know where we are * on the page or whether we'll stay there. */ void edit_scroll(updown direction, int nlines) { filestruct *foo; int i, scroll_rows = 0; /* Scrolling less than one line or more than editwinrows lines is * redundant, so don't allow it. */ if (nlines < 1 || nlines > editwinrows) return; /* Move the top line of the edit window up or down (depending on the * value of direction) nlines lines. If there are fewer lines of * text than that left, move it to the top or bottom line of the * file (depending on the value of direction). Keep track of * how many lines we moved in scroll_rows. */ for (i = nlines; i > 0; i--) { if (direction == UP) { if (edittop->prev == NULL) break; edittop = edittop->prev; scroll_rows--; } else { if (edittop->next == NULL) break; edittop = edittop->next; scroll_rows++; } } /* Scroll the text on the screen up or down scroll_rows lines, * depending on the value of direction. */ scrollok(edit, TRUE); wscrl(edit, scroll_rows); scrollok(edit, FALSE); foo = edittop; if (direction != UP) { int slines = editwinrows - nlines; for (; slines > 0 && foo != NULL; slines--) foo = foo->next; } /* And draw new lines on the blank top or bottom lines of the edit * window, depending on the value of direction. Don't draw the new * topmost or new bottommost line. */ while (scroll_rows != 0 && foo != NULL) { if (foo->next != NULL) update_line(foo, 0); if (direction == UP) scroll_rows++; else scroll_rows--; foo = foo->next; } } /* Update any lines between old_current and current that need to be * updated. */ void edit_redraw(const filestruct *old_current, size_t old_pww) { bool do_refresh = need_vertical_update(0) || need_vertical_update(old_pww); const filestruct *foo; /* If either old_current or current is offscreen, refresh the screen * and get out. */ if (old_current->lineno < edittop->lineno || old_current->lineno >= edittop->lineno + editwinrows || current->lineno < edittop->lineno || current->lineno >= edittop->lineno + editwinrows) { edit_refresh(); return; } /* Update old_current and current if we're not on the first page * and/or we're not on the same page as before. If the mark is on, * update all the lines between old_current and current too. */ foo = old_current; while (foo != current) { if (do_refresh) update_line(foo, 0); #ifndef NANO_SMALL if (!ISSET(MARK_ISSET)) #endif break; if (foo->lineno > current->lineno) foo = foo->prev; else foo = foo->next; } if (do_refresh) update_line(current, current_x); } /* Refresh the screen without changing the position of lines. */ void edit_refresh(void) { if (current->lineno < edittop->lineno || current->lineno >= edittop->lineno + editwinrows) /* Note that edit_update() changes edittop so that it's in range * of current. Thus, when it then calls edit_refresh(), there * is no danger of getting an infinite loop. */ edit_update( #ifndef NANO_SMALL ISSET(SMOOTH_SCROLL) ? NONE : #endif CENTER); else { int nlines = 0; const filestruct *foo = edittop; #ifdef DEBUG fprintf(stderr, "edit_refresh(): edittop->lineno = %ld\n", (long)edittop->lineno); #endif while (nlines < editwinrows) { update_line(foo, foo == current ? current_x : 0); nlines++; if (foo->next == NULL) break; foo = foo->next; } while (nlines < editwinrows) { mvwaddstr(edit, nlines, 0, hblank); nlines++; } reset_cursor(); wrefresh(edit); } } /* A nice generic routine to update the edit buffer. We keep current in * the same place and move edittop to put it in range of current. */ void edit_update(topmidnone location) { filestruct *foo = current; if (location != TOP) { /* If location is CENTER, we move edittop up (editwinrows / 2) * lines. This puts current at the center of the screen. If * location is NONE, we move edittop up current_y lines if * current_y is in range of the screen, 0 lines if current_y is * less than 0, or (editwinrows - 1) lines if current_y is * greater than (editwinrows - 1). This puts current at the * same place on the screen as before, or at the top or bottom * of the screen if edittop is beyond either. */ int goal; if (location == CENTER) goal = editwinrows / 2; else { goal = current_y; /* Limit goal to (editwinrows - 1) lines maximum. */ if (goal > editwinrows - 1) goal = editwinrows - 1; } for (; goal > 0 && foo->prev != NULL; goal--) foo = foo->prev; } edittop = foo; edit_refresh(); } /* Ask a simple yes/no question, specified in msg, on the statusbar. * Return 1 for Y, 0 for N, 2 for All (if all is TRUE when passed in) * and -1 for abort (^C). */ int do_yesno(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! */ 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: if possible, specify * both the shortcuts for 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 Y, N, A. */ if (COLS < 32) width = COLS / 2; /* Write 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, A_REVERSE); blank_statusbar(); mvwaddnstr(bottomwin, 0, 0, msg, actual_x(msg, COLS - 1)); wattroff(bottomwin, A_REVERSE); wrefresh(bottomwin); do { int kbinput; bool meta_key, func_key; #ifndef DISABLE_MOUSE int mouse_x, mouse_y; #endif kbinput = get_kbinput(edit, &meta_key, &func_key); if (kbinput == NANO_REFRESH_KEY) { total_redraw(); continue; } else if (kbinput == NANO_CANCEL_KEY) ok = -1; #ifndef DISABLE_MOUSE else if (kbinput == KEY_MOUSE) { get_mouseinput(&mouse_x, &mouse_y, FALSE); if (mouse_x != -1 && mouse_y != -1 && !ISSET(NO_HELP) && wenclose(bottomwin, mouse_y, mouse_x) && mouse_x < (width * 2) && mouse_y >= editwinrows + 3) { int x = mouse_x / width; /* Did we click in the first column of shortcuts, or * the second? */ int y = mouse_y - editwinrows - 3; /* Did we click in the first row of shortcuts? */ 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 /* Look for the kbinput in the yes, no and (optionally) all * strings. */ else 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); return ok; } void total_redraw(void) { #ifdef USE_SLANG /* Slang curses emulation brain damage, part 3: Slang doesn't define * curscr, and even if it did, if we just do what curses does here, * it'll leave some windows cleared without updating them * properly. */ SLsmg_touch_screen(); SLsmg_refresh(); #else clearok(curscr, TRUE); wrefresh(curscr); #endif } void total_refresh(void) { total_redraw(); titlebar(NULL); edit_refresh(); bottombars(currshortcut); } void display_main_list(void) { bottombars(main_list); } /* If constant is FALSE, the user typed Ctrl-C, so we unconditionally * display the cursor position. Otherwise, we display it only if the * character position changed and disable_cursorpos is FALSE. * * If constant is TRUE and disable_cursorpos is TRUE, we set the latter * to FALSE and update old_i and old_totsize. That way, we leave the * current statusbar alone, but next time we will display. */ void do_cursorpos(bool constant) { char c; filestruct *f; size_t i = 0; static size_t old_i = 0, old_totsize = (size_t)-1; assert(current != NULL && fileage != NULL && totlines != 0); if (old_totsize == (size_t)-1) old_totsize = totsize; c = current->data[current_x]; f = current->next; current->data[current_x] = '\0'; current->next = NULL; get_totals(fileage, current, NULL, &i); current->data[current_x] = c; current->next = f; /* Check whether totsize is correct. If it isn't, there is a bug * somewhere. */ assert(current != filebot || i == totsize); if (constant && disable_cursorpos) { disable_cursorpos = FALSE; old_i = i; old_totsize = totsize; return; } /* If constant is FALSE, display the position on the statusbar * unconditionally. Otherwise, only display the position when the * character values have changed. Finally, if disable_cursorpos is * TRUE, set it to FALSE. */ if (!constant || old_i != i || old_totsize != totsize) { size_t xpt = xplustabs() + 1; size_t cur_len = strlenpt(current->data) + 1; int linepct = 100 * current->lineno / totlines; int colpct = 100 * xpt / cur_len; int bytepct = (totsize == 0) ? 0 : 100 * i / totsize; statusbar( _("line %ld/%lu (%d%%), col %lu/%lu (%d%%), char %lu/%lu (%d%%)"), (long)current->lineno, (unsigned long)totlines, linepct, (unsigned long)xpt, (unsigned long)cur_len, colpct, (unsigned long)i, (unsigned long)totsize, bytepct); disable_cursorpos = FALSE; } old_i = i; old_totsize = totsize; } void do_cursorpos_void(void) { do_cursorpos(FALSE); } #ifndef DISABLE_HELP /* Calculate the next line of help_text, starting at ptr. */ size_t help_line_len(const char *ptr) { int help_cols = (COLS > 24) ? COLS - 8 : 24; /* Try to break the line at (COLS - 8) columns if we have more than * 24 columns, and at 24 columns otherwise. */ size_t retval = break_line(ptr, help_cols, TRUE); size_t retval_save = retval; /* Get the length of the entire line up to a null or a newline. */ while (*(ptr + retval) != '\0' && *(ptr + retval) != '\n') retval += move_mbright(ptr + retval, 0); /* If the entire line doesn't go more than 8 columns beyond where we * tried to break it, we should display it as-is. Otherwise, we * should display it only up to the break. */ if (strnlenpt(ptr, retval) > help_cols + 8) retval = retval_save; return retval; } /* Our dynamic, shortcut-list-compliant help function. */ void do_help(void) { int line = 0; /* The line number in help_text of the first displayed help * line. This variable is zero-based. */ bool no_more = FALSE; /* no_more means the end of the help text is shown, so don't go * down any more. */ int kbinput = ERR; bool meta_key, func_key; bool old_no_help = ISSET(NO_HELP); #ifndef DISABLE_MOUSE const shortcut *oldshortcut = currshortcut; /* We will set currshortcut to allow clicking on the help * screen's shortcut list. */ #endif curs_set(0); blank_edit(); wattroff(bottomwin, A_REVERSE); blank_statusbar(); /* Set help_text as the string to display. */ help_init(); assert(help_text != NULL); #ifndef DISABLE_MOUSE /* Set currshortcut to allow clicking on the help screen's shortcut * list, AFTER help_init(). */ currshortcut = help_list; #endif if (ISSET(NO_HELP)) { /* Make sure that the help screen's shortcut list will actually * be displayed. */ UNSET(NO_HELP); window_init(); } bottombars(help_list); do { int i; int old_line = line; /* We redisplay the help only if it moved. */ const char *ptr = help_text; switch (kbinput) { #ifndef DISABLE_MOUSE case KEY_MOUSE: { int mouse_x, mouse_y; get_mouseinput(&mouse_x, &mouse_y, TRUE); } break; #endif case NANO_PREVPAGE_KEY: case NANO_PREVPAGE_FKEY: if (line > 0) { line -= editwinrows - 2; if (line < 0) line = 0; } break; case NANO_NEXTPAGE_KEY: case NANO_NEXTPAGE_FKEY: if (!no_more) line += editwinrows - 2; break; case NANO_PREVLINE_KEY: if (line > 0) line--; break; case NANO_NEXTLINE_KEY: if (!no_more) line++; break; } if (kbinput == NANO_REFRESH_KEY) total_redraw(); else { if (line == old_line && kbinput != ERR) goto skip_redisplay; blank_edit(); } /* Calculate where in the text we should be, based on the * page. */ for (i = 0; i < line; i++) { ptr += help_line_len(ptr); if (*ptr == '\n') ptr++; } for (i = 0; i < editwinrows && *ptr != '\0'; i++) { size_t j = help_line_len(ptr); mvwaddnstr(edit, i, 0, ptr, j); ptr += j; if (*ptr == '\n') ptr++; } no_more = (*ptr == '\0'); skip_redisplay: kbinput = get_kbinput(edit, &meta_key, &func_key); } while (kbinput != NANO_EXIT_KEY && kbinput != NANO_EXIT_FKEY); #ifndef DISABLE_MOUSE currshortcut = oldshortcut; #endif if (old_no_help) { blank_bottombars(); wrefresh(bottomwin); SET(NO_HELP); window_init(); } else bottombars(currshortcut); curs_set(1); edit_refresh(); /* The help_init() at the beginning allocated help_text. Since * help_text has now been written to the screen, we don't need it * anymore. */ free(help_text); help_text = NULL; } #endif /* !DISABLE_HELP */ /* Highlight the current word being replaced or spell checked. We * expect word to have tabs and control characters expanded. */ void do_replace_highlight(bool highlight_flag, const char *word) { size_t y = xplustabs(); size_t word_len = strlenpt(word); y = get_page_start(y) + COLS - y; /* Now y is the number of columns that we can display on this * line. */ assert(y > 0); if (word_len > y) y--; reset_cursor(); if (highlight_flag) wattron(edit, A_REVERSE); #ifdef HAVE_REGEX_H /* This is so we can show zero-length regexes. */ if (word_len == 0) waddch(edit, ' '); else #endif waddnstr(edit, word, actual_x(word, y)); if (word_len > y) waddch(edit, '$'); if (highlight_flag) wattroff(edit, A_REVERSE); } #ifndef NDEBUG /* Return what the current line number should be, starting at edittop * and ending at fileptr. */ int check_linenumbers(const filestruct *fileptr) { int check_line = 0; const filestruct *filetmp; for (filetmp = edittop; filetmp != fileptr; filetmp = filetmp->next) check_line++; return check_line; } #endif #ifdef DEBUG /* Dump the filestruct inptr to stderr. */ void dump_buffer(const filestruct *inptr) { if (inptr == fileage) fprintf(stderr, "Dumping file buffer to stderr...\n"); else if (inptr == cutbuffer) fprintf(stderr, "Dumping cutbuffer to stderr...\n"); else fprintf(stderr, "Dumping a buffer to stderr...\n"); while (inptr != NULL) { fprintf(stderr, "(%ld) %s\n", (long)inptr->lineno, inptr->data); inptr = inptr->next; } } /* Dump the main filestruct to stderr in reverse. */ void dump_buffer_reverse(void) { const filestruct *fileptr = filebot; while (fileptr != NULL) { fprintf(stderr, "(%ld) %s\n", (long)fileptr->lineno, fileptr->data); fileptr = fileptr->prev; } } #endif /* DEBUG */ #ifdef NANO_EXTRA #define CREDIT_LEN 53 #define XLCREDIT_LEN 8 /* Easter egg: Display credits. Assume nodelay(edit) is FALSE. */ void do_credits(void) { int kbinput = ERR, crpos = 0, xlpos = 0; const char *credits[CREDIT_LEN] = { NULL, /* "The nano text editor" */ NULL, /* "version" */ VERSION, "", NULL, /* "Brought to you by:" */ "Chris Allegretta", "Jordi Mallach", "Adam Rogoyski", "Rob Siemborski", "Rocco Corsi", "David Lawrence Ramsey", "David Benbennick", "Ken Tyler", "Sven Guckes", NULL, /* credits[14], handled below. */ "Pauli Virtanen", "Daniele Medri", "Clement Laforet", "Tedi Heriyanto", "Bill Soudan", "Christian Weisgerber", "Erik Andersen", "Big Gaute", "Joshua Jensen", "Ryan Krebs", "Albert Chin", "", NULL, /* "Special thanks to:" */ "Plattsburgh State University", "Benet Laboratories", "Amy Allegretta", "Linda Young", "Jeremy Robichaud", "Richard Kolb II", NULL, /* "The Free Software Foundation" */ "Linus Torvalds", NULL, /* "For ncurses:" */ "Thomas Dickey", "Pavel Curtis", "Zeyd Ben-Halim", "Eric S. Raymond", NULL, /* "and anyone else we forgot..." */ NULL, /* "Thank you for using nano!" */ "", "", "", "", "(c) 1999-2005 Chris Allegretta", "", "", "", "", "http://www.nano-editor.org/" }; const char *xlcredits[XLCREDIT_LEN] = { N_("The nano text editor"), N_("version"), N_("Brought to you by:"), N_("Special thanks to:"), N_("The Free Software Foundation"), N_("For ncurses:"), N_("and anyone else we forgot..."), N_("Thank you for using nano!") }; /* credits[14]: Use a minor hack to make sure this name is displayed * properly, since we can't dynamically assign it above. */ credits[14] = #ifdef NANO_WIDE ISSET(USE_UTF8) ? "Florian K\xC3\xB6nig" : #endif "Florian K\xF6nig"; curs_set(0); nodelay(edit, TRUE); scrollok(edit, TRUE); blank_titlebar(); blank_topbar(); blank_edit(); blank_statusbar(); blank_bottombars(); wrefresh(topwin); wrefresh(edit); wrefresh(bottomwin); for (crpos = 0; crpos < CREDIT_LEN + editwinrows / 2; crpos++) { if ((kbinput = wgetch(edit)) != ERR) break; if (crpos < CREDIT_LEN) { const char *what; size_t start_x; if (credits[crpos] == NULL) { assert(0 <= xlpos && xlpos < XLCREDIT_LEN); what = _(xlcredits[xlpos]); xlpos++; } else what = credits[crpos]; start_x = COLS / 2 - strlenpt(what) / 2 - 1; mvwaddstr(edit, editwinrows - 1 - (editwinrows % 2), start_x, what); } napms(700); scroll(edit); wrefresh(edit); if ((kbinput = wgetch(edit)) != ERR) break; napms(700); scroll(edit); wrefresh(edit); } if (kbinput != ERR) ungetch(kbinput); curs_set(1); scrollok(edit, FALSE); nodelay(edit, FALSE); total_refresh(); } #endif