| /* $Id$ */ |
| /************************************************************************** |
| * winio.c * |
| * * |
| * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007 * |
| * Free Software Foundation, Inc. * |
| * This program is free software; you can redistribute it and/or modify * |
| * it under the terms of the GNU General Public License as published by * |
| * the Free Software Foundation; either version 3, or (at your option) * |
| * any later version. * |
| * * |
| * This program is distributed in the hope that it will be useful, but * |
| * WITHOUT ANY WARRANTY; without even the implied warranty of * |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * |
| * General Public License for more details. * |
| * * |
| * You should have received a copy of the GNU General Public License * |
| * along with this program; if not, write to the Free Software * |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * |
| * 02110-1301, USA. * |
| * * |
| **************************************************************************/ |
| |
| #include "proto.h" |
| |
| #include <stdio.h> |
| #include <stdarg.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <ctype.h> |
| |
| static int *key_buffer = NULL; |
| /* The keystroke buffer, containing all the keystrokes we |
| * haven't handled yet at a given point. */ |
| static size_t key_buffer_len = 0; |
| /* The length of the keystroke buffer. */ |
| static int statusblank = 0; |
| /* The number of keystrokes left after we call statusbar(), |
| * before we actually blank the statusbar. */ |
| static bool disable_cursorpos = FALSE; |
| /* Should we temporarily disable constant cursor position |
| * display? */ |
| |
| /* 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, xterm, rxvt, Eterm, |
| * and Terminal. 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.) */ |
| |
| /* Read in a sequence of keystrokes from win and save them in the |
| * keystroke buffer. This should only be called when the keystroke |
| * buffer is empty. */ |
| void get_key_buffer(WINDOW *win) |
| { |
| int input; |
| size_t errcount; |
| |
| /* If the keystroke buffer isn't empty, get out. */ |
| if (key_buffer != NULL) |
| return; |
| |
| /* Read in the first character using blocking input. */ |
| #ifndef NANO_TINY |
| allow_pending_sigwinch(TRUE); |
| #endif |
| |
| /* Just before reading in the first character, display any pending |
| * screen updates. */ |
| doupdate(); |
| |
| errcount = 0; |
| if (nodelay_mode) { |
| if ((input = wgetch(win)) == ERR) |
| return; |
| } else |
| while ((input = wgetch(win)) == ERR) { |
| errcount++; |
| |
| /* If we've failed to get a character MAX_BUF_SIZE times in a |
| * row, assume that the input source we were using is gone and |
| * die gracefully. We could check if errno is set to EIO |
| * ("Input/output error") and die gracefully in that case, but |
| * it's not always set properly. Argh. */ |
| if (errcount == MAX_BUF_SIZE) |
| handle_hupterm(0); |
| } |
| |
| #ifndef NANO_TINY |
| allow_pending_sigwinch(FALSE); |
| #endif |
| |
| /* Increment the length of the keystroke buffer, and save the value |
| * of the keystroke at the end of it. */ |
| 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_TINY |
| 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, and |
| * save the value of the keystroke at the end of it. */ |
| key_buffer_len++; |
| key_buffer = (int *)nrealloc(key_buffer, key_buffer_len * |
| sizeof(int)); |
| key_buffer[key_buffer_len - 1] = input; |
| |
| #ifndef NANO_TINY |
| allow_pending_sigwinch(FALSE); |
| #endif |
| } |
| |
| /* Switch back to non-blocking input. */ |
| nodelay(win, FALSE); |
| |
| #ifdef DEBUG |
| fprintf(stderr, "get_key_buffer(): key_buffer_len = %lu\n", (unsigned long)key_buffer_len); |
| #endif |
| } |
| |
| /* Return the length of the keystroke buffer. */ |
| size_t get_key_buffer_len(void) |
| { |
| return key_buffer_len; |
| } |
| |
| /* Add the keystrokes in input to the keystroke buffer. */ |
| void unget_input(int *input, size_t input_len) |
| { |
| #ifndef NANO_TINY |
| 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 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 keystroke buffer, |
| * and reallocate the 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 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 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 keystroke buffer. If the |
| * keystroke buffer is empty and win isn't NULL, try to read in more |
| * characters from win and add them to the keystroke buffer before doing |
| * anything else. If the keystroke buffer is empty and win is NULL, |
| * return NULL. */ |
| int *get_input(WINDOW *win, size_t input_len) |
| { |
| int *input; |
| |
| #ifndef NANO_TINY |
| allow_pending_sigwinch(TRUE); |
| allow_pending_sigwinch(FALSE); |
| #endif |
| |
| if (key_buffer_len == 0) { |
| if (win != NULL) { |
| get_key_buffer(win); |
| |
| if (key_buffer_len == 0) |
| return NULL; |
| } else |
| return NULL; |
| } |
| |
| /* If input_len is greater than the length of the keystroke buffer, |
| * only read the number of characters in the keystroke buffer. */ |
| if (input_len > key_buffer_len) |
| input_len = key_buffer_len; |
| |
| /* Subtract input_len from the length of the keystroke buffer, and |
| * allocate 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 keystrokes from the beginning of the keystroke |
| * buffer into input. */ |
| memcpy(input, key_buffer, input_len * sizeof(int)); |
| |
| /* If the keystroke buffer is empty, mark it as such. */ |
| if (key_buffer_len == 0) { |
| free(key_buffer); |
| key_buffer = NULL; |
| /* If the keystroke buffer isn't empty, move its beginning forward |
| * far enough 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)) == ERR); |
| |
| /* If we read from the edit window, blank the statusbar if we need |
| * to. */ |
| if (win == edit) |
| check_statusblank(); |
| |
| 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) |
| { |
| static int escapes = 0, byte_digits = 0; |
| int *kbinput, retval = ERR; |
| |
| *meta_key = FALSE; |
| *func_key = FALSE; |
| |
| /* Read in a character. */ |
| if (nodelay_mode) { |
| kbinput = get_input(win, 1); |
| if (kbinput == 0) |
| return 0; |
| } else |
| 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. */ |
| case 3: |
| /* Three escapes: wait for more input. */ |
| break; |
| default: |
| /* More than three escapes: limit the escape counter |
| * to no more than two, and wait for more input. */ |
| escapes %= 3; |
| } |
| break; |
| default: |
| switch (escapes) { |
| case 0: |
| /* One non-escape: normal input mode. Save the |
| * non-escape character as the result. */ |
| retval = *kbinput; |
| break; |
| case 1: |
| /* Reset the escape counter. */ |
| escapes = 0; |
| if (get_key_buffer_len() == 0) { |
| /* One escape followed by a non-escape, and |
| * there aren't any other keystrokes waiting: |
| * meta key sequence mode. Set meta_key to |
| * TRUE, and save the lowercase version of the |
| * non-escape character as the result. */ |
| *meta_key = TRUE; |
| retval = tolower(*kbinput); |
| } else |
| /* One escape followed by a non-escape, and |
| * there are other keystrokes waiting: escape |
| * sequence mode. Interpret the escape |
| * sequence. */ |
| retval = parse_escape_seq_kbinput(win, |
| *kbinput); |
| break; |
| case 2: |
| if (get_key_buffer_len() == 0) { |
| if (('0' <= *kbinput && *kbinput <= '2' && |
| byte_digits == 0) || ('0' <= *kbinput && |
| *kbinput <= '9' && byte_digits > 0)) { |
| /* Two escapes followed by one or more |
| * decimal digits, and there aren't any |
| * other keystrokes waiting: byte sequence |
| * mode. If the byte 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. */ |
| int byte; |
| |
| byte_digits++; |
| byte = get_byte_kbinput(*kbinput); |
| |
| if (byte != ERR) { |
| char *byte_mb; |
| int byte_mb_len, *seq, i; |
| |
| /* If we've read in a complete byte |
| * sequence, reset the escape counter |
| * and the byte sequence counter, and |
| * put back the corresponding byte |
| * value. */ |
| escapes = 0; |
| byte_digits = 0; |
| |
| /* Put back the multibyte equivalent of |
| * the byte value. */ |
| byte_mb = make_mbchar((long)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, we're not in the middle of a |
| * byte sequence, and there aren't any |
| * other keystrokes waiting: 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; |
| } |
| } |
| } else { |
| /* Two escapes followed by a non-escape, and |
| * there are other keystrokes waiting: combined |
| * meta and escape sequence mode. Reset the |
| * escape counter, set meta_key to TRUE, and |
| * interpret the escape sequence. */ |
| escapes = 0; |
| *meta_key = TRUE; |
| retval = parse_escape_seq_kbinput(win, |
| *kbinput); |
| } |
| break; |
| case 3: |
| /* Reset the escape counter. */ |
| escapes = 0; |
| if (get_key_buffer_len() == 0) |
| /* Three escapes followed by a non-escape, and |
| * there aren't any other keystrokes waiting: |
| * normal input mode. Save the non-escape |
| * character as the result. */ |
| retval = *kbinput; |
| else |
| /* Three escapes followed by a non-escape, and |
| * there are other keystrokes waiting: combined |
| * control character and escape sequence mode. |
| * Interpret the escape sequence, and interpret |
| * the result as a control sequence. */ |
| retval = get_control_kbinput( |
| parse_escape_seq_kbinput(win, |
| *kbinput)); |
| break; |
| } |
| } |
| |
| if (retval != ERR) { |
| switch (retval) { |
| case NANO_CONTROL_8: |
| retval = ISSET(REBIND_DELETE) ? sc_seq_or(DO_DELETE, 0) : |
| sc_seq_or(DO_BACKSPACE, 0); |
| break; |
| case KEY_DOWN: |
| #ifdef KEY_SDOWN |
| /* ncurses and Slang don't support KEY_SDOWN. */ |
| case KEY_SDOWN: |
| #endif |
| retval = sc_seq_or(DO_DOWN_VOID, *kbinput); |
| break; |
| case KEY_UP: |
| #ifdef KEY_SUP |
| /* ncurses and Slang don't support KEY_SUP. */ |
| case KEY_SUP: |
| #endif |
| retval = sc_seq_or(DO_UP_VOID, *kbinput); |
| break; |
| case KEY_LEFT: |
| #ifdef KEY_SLEFT |
| /* Slang doesn't support KEY_SLEFT. */ |
| case KEY_SLEFT: |
| #endif |
| retval = sc_seq_or(DO_LEFT, *kbinput); |
| break; |
| case KEY_RIGHT: |
| #ifdef KEY_SRIGHT |
| /* Slang doesn't support KEY_SRIGHT. */ |
| case KEY_SRIGHT: |
| #endif |
| retval = sc_seq_or(DO_RIGHT, *kbinput); |
| break; |
| #ifdef KEY_SHOME |
| /* HP-UX 10-11 and Slang don't support KEY_SHOME. */ |
| case KEY_SHOME: |
| #endif |
| case KEY_A1: /* Home (7) on numeric keypad with |
| * NumLock off. */ |
| retval = sc_seq_or(DO_HOME, *kbinput); |
| break; |
| case KEY_BACKSPACE: |
| retval = sc_seq_or(DO_BACKSPACE, *kbinput); |
| break; |
| #ifdef KEY_SDC |
| /* Slang doesn't support KEY_SDC. */ |
| case KEY_SDC: |
| if (ISSET(REBIND_DELETE)) |
| retval = sc_seq_or(DO_DELETE, *kbinput); |
| else |
| retval = sc_seq_or(DO_BACKSPACE, *kbinput); |
| break; |
| #endif |
| #ifdef KEY_SIC |
| /* Slang doesn't support KEY_SIC. */ |
| case KEY_SIC: |
| retval = sc_seq_or(DO_INSERTFILE_VOID, *kbinput); |
| break; |
| #endif |
| case KEY_C3: /* PageDown (4) on numeric keypad with |
| * NumLock off. */ |
| retval = sc_seq_or(DO_PAGE_DOWN, *kbinput); |
| break; |
| case KEY_A3: /* PageUp (9) on numeric keypad with |
| * NumLock off. */ |
| retval = sc_seq_or(DO_PAGE_UP, *kbinput); |
| break; |
| case KEY_ENTER: |
| retval = sc_seq_or(DO_ENTER, *kbinput); |
| break; |
| case KEY_B2: /* Center (5) on numeric keypad with |
| * NumLock off. */ |
| retval = ERR; |
| break; |
| case KEY_C1: /* End (1) on numeric keypad with |
| * NumLock off. */ |
| #ifdef KEY_SEND |
| /* HP-UX 10-11 and Slang don't support KEY_SEND. */ |
| case KEY_SEND: |
| #endif |
| retval = sc_seq_or(DO_END, *kbinput); |
| break; |
| #ifdef KEY_BEG |
| /* Slang doesn't support KEY_BEG. */ |
| case KEY_BEG: /* Center (5) on numeric keypad with |
| * NumLock off. */ |
| retval = ERR; |
| break; |
| #endif |
| #ifdef KEY_CANCEL |
| /* Slang doesn't support KEY_CANCEL. */ |
| case KEY_CANCEL: |
| #ifdef KEY_SCANCEL |
| /* Slang doesn't support KEY_SCANCEL. */ |
| case KEY_SCANCEL: |
| #endif |
| retval = first_sc_for(currmenu, CANCEL_MSG)->seq; |
| break; |
| #endif |
| #ifdef KEY_SBEG |
| /* Slang doesn't support KEY_SBEG. */ |
| case KEY_SBEG: /* Center (5) on numeric keypad with |
| * NumLock off. */ |
| retval = ERR; |
| break; |
| #endif |
| #ifdef KEY_SSUSPEND |
| /* Slang doesn't support KEY_SSUSPEND. */ |
| case KEY_SSUSPEND: |
| retval = sc_seq_or(DO_SUSPEND_VOID, 0); |
| break; |
| #endif |
| #ifdef KEY_SUSPEND |
| /* Slang doesn't support KEY_SUSPEND. */ |
| case KEY_SUSPEND: |
| retval = sc_seq_or(DO_SUSPEND_VOID, 0); |
| 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: |
| retval = ERR; |
| break; |
| #endif |
| #if !defined(NANO_TINY) && defined(KEY_RESIZE) |
| /* Since we don't change the default SIGWINCH handler when |
| * NANO_TINY is defined, KEY_RESIZE is never generated. |
| * Also, Slang and SunOS 5.7-5.9 don't support |
| * KEY_RESIZE. */ |
| case KEY_RESIZE: |
| retval = ERR; |
| break; |
| #endif |
| } |
| |
| /* If our result is 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 = %s, func_key = %s, escapes = %d, byte_digits = %d, retval = %d\n", *kbinput, *meta_key ? "TRUE" : "FALSE", *func_key ? "TRUE" : "FALSE", escapes, byte_digits, retval); |
| #endif |
| |
| free(kbinput); |
| |
| /* 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. |
| * Assume that Escape has already been read in. */ |
| int get_escape_seq_kbinput(const int *seq, size_t seq_len) |
| { |
| int retval = ERR; |
| |
| if (seq_len > 1) { |
| switch (seq[0]) { |
| case 'O': |
| switch (seq[1]) { |
| case '1': |
| if (seq_len >= 3) { |
| switch (seq[2]) { |
| case ';': |
| if (seq_len >= 4) { |
| switch (seq[3]) { |
| case '2': |
| if (seq_len >= 5) { |
| switch (seq[4]) { |
| case 'A': /* Esc O 1 ; 2 A == Shift-Up on |
| * Terminal. */ |
| case 'B': /* Esc O 1 ; 2 B == Shift-Down on |
| * Terminal. */ |
| case 'C': /* Esc O 1 ; 2 C == Shift-Right on |
| * Terminal. */ |
| case 'D': /* Esc O 1 ; 2 D == Shift-Left on |
| * Terminal. */ |
| retval = get_escape_seq_abcd(seq[4]); |
| break; |
| case 'P': /* Esc O 1 ; 2 P == F13 on |
| * Terminal. */ |
| retval = KEY_F(13); |
| break; |
| case 'Q': /* Esc O 1 ; 2 Q == F14 on |
| * Terminal. */ |
| retval = KEY_F(14); |
| break; |
| case 'R': /* Esc O 1 ; 2 R == F15 on |
| * Terminal. */ |
| retval = KEY_F(15); |
| break; |
| case 'S': /* Esc O 1 ; 2 S == F16 on |
| * Terminal. */ |
| retval = KEY_F(16); |
| break; |
| } |
| } |
| break; |
| case '5': |
| if (seq_len >= 5) { |
| switch (seq[4]) { |
| case 'A': /* Esc O 1 ; 5 A == Ctrl-Up on |
| * Terminal. */ |
| case 'B': /* Esc O 1 ; 5 B == Ctrl-Down on |
| * Terminal. */ |
| case 'C': /* Esc O 1 ; 5 C == Ctrl-Right on |
| * Terminal. */ |
| case 'D': /* Esc O 1 ; 5 D == Ctrl-Left on |
| * Terminal. */ |
| retval = get_escape_seq_abcd(seq[4]); |
| break; |
| } |
| } |
| break; |
| } |
| } |
| break; |
| } |
| } |
| break; |
| 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. */ |
| retval = KEY_B2; |
| break; |
| case 'F': /* Esc O F == End on xterm/Terminal. */ |
| retval = sc_seq_or(DO_END, 0); |
| break; |
| case 'H': /* Esc O H == Home on xterm/Terminal. */ |
| retval = sc_seq_or(DO_HOME, 0);; |
| break; |
| case 'M': /* Esc O M == Enter on numeric keypad with |
| * NumLock off on VT100/VT220/VT320/xterm/ |
| * rxvt/Eterm. */ |
| retval = sc_seq_or(DO_HOME, 0);; |
| 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/Eterm/Terminal. */ |
| retval = '*'; |
| break; |
| case 'k': /* Esc O k == '+' on numeric keypad with |
| * NumLock off on VT100/VT220/VT320/xterm/ |
| * rxvt/Eterm/Terminal. */ |
| retval = '+'; |
| break; |
| case 'l': /* Esc O l == ',' on numeric keypad with |
| * NumLock off on VT100/VT220/VT320/xterm/ |
| * rxvt/Eterm/Terminal. */ |
| retval = ','; |
| break; |
| case 'm': /* Esc O m == '-' on numeric keypad with |
| * NumLock off on VT100/VT220/VT320/xterm/ |
| * rxvt/Eterm/Terminal. */ |
| retval = '-'; |
| break; |
| case 'n': /* Esc O n == Delete (.) on numeric keypad |
| * with NumLock off on VT100/VT220/VT320/ |
| * xterm/rxvt/Eterm/Terminal. */ |
| retval = sc_seq_or(DO_DELETE, 0);; |
| break; |
| case 'o': /* Esc O o == '/' on numeric keypad with |
| * NumLock off on VT100/VT220/VT320/xterm/ |
| * rxvt/Eterm/Terminal. */ |
| retval = '/'; |
| break; |
| case 'p': /* Esc O p == Insert (0) on numeric keypad |
| * with NumLock off on VT100/VT220/VT320/ |
| * rxvt/Eterm/Terminal. */ |
| retval = sc_seq_or(DO_INSERTFILE_VOID, 0);; |
| break; |
| case 'q': /* Esc O q == End (1) on numeric keypad |
| * with NumLock off on VT100/VT220/VT320/ |
| * rxvt/Eterm/Terminal. */ |
| retval = sc_seq_or(DO_END, 0);; |
| break; |
| case 'r': /* Esc O r == Down (2) on numeric keypad |
| * with NumLock off on VT100/VT220/VT320/ |
| * rxvt/Eterm/Terminal. */ |
| retval = sc_seq_or(DO_DOWN_VOID, 0);; |
| break; |
| case 's': /* Esc O s == PageDown (3) on numeric |
| * keypad with NumLock off on VT100/VT220/ |
| * VT320/rxvt/Eterm/Terminal. */ |
| retval = sc_seq_or(DO_PAGE_DOWN, 0);; |
| break; |
| case 't': /* Esc O t == Left (4) on numeric keypad |
| * with NumLock off on VT100/VT220/VT320/ |
| * rxvt/Eterm/Terminal. */ |
| retval = sc_seq_or(DO_LEFT, 0);; |
| break; |
| case 'u': /* Esc O u == Center (5) on numeric keypad |
| * with NumLock off on VT100/VT220/VT320/ |
| * rxvt/Eterm. */ |
| retval = KEY_B2; |
| break; |
| case 'v': /* Esc O v == Right (6) on numeric keypad |
| * with NumLock off on VT100/VT220/VT320/ |
| * rxvt/Eterm/Terminal. */ |
| retval = sc_seq_or(DO_RIGHT, 0); |
| break; |
| case 'w': /* Esc O w == Home (7) on numeric keypad |
| * with NumLock off on VT100/VT220/VT320/ |
| * rxvt/Eterm/Terminal. */ |
| retval = sc_seq_or(DO_HOME, 0); |
| break; |
| case 'x': /* Esc O x == Up (8) on numeric keypad |
| * with NumLock off on VT100/VT220/VT320/ |
| * rxvt/Eterm/Terminal. */ |
| retval = sc_seq_or(DO_UP_VOID, 0); |
| break; |
| case 'y': /* Esc O y == PageUp (9) on numeric keypad |
| * with NumLock off on VT100/VT220/VT320/ |
| * rxvt/Eterm/Terminal. */ |
| retval = sc_seq_or(DO_PAGE_UP, 0); |
| 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 = sc_seq_or(DO_HOME, 0);; |
| 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/Terminal. */ |
| retval = sc_seq_or(DO_INSERTFILE_VOID, 0);; |
| break; |
| } |
| } |
| break; |
| case '3': /* Esc [ 3 ~ == Delete on VT220/VT320/ |
| * Linux console/xterm/Terminal. */ |
| retval = sc_seq_or(DO_DELETE, 0);; |
| break; |
| case '4': /* Esc [ 4 ~ == End on VT220/VT320/Linux |
| * console/xterm. */ |
| retval = sc_seq_or(DO_END, 0);; |
| break; |
| case '5': /* Esc [ 5 ~ == PageUp on VT220/VT320/ |
| * Linux console/xterm/Terminal; |
| * Esc [ 5 ^ == PageUp on Eterm. */ |
| retval = sc_seq_or(DO_PAGE_UP, 0);; |
| break; |
| case '6': /* Esc [ 6 ~ == PageDown on VT220/VT320/ |
| * Linux console/xterm/Terminal; |
| * Esc [ 6 ^ == PageDown on Eterm. */ |
| retval = sc_seq_or(DO_PAGE_DOWN, 0);; |
| break; |
| case '7': /* Esc [ 7 ~ == Home on rxvt. */ |
| retval = sc_seq_or(DO_HOME, 0); |
| break; |
| case '8': /* Esc [ 8 ~ == End on rxvt. */ |
| retval = sc_seq_or(DO_END, 0); |
| break; |
| case '9': /* Esc [ 9 == Delete on Mach console. */ |
| retval = sc_seq_or(DO_DELETE, 0);; |
| break; |
| case '@': /* Esc [ @ == Insert on Mach console. */ |
| retval = sc_seq_or(DO_INSERTFILE_VOID, 0);; |
| break; |
| case 'A': /* Esc [ A == Up on ANSI/VT220/Linux |
| * console/FreeBSD console/Mach console/ |
| * rxvt/Eterm/Terminal. */ |
| case 'B': /* Esc [ B == Down on ANSI/VT220/Linux |
| * console/FreeBSD console/Mach console/ |
| * rxvt/Eterm/Terminal. */ |
| case 'C': /* Esc [ C == Right on ANSI/VT220/Linux |
| * console/FreeBSD console/Mach console/ |
| * rxvt/Eterm/Terminal. */ |
| case 'D': /* Esc [ D == Left on ANSI/VT220/Linux |
| * console/FreeBSD console/Mach console/ |
| * rxvt/Eterm/Terminal. */ |
| retval = get_escape_seq_abcd(seq[1]); |
| break; |
| case 'E': /* Esc [ E == Center (5) on numeric keypad |
| * with NumLock off on FreeBSD console/ |
| * Terminal. */ |
| retval = KEY_B2; |
| break; |
| case 'F': /* Esc [ F == End on FreeBSD |
| * console/Eterm. */ |
| retval = sc_seq_or(DO_END, 0); |
| break; |
| case 'G': /* Esc [ G == PageDown on FreeBSD |
| * console. */ |
| retval = sc_seq_or(DO_PAGE_DOWN, 0); |
| break; |
| case 'H': /* Esc [ H == Home on ANSI/VT220/FreeBSD |
| * console/Mach console/Eterm. */ |
| retval = sc_seq_or(DO_HOME, 0); |
| break; |
| case 'I': /* Esc [ I == PageUp on FreeBSD |
| * console. */ |
| retval = sc_seq_or(DO_PAGE_UP, 0); |
| break; |
| case 'L': /* Esc [ L == Insert on ANSI/FreeBSD |
| * console. */ |
| retval = sc_seq_or(DO_INSERTFILE_VOID, 0); |
| 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 = sc_seq_or(DO_PAGE_DOWN, 0); |
| break; |
| case 'V': /* Esc [ V == PageUp on Mach console. */ |
| retval = sc_seq_or(DO_PAGE_UP, 0); |
| 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 = sc_seq_or(DO_END, 0); |
| 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\n", retval); |
| #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 sc_seq_or(DO_UP_VOID, 0);; |
| case 'b': |
| return sc_seq_or(DO_DOWN_VOID, 0);; |
| case 'c': |
| return sc_seq_or(DO_RIGHT, 0);; |
| case 'd': |
| return sc_seq_or(DO_LEFT, 0);; |
| default: |
| return ERR; |
| } |
| } |
| |
| /* Interpret the escape sequence in the keystroke buffer, the first |
| * character of which is kbinput. Assume that the keystroke buffer |
| * isn't empty, and that the initial escape has already been read in. */ |
| int parse_escape_seq_kbinput(WINDOW *win, int kbinput) |
| { |
| int retval, *seq; |
| size_t seq_len; |
| |
| /* 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_key_buffer_len(); |
| seq = get_input(NULL, seq_len); |
| retval = get_escape_seq_kbinput(seq, seq_len); |
| |
| free(seq); |
| |
| /* If we got an unrecognized escape sequence, throw it out. */ |
| if (retval == ERR) { |
| if (win == edit) { |
| statusbar(_("Unknown Command")); |
| beep(); |
| } |
| } |
| |
| #ifdef DEBUG |
| fprintf(stderr, "parse_escape_seq_kbinput(): kbinput = %d, seq_len = %lu, retval = %d\n", kbinput, (unsigned long)seq_len, retval); |
| #endif |
| |
| return retval; |
| } |
| |
| /* 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) |
| { |
| static int byte_digits = 0, byte = 0; |
| int retval = ERR; |
| |
| /* Increment the byte digit counter. */ |
| byte_digits++; |
| |
| switch (byte_digits) { |
| case 1: |
| /* First digit: This must be from zero to two. Put it in |
| * the 100's position of the byte sequence holder. */ |
| if ('0' <= kbinput && kbinput <= '2') |
| byte = (kbinput - '0') * 100; |
| else |
| /* This isn't the start of a byte sequence. Return this |
| * character as the result. */ |
| retval = kbinput; |
| break; |
| case 2: |
| /* Second digit: This must be from zero to five if the first |
| * was two, and may be any decimal value if the first was |
| * zero or one. Put it in 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 |
| /* This isn't the second digit of a byte sequence. |
| * Return this character as the result. */ |
| retval = kbinput; |
| break; |
| case 3: |
| /* Third digit: This must be from zero to five if the first |
| * was two and the second was between zero and five, and may |
| * be any decimal value if the first was zero or one and the |
| * second was between six and nine. Put it in the 1's |
| * position of the byte sequence holder. */ |
| if (('0' <= kbinput && kbinput <= '5') || (byte < 250 && |
| '6' <= kbinput && kbinput <= '9')) { |
| byte += kbinput - '0'; |
| /* If this character is a valid decimal value, then the |
| * byte sequence is complete. */ |
| retval = byte; |
| } else |
| /* This isn't the third digit of a byte sequence. |
| * Return this character as the result. */ |
| retval = kbinput; |
| break; |
| default: |
| /* If there are more than three digits, return this |
| * character as the result. (Maybe we should produce an |
| * error instead?) */ |
| 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; |
| } |
| |
| #ifdef ENABLE_UTF8 |
| /* If the character in kbinput is a valid hexadecimal digit, multiply it |
| * by factor and add the result to uni. */ |
| long add_unicode_digit(int kbinput, long factor, long *uni) |
| { |
| long retval = ERR; |
| |
| if ('0' <= kbinput && kbinput <= '9') |
| *uni += (kbinput - '0') * factor; |
| else if ('a' <= tolower(kbinput) && tolower(kbinput) <= 'f') |
| *uni += (tolower(kbinput) - 'a' + 10) * factor; |
| else |
| /* If this character isn't a valid hexadecimal value, save it as |
| * the result. */ |
| retval = kbinput; |
| |
| return retval; |
| } |
| |
| /* Translate a Unicode sequence: turn a six-digit hexadecimal number |
| * (from 000000 to 10FFFF, case-insensitive) into its corresponding |
| * multibyte value. */ |
| long get_unicode_kbinput(int kbinput) |
| { |
| static int uni_digits = 0; |
| static long uni = 0; |
| long retval = ERR; |
| |
| /* Increment the Unicode digit counter. */ |
| uni_digits++; |
| |
| switch (uni_digits) { |
| case 1: |
| /* First digit: This must be zero or one. Put it in the |
| * 0x100000's position of the Unicode sequence holder. */ |
| if ('0' <= kbinput && kbinput <= '1') |
| uni = (kbinput - '0') * 0x100000; |
| else |
| /* This isn't the first digit of a Unicode sequence. |
| * Return this character as the result. */ |
| retval = kbinput; |
| break; |
| case 2: |
| /* Second digit: This must be zero if the first was one, and |
| * may be any hexadecimal value if the first was zero. Put |
| * it in the 0x10000's position of the Unicode sequence |
| * holder. */ |
| if (uni == 0 || '0' == kbinput) |
| retval = add_unicode_digit(kbinput, 0x10000, &uni); |
| else |
| /* This isn't the second digit of a Unicode sequence. |
| * Return this character as the result. */ |
| retval = kbinput; |
| break; |
| case 3: |
| /* Third digit: This may be any hexadecimal value. Put it |
| * in the 0x1000's position of the Unicode sequence |
| * holder. */ |
| retval = add_unicode_digit(kbinput, 0x1000, &uni); |
| break; |
| case 4: |
| /* Fourth digit: This may be any hexadecimal value. Put it |
| * in the 0x100's position of the Unicode sequence |
| * holder. */ |
| retval = add_unicode_digit(kbinput, 0x100, &uni); |
| break; |
| case 5: |
| /* Fifth digit: This may be any hexadecimal value. Put it |
| * in the 0x10's position of the Unicode sequence holder. */ |
| retval = add_unicode_digit(kbinput, 0x10, &uni); |
| break; |
| case 6: |
| /* Sixth digit: This may be any hexadecimal value. Put it |
| * in the 0x1's position of the Unicode sequence holder. */ |
| retval = add_unicode_digit(kbinput, 0x1, &uni); |
| /* If this character is a valid hexadecimal value, then the |
| * Unicode sequence is complete. */ |
| if (retval == ERR) |
| retval = uni; |
| break; |
| default: |
| /* If there are more than six digits, return this character |
| * as the result. (Maybe we should produce an error |
| * instead?) */ |
| retval = kbinput; |
| break; |
| } |
| |
| /* If we have a result, reset the Unicode digit counter and the |
| * Unicode sequence holder. */ |
| if (retval != ERR) { |
| uni_digits = 0; |
| uni = 0; |
| } |
| |
| #ifdef DEBUG |
| fprintf(stderr, "get_unicode_kbinput(): kbinput = %d, uni_digits = %d, uni = %ld, retval = %ld\n", kbinput, uni_digits, uni, retval); |
| #endif |
| |
| return retval; |
| } |
| #endif /* ENABLE_UTF8 */ |
| |
| /* 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-Space (Ctrl-2, Ctrl-@, Ctrl-`) */ |
| if (kbinput == ' ' || kbinput == '2') |
| retval = NANO_CONTROL_SPACE; |
| /* Ctrl-/ (Ctrl-7, Ctrl-_) */ |
| else if (kbinput == '/') |
| retval = NANO_CONTROL_7; |
| /* Ctrl-3 (Ctrl-[, Esc) to Ctrl-7 (Ctrl-/, Ctrl-_) */ |
| else if ('3' <= kbinput && kbinput <= '7') |
| retval = kbinput - 24; |
| /* Ctrl-8 (Ctrl-?) */ |
| else if (kbinput == '8' || kbinput == '?') |
| retval = NANO_CONTROL_8; |
| /* Ctrl-@ (Ctrl-Space, Ctrl-2, Ctrl-`) to Ctrl-_ (Ctrl-/, Ctrl-7) */ |
| else if ('@' <= kbinput && kbinput <= '_') |
| retval = kbinput - '@'; |
| /* Ctrl-` (Ctrl-2, Ctrl-Space, Ctrl-@) to Ctrl-~ (Ctrl-6, Ctrl-^) */ |
| else if ('`' <= kbinput && kbinput <= '~') |
| retval = kbinput - '`'; |
| 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 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 if necessary so that we |
| * don't get extended keypad values. */ |
| if (ISSET(PRESERVE)) |
| disable_flow_control(); |
| if (!ISSET(REBIND_KEYPAD)) |
| 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 if necessary now that we're done. */ |
| if (ISSET(PRESERVE)) |
| enable_flow_control(); |
| if (!ISSET(REBIND_KEYPAD)) |
| 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 multibyte value if possible. After |
| * that, leave the input as-is. */ |
| int *parse_verbatim_kbinput(WINDOW *win, size_t *kbinput_len) |
| { |
| int *kbinput, *retval; |
| |
| /* Read in the first keystroke. */ |
| while ((kbinput = get_input(win, 1)) == NULL); |
| |
| #ifdef ENABLE_UTF8 |
| if (using_utf8()) { |
| /* Check whether the first keystroke is a valid hexadecimal |
| * digit. */ |
| long uni = get_unicode_kbinput(*kbinput); |
| |
| /* If the first keystroke isn't a valid hexadecimal digit, put |
| * back the first keystroke. */ |
| if (uni != ERR) |
| unget_input(kbinput, 1); |
| |
| /* Otherwise, read in keystrokes until we have a complete |
| * Unicode sequence, and put back the corresponding Unicode |
| * value. */ |
| else { |
| char *uni_mb; |
| int uni_mb_len, *seq, i; |
| |
| if (win == edit) |
| /* TRANSLATORS: This is displayed during the input of a |
| * six-digit hexadecimal Unicode character code. */ |
| statusbar(_("Unicode Input")); |
| |
| while (uni == ERR) { |
| while ((kbinput = get_input(win, 1)) == NULL); |
| |
| uni = get_unicode_kbinput(*kbinput); |
| } |
| |
| /* Put back the multibyte equivalent of the Unicode |
| * value. */ |
| uni_mb = make_mbchar(uni, &uni_mb_len); |
| |
| seq = (int *)nmalloc(uni_mb_len * sizeof(int)); |
| |
| for (i = 0; i < uni_mb_len; i++) |
| seq[i] = (unsigned char)uni_mb[i]; |
| |
| unget_input(seq, uni_mb_len); |
| |
| free(seq); |
| free(uni_mb); |
| } |
| } else |
| #endif /* ENABLE_UTF8 */ |
| |
| /* Put back the first keystroke. */ |
| unget_input(kbinput, 1); |
| |
| free(kbinput); |
| |
| /* Get the complete sequence, and save the characters in it as the |
| * result. */ |
| *kbinput_len = get_key_buffer_len(); |
| retval = get_input(NULL, *kbinput_len); |
| |
| return retval; |
| } |
| |
| #ifndef DISABLE_MOUSE |
| /* Handle any mouse event that may have occurred. We currently handle |
| * releases/clicks of the first mouse button. If allow_shortcuts is |
| * TRUE, releasing/clicking on a visible shortcut will put back the |
| * keystroke associated with that shortcut. If NCURSES_MOUSE_VERSION is |
| * at least 2, we also currently handle presses of the fourth mouse |
| * button (upward rolls of the mouse wheel) by putting back the |
| * keystrokes to move up, and presses of the fifth mouse button |
| * (downward rolls of the mouse wheel) by putting back the keystrokes to |
| * move down. We also store the coordinates of a mouse event that needs |
| * to be handled in mouse_x and mouse_y, relative to the entire screen. |
| * Return -1 on error, 0 if the mouse event needs to be handled, 1 if |
| * it's been handled by putting back keystrokes that need to be handled. |
| * or 2 if it's been ignored. Assume that KEY_MOUSE has already been |
| * read in. */ |
| int get_mouseinput(int *mouse_x, int *mouse_y, bool allow_shortcuts) |
| { |
| MEVENT mevent; |
| bool in_bottomwin; |
| subnfunc *f; |
| |
| *mouse_x = -1; |
| *mouse_y = -1; |
| |
| /* First, get the actual mouse event. */ |
| if (getmouse(&mevent) == ERR) |
| return -1; |
| |
| /* Save the screen coordinates where the mouse event took place. */ |
| *mouse_x = mevent.x; |
| *mouse_y = mevent.y; |
| |
| in_bottomwin = wenclose(bottomwin, *mouse_y, *mouse_x); |
| |
| /* Handle releases/clicks of the first mouse button. */ |
| if (mevent.bstate & (BUTTON1_RELEASED | BUTTON1_CLICKED)) { |
| /* If we're allowing shortcuts, the current shortcut list is |
| * being displayed on the last two lines of the screen, and the |
| * first mouse button was released on/clicked inside it, we need |
| * to figure out which shortcut was released on/clicked and put |
| * back the equivalent keystroke(s) for it. */ |
| if (allow_shortcuts && !ISSET(NO_HELP) && in_bottomwin) { |
| int i; |
| /* The width of all the shortcuts, except for the last |
| * two, in the shortcut list in bottomwin. */ |
| int j; |
| /* The y-coordinate relative to the beginning of the |
| * shortcut list in bottomwin. */ |
| size_t currslen; |
| /* The number of shortcuts in the current shortcut |
| * list. */ |
| |
| /* Translate the mouse event coordinates so that they're |
| * relative to bottomwin. */ |
| wmouse_trafo(bottomwin, mouse_y, mouse_x, FALSE); |
| |
| /* Handle releases/clicks of the first mouse button on the |
| * statusbar elsewhere. */ |
| if (*mouse_y == 0) { |
| /* Restore the untranslated mouse event coordinates, so |
| * that they're relative to the entire screen again. */ |
| *mouse_x = mevent.x; |
| *mouse_y = mevent.y; |
| |
| return 0; |
| } |
| |
| /* Calculate the y-coordinate relative to the beginning of |
| * the shortcut list in bottomwin. */ |
| j = *mouse_y - 1; |
| |
| /* Get the shortcut lists' length. */ |
| if (currmenu == MMAIN) |
| currslen = MAIN_VISIBLE; |
| else { |
| currslen = length_of_list(currmenu); |
| |
| /* We don't show any more shortcuts than the main list |
| * does. */ |
| if (currslen > MAIN_VISIBLE) |
| currslen = MAIN_VISIBLE; |
| } |
| |
| /* Calculate the width of all of the shortcuts in the list |
| * except for the last two, which are longer by (COLS % i) |
| * columns so as to not waste space. */ |
| if (currslen < 2) |
| i = COLS / (MAIN_VISIBLE / 2); |
| else |
| i = COLS / ((currslen / 2) + (currslen % 2)); |
| |
| /* Calculate the x-coordinate relative to the beginning of |
| * the shortcut list in bottomwin, and add it to j. j |
| * should now be the index in the shortcut list of the |
| * shortcut we released/clicked on. */ |
| j = (*mouse_x / i) * 2 + j; |
| |
| /* Adjust j if we released on the last two shortcuts. */ |
| if ((j >= currslen) && (*mouse_x % i < COLS % i)) |
| j -= 2; |
| |
| /* Ignore releases/clicks of the first mouse button beyond |
| * the last shortcut. */ |
| if (j >= currslen) |
| return 2; |
| |
| /* Go through the shortcut list to determine which shortcut |
| * we released/clicked on. */ |
| f = allfuncs; |
| |
| for (; j > 0; j--) { |
| if (f->next != NULL) |
| f = f->next; |
| |
| while (f->next != NULL && ((f->menus & currmenu) == 0 |
| #ifndef DISABLE_HELP |
| || strlen(f->help) == 0 |
| #endif |
| )) |
| f = f->next; |
| } |
| |
| |
| /* And put back the equivalent key. */ |
| if (f != NULL) { |
| const sc *s = first_sc_for(currmenu, f->scfunc); |
| if (s != NULL) |
| unget_kbinput(s->seq, s->type == META, FALSE); |
| } |
| } else |
| /* Handle releases/clicks of the first mouse button that |
| * aren't on the current shortcut list elsewhere. */ |
| return 0; |
| } |
| #if NCURSES_MOUSE_VERSION >= 2 |
| /* Handle presses of the fourth mouse button (upward rolls of the |
| * mouse wheel) and presses of the fifth mouse button (downward |
| * rolls of the mouse wheel) . */ |
| else if (mevent.bstate & (BUTTON4_PRESSED | BUTTON5_PRESSED)) { |
| bool in_edit = wenclose(edit, *mouse_y, *mouse_x); |
| |
| if (in_bottomwin) |
| /* Translate the mouse event coordinates so that they're |
| * relative to bottomwin. */ |
| wmouse_trafo(bottomwin, mouse_y, mouse_x, FALSE); |
| |
| if (in_edit || (in_bottomwin && *mouse_y == 0)) { |
| int i; |
| |
| /* One upward roll of the mouse wheel is equivalent to |
| * moving up three lines, and one downward roll of the mouse |
| * wheel is equivalent to moving down three lines. */ |
| for (i = 0; i < 3; i++) |
| unget_kbinput((mevent.bstate & BUTTON4_PRESSED) ? |
| sc_seq_or(do_up_void, 0) : sc_seq_or(DO_DOWN_VOID, 0);, FALSE, |
| FALSE); |
| |
| return 1; |
| } else |
| /* Ignore presses of the fourth mouse button and presses of |
| * the fifth mouse buttons that aren't on the edit window or |
| * the statusbar. */ |
| return 2; |
| } |
| #endif |
| |
| /* Ignore all other mouse events. */ |
| return 2; |
| } |
| #endif /* !DISABLE_MOUSE */ |
| |
| /* Return the shortcut corresponding to the values of kbinput (the key |
| * itself), meta_key (whether the key is a meta sequence), and func_key |
| * (whether the key is a function key), if any. The shortcut will be |
| * the first one in the list (control key, meta key sequence, function |
| * key, other meta key sequence) for the corresponding function. For |
| * example, passing in a meta key sequence that corresponds to a |
| * function with a control key, a function key, and a meta key sequence |
| * will return the control key corresponding to that function. */ |
| const sc *get_shortcut(int menu, int *kbinput, bool |
| *meta_key, bool *func_key) |
| { |
| sc *s; |
| |
| #ifdef DEBUG |
| fprintf(stderr, "get_shortcut(): kbinput = %d, meta_key = %s, func_key = %s\n", *kbinput, *meta_key ? "TRUE" : "FALSE", *func_key ? "TRUE" : "FALSE"); |
| #endif |
| |
| /* Check for shortcuts. */ |
| for (s = sclist; s != NULL; s = s->next) { |
| if ((menu & s->menu) |
| && ((s->type == META && *meta_key == TRUE && *kbinput == s->seq) |
| || (s->type != META && *kbinput == s->seq))) { |
| #ifdef DEBUG |
| fprintf (stderr, "matched seq \"%s\" and btw meta was %d (menus %d = %d)\n", s->keystr, *meta_key, menu, s->menu); |
| #endif |
| return s; |
| } |
| } |
| #ifdef DEBUG |
| fprintf (stderr, "matched nothing btw meta was %d\n", *meta_key); |
| #endif |
| |
| return NULL; |
| } |
| |
| |
| /* Try to get a function back from a window. Just a wrapper so |
| functions to need to create function_key meta_key blah blah |
| mmenu - what menu name to look through for valid funcs */ |
| const subnfunc *getfuncfromkey(WINDOW *win) |
| { |
| int kbinput; |
| bool func_key = FALSE, meta_key = FALSE; |
| const sc *s; |
| const subnfunc *f; |
| |
| kbinput = parse_kbinput(win, &meta_key, &func_key); |
| if (kbinput == 0) |
| return NULL; |
| |
| s = get_shortcut(currmenu, &kbinput, &meta_key, &func_key); |
| if (!s) |
| return NULL; |
| |
| f = sctofunc((sc *) s); |
| return f; |
| |
| } |
| |
| |
| |
| /* Move to (x, y) in win, and display a line of n spaces with the |
| * current attributes. */ |
| void blank_line(WINDOW *win, int y, int x, int n) |
| { |
| wmove(win, y, x); |
| |
| for (; n > 0; n--) |
| waddch(win, ' '); |
| } |
| |
| /* Blank the first line of the top portion of the window. */ |
| void blank_titlebar(void) |
| { |
| blank_line(topwin, 0, 0, COLS); |
| } |
| |
| /* If the MORE_SPACE flag isn't set, blank the second line of the top |
| * portion of the window. */ |
| void blank_topbar(void) |
| { |
| if (!ISSET(MORE_SPACE)) |
| blank_line(topwin, 1, 0, COLS); |
| } |
| |
| /* Blank all the lines of the middle portion of the window, i.e. the |
| * edit window. */ |
| void blank_edit(void) |
| { |
| int i; |
| |
| for (i = 0; i < editwinrows; i++) |
| blank_line(edit, i, 0, COLS); |
| } |
| |
| /* Blank the first line of the bottom portion of the window. */ |
| void blank_statusbar(void) |
| { |
| blank_line(bottomwin, 0, 0, COLS); |
| } |
| |
| /* If the NO_HELP flag isn't set, blank the last two lines of the bottom |
| * portion of the window. */ |
| void blank_bottombars(void) |
| { |
| if (!ISSET(NO_HELP)) { |
| blank_line(bottomwin, 1, 0, COLS); |
| blank_line(bottomwin, 2, 0, COLS); |
| } |
| } |
| |
| /* Check if the number of keystrokes needed to blank the statusbar has |
| * been pressed. If so, blank the statusbar, unless constant cursor |
| * position display is on. */ |
| void check_statusblank(void) |
| { |
| if (statusblank > 0) { |
| statusblank--; |
| |
| if (statusblank == 0 && !ISSET(CONST_UPDATE)) { |
| blank_statusbar(); |
| wnoutrefresh(bottomwin); |
| reset_cursor(); |
| wnoutrefresh(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; |
| int buf_mb_len; |
| |
| /* 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, ""); |
| |
| buf_mb = charalloc(mb_cur_max()); |
| |
| start_index = actual_x(buf, start_col); |
| column = strnlenpt(buf, start_index); |
| |
| assert(column <= start_col); |
| |
| /* Make sure there's enough room for the initial character, whether |
| * it's a multibyte control character, a non-control multibyte |
| * character, a tab character, or a null terminator. Rationale: |
| * |
| * multibyte control character followed by a null terminator: |
| * 1 byte ('^') + mb_cur_max() bytes + 1 byte ('\0') |
| * multibyte non-control character followed by a null terminator: |
| * mb_cur_max() bytes + 1 byte ('\0') |
| * tab character followed by a null terminator: |
| * mb_cur_max() bytes + (tabsize - 1) bytes + 1 byte ('\0') |
| * |
| * Since tabsize has a minimum value of 1, it can substitute for 1 |
| * byte above. */ |
| alloc_len = (mb_cur_max() + tabsize + 1) * MAX_BUF_SIZE; |
| converted = charalloc(alloc_len); |
| |
| index = 0; |
| |
| if (buf[start_index] != '\0' && buf[start_index] != '\t' && |
| (column < start_col || (dollars && column > 0))) { |
| /* 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); |
| |
| 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 ENABLE_UTF8 |
| else if (using_utf8() && mbwidth(buf_mb) == 2) { |
| if (column >= start_col) { |
| converted[index++] = ' '; |
| start_col++; |
| } |
| |
| converted[index++] = ' '; |
| start_col++; |
| |
| start_index += buf_mb_len; |
| } |
| #endif |
| } |
| |
| while (buf[start_index] != '\0') { |
| buf_mb_len = parse_mbchar(buf + start_index, buf_mb, NULL); |
| |
| /* Make sure there's enough room for the next character, whether |
| * it's a multibyte control character, a non-control multibyte |
| * character, a tab character, or a null terminator. */ |
| if (index + mb_cur_max() + tabsize + 1 >= alloc_len - 1) { |
| alloc_len += (mb_cur_max() + tabsize + 1) * MAX_BUF_SIZE; |
| converted = charealloc(converted, alloc_len); |
| } |
| |
| /* If buf contains a tab character, interpret it. */ |
| if (*buf_mb == '\t') { |
| #if !defined(NANO_TINY) && 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 buf |
| * contains an invalid multibyte control character, display it |
| * as such.*/ |
| } 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); |
| /* If buf contains a space character, interpret it. */ |
| } else if (*buf_mb == ' ') { |
| #if !defined(NANO_TINY) && 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++; |
| /* If buf contains a non-control character, interpret it. If |
| * buf contains an invalid multibyte non-control character, |
| * display it as such. */ |
| } else { |
| char *nctrl_buf_mb = charalloc(mb_cur_max()); |
| int nctrl_buf_mb_len, i; |
| |
| nctrl_buf_mb = mbrep(buf_mb, nctrl_buf_mb, |
| &nctrl_buf_mb_len); |
| |
| for (i = 0; i < nctrl_buf_mb_len; i++) |
| converted[index++] = nctrl_buf_mb[i]; |
| |
| start_col += mbwidth(nctrl_buf_mb); |
| |
| free(nctrl_buf_mb); |
| } |
| |
| start_index += buf_mb_len; |
| } |
| |
| free(buf_mb); |
| |
| assert(alloc_len >= index + 1); |
| |
| /* Null-terminate converted. */ |
| converted[index] = '\0'; |
| |
| /* Make sure converted takes up no more than len columns. */ |
| index = actual_x(converted, len); |
| null_at(&converted, index); |
| |
| return converted; |
| } |
| |
| /* If path is NULL, we're in normal editing mode, so display the current |
| * version of nano, the current filename, and whether the current file |
| * has been modified on the titlebar. If path isn't NULL, we're in the |
| * file browser, and path contains the directory to start the file |
| * browser in, so display the current version of nano and the contents |
| * of path on the titlebar. */ |
| void titlebar(const char *path) |
| { |
| int space = COLS; |
| /* The space we have available for display. */ |
| size_t verlen = strlenpt(PACKAGE_STRING) + 1; |
| /* The length of the version message in columns, plus one for |
| * padding. */ |
| const char *prefix; |
| /* "DIR:", "File:", or "New Buffer". Goes before filename. */ |
| size_t prefixlen; |
| /* The length of the prefix in columns, plus one for padding. */ |
| const char *state; |
| /* "Modified", "View", or "". Shows the state of this |
| * buffer. */ |
| size_t statelen = 0; |
| /* The length of the state in columns, or the length of |
| * "Modified" if the state is blank and we're not in the file |
| * browser. */ |
| char *exppath = NULL; |
| /* The filename, 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 || openfile->filename != NULL); |
| |
| wattron(topwin, reverse_attr); |
| |
| blank_titlebar(); |
| |
| /* space has to be at least 4: two spaces before the version message, |
| * at least one character of the version message, and one space |
| * after the version message. */ |
| if (space < 4) |
| space = 0; |
| else { |
| /* Limit verlen to 1/3 the length of the screen in columns, |
| * minus three columns for spaces. */ |
| if (verlen > (COLS / 3) - 3) |
| verlen = (COLS / 3) - 3; |
| } |
| |
| if (space >= 4) { |
| /* Add a space after the version message, and account for both |
| * it and the two spaces before it. */ |
| mvwaddnstr(topwin, 0, 2, PACKAGE_STRING, |
| actual_x(PACKAGE_STRING, verlen)); |
| verlen += 3; |
| |
| /* Account for the full length of the version message. */ |
| space -= verlen; |
| } |
| |
| #ifndef DISABLE_BROWSER |
| /* Don't display the state if we're in the file browser. */ |
| if (path != NULL) |
| state = ""; |
| else |
| #endif |
| state = openfile->modified ? _("Modified") : ISSET(VIEW_MODE) ? |
| _("View") : ""; |
| |
| statelen = strlenpt((*state == '\0' && path == NULL) ? |
| _("Modified") : state); |
| |
| /* If possible, add a space before state. */ |
| if (space > 0 && statelen < space) |
| statelen++; |
| else |
| goto the_end; |
| |
| #ifndef DISABLE_BROWSER |
| /* path should be a directory if we're in the file browser. */ |
| if (path != NULL) |
| prefix = _("DIR:"); |
| else |
| #endif |
| if (openfile->filename[0] == '\0') { |
| prefix = _("New Buffer"); |
| newfie = TRUE; |
| } else |
| prefix = _("File:"); |
| |
| prefixlen = strnlenpt(prefix, space - statelen) + 1; |
| |
| /* If newfie is FALSE, add a space after prefix. */ |
| if (!newfie && prefixlen + statelen < space) |
| prefixlen++; |
| |
| /* If we're not in the file browser, set path to the current |
| * filename. */ |
| if (path == NULL) |
| path = openfile->filename; |
| |
| /* Account for the full lengths of the prefix and the state. */ |
| if (space >= prefixlen + statelen) |
| space -= prefixlen + statelen; |
| else |
| space = 0; |
| /* space is now the room we have for the filename. */ |
| |
| if (!newfie) { |
| size_t lenpt = strlenpt(path), start_col; |
| |
| /* Don't set dots to TRUE if we have fewer than eight columns |
| * (i.e. one column for padding, plus seven columns for a |
| * filename). */ |
| dots = (space >= 8 && 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 is TRUE, we will display something like "File: |
| * ...ename". */ |
| if (dots) { |
| mvwaddnstr(topwin, 0, verlen - 1, 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); |
| } else { |
| size_t exppathlen = newfie ? 0 : strlenpt(exppath); |
| /* The length of the expanded filename. */ |
| |
| /* There is room for the whole filename, so we center it. */ |
| mvwaddnstr(topwin, 0, verlen + ((space - exppathlen) / 3), |
| prefix, actual_x(prefix, prefixlen)); |
| if (!newfie) { |
| waddch(topwin, ' '); |
| waddstr(topwin, exppath); |
| } |
| } |
| |
| the_end: |
| free(exppath); |
| |
| if (state[0] != '\0') { |
| if (statelen >= COLS - 1) |
| mvwaddnstr(topwin, 0, 0, state, actual_x(state, COLS)); |
| else { |
| assert(COLS - statelen - 1 >= 0); |
| |
| mvwaddnstr(topwin, 0, COLS - statelen - 1, state, |
| actual_x(state, statelen)); |
| } |
| } |
| |
| wattroff(topwin, reverse_attr); |
| |
| wnoutrefresh(topwin); |
| reset_cursor(); |
| wnoutrefresh(edit); |
| } |
| |
| /* Mark the current file as modified if it isn't already, and then |
| * update the titlebar to display the file's new status. */ |
| void set_modified(void) |
| { |
| if (!openfile->modified) { |
| openfile->modified = TRUE; |
| titlebar(NULL); |
| } |
| } |
| |
| /* Display a message on the statusbar, and set disable_cursorpos to |
| * TRUE, so that the message won't be immediately overwritten if |
| * constant cursor position display is on. */ |
| void statusbar(const char *msg, ...) |
| { |
| va_list ap; |
| char *bar, *foo; |
| size_t start_x, foo_len; |
| #if !defined(NANO_TINY) && defined(ENABLE_NANORC) |
| bool old_whitespace; |
| #endif |
| |
| 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 (isendwin()) { |
| vfprintf(stderr, msg, ap); |
| va_end(ap); |
| return; |
| } |
| |
| blank_statusbar(); |
| |
| #if !defined(NANO_TINY) && defined(ENABLE_NANORC) |
| 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_TINY) && 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, reverse_attr); |
| waddstr(bottomwin, "[ "); |
| waddstr(bottomwin, foo); |
| free(foo); |
| waddstr(bottomwin, " ]"); |
| wattroff(bottomwin, reverse_attr); |
| wnoutrefresh(bottomwin); |
| reset_cursor(); |
| wnoutrefresh(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-six keystrokes, as |
| * Pico does. */ |
| statusblank = |
| #ifndef NANO_TINY |
| ISSET(QUICK_BLANK) && !ISSET(CONST_UPDATE) ? 1 : |
| #endif |
| 26; |
| } |
| |
| /* Display the shortcut list in s on the last two rows of the bottom |
| * portion of the window. */ |
| void bottombars(int menu) |
| { |
| size_t i, colwidth, slen; |
| subnfunc *f; |
| const sc *s; |
| |
| if (ISSET(NO_HELP)) |
| return; |
| |
| if (menu == MMAIN) { |
| slen = MAIN_VISIBLE; |
| |
| assert(slen <= length_of_list(menu)); |
| } else { |
| slen = length_of_list(menu); |
| |
| /* 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, except for the |
| * last two, which will be longer by (COLS % colwidth) columns so as |
| * to not waste space. We need at least three columns to display |
| * anything properly. */ |
| colwidth = COLS / ((slen / 2) + (slen % 2)); |
| |
| blank_bottombars(); |
| |
| #ifdef DEBUG |
| fprintf(stderr, "In bottombars, and slen == \"%d\"\n", (int) slen); |
| #endif |
| |
| for (f = allfuncs, i = 0; i < slen && f != NULL; f = f->next) { |
| |
| #ifdef DEBUG |
| fprintf(stderr, "Checking menu items...."); |
| #endif |
| if ((f->menus & menu) == 0) |
| continue; |
| |
| if (!f->desc || strlen(f->desc) == 0) |
| continue; |
| |
| #ifdef DEBUG |
| fprintf(stderr, "found one! f->menus = %d, desc = \"%s\"\n", f->menus, f->desc); |
| #endif |
| s = first_sc_for(menu, f->scfunc); |
| if (s == NULL) { |
| #ifdef DEBUG |
| fprintf(stderr, "Whoops, guess not, no shortcut key found for func!\n"); |
| #endif |
| continue; |
| } |
| wmove(bottomwin, 1 + i % 2, (i / 2) * colwidth); |
| #ifdef DEBUG |
| fprintf(stderr, "Calling onekey with keystr \"%s\" and desc \"%s\"\n", s->keystr, f->desc); |
| #endif |
| onekey(s->keystr, _(f->desc), colwidth + (COLS % colwidth)); |
| i++; |
| } |
| |
| wnoutrefresh(bottomwin); |
| reset_cursor(); |
| wnoutrefresh(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, reverse_attr); |
| waddnstr(bottomwin, keystroke, actual_x(keystroke, len)); |
| wattroff(bottomwin, reverse_attr); |
| |
| if (len > keystroke_len) |
| len -= keystroke_len; |
| else |
| len = 0; |
| |
| if (len > 0) { |
| waddch(bottomwin, ' '); |
| waddnstr(bottomwin, desc, actual_x(desc, len)); |
| } |
| } |
| |
| /* Reset current_y, based on the position of current, and put the cursor |
| * in the edit window at (current_y, current_x). */ |
| void reset_cursor(void) |
| { |
| size_t xpt; |
| /* If we haven't opened any files yet, put the cursor in the top |
| * left corner of the edit window and get out. */ |
| if (openfile == NULL) { |
| wmove(edit, 0, 0); |
| return; |
| } |
| |
| xpt = xplustabs(); |
| |
| if (ISSET(SOFTWRAP)) { |
| filestruct *tmp; |
| openfile->current_y = 0; |
| |
| for (tmp = openfile->edittop; tmp && tmp != openfile->current; tmp = tmp->next) |
| openfile->current_y += 1 + strlenpt(tmp->data) / COLS; |
| |
| openfile->current_y += xplustabs() / COLS; |
| if (openfile->current_y < editwinrows) |
| wmove(edit, openfile->current_y, xpt % COLS); |
| } else { |
| openfile->current_y = openfile->current->lineno - |
| openfile->edittop->lineno; |
| |
| if (openfile->current_y < editwinrows) |
| wmove(edit, openfile->current_y, xpt - get_page_start(xpt)); |
| } |
| } |
| |
| /* edit_draw() takes care of the job of actually painting a line into |
| * the edit window. fileptr is the line to be painted, at row line 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_draw(filestruct *fileptr, const char *converted, int |
| line, size_t start) |
| { |
| #if !defined(NANO_TINY) || 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(openfile != NULL && 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, line, 0, converted); |
| |
| #ifdef ENABLE_COLOR |
| /* If color syntaxes are available and turned on, we need to display |
| * them. */ |
| if (openfile->colorstrings != NULL && !ISSET(NO_COLOR_SYNTAX)) { |
| const colortype *tmpcolor = openfile->colorstrings; |
| |
| /* Set up multi-line color data for this line if it's not yet calculated */ |
| if (fileptr->multidata == NULL && openfile->syntax |
| && openfile->syntax->nmultis > 0) { |
| int i; |
| fileptr->multidata = nmalloc(openfile->syntax->nmultis * sizeof(short)); |
| for (i = 0; i < openfile->syntax->nmultis; i++) |
| fileptr->multidata[i] = -1; /* Assue this applies until we know otherwise */ |
| } |
| 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(). A return value of zero means |
| * that 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 e.g. 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 zero. 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; |
| |
| /* Skip over a zero-length regex match. */ |
| if (startmatch.rm_so == startmatch.rm_eo) |
| startmatch.rm_eo++; |
| else if (startmatch.rm_so < endpos && |
| startmatch.rm_eo > startpos) { |
| x_start = (startmatch.rm_so <= startpos) ? 0 : |
| 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, line, x_start, converted + |
| index, paintlen); |
| } |
| k = startmatch.rm_eo; |
| } |
| } else if (fileptr->multidata != NULL && fileptr->multidata[tmpcolor->id] != CNONE) { |
| /* 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; |
| short md = fileptr->multidata[tmpcolor->id]; |
| |
| if (md == -1) |
| fileptr->multidata[tmpcolor->id] = CNONE; /* until we find out otherwise */ |
| else if (md == CNONE) |
| continue; |
| else if (md == CWHOLELINE) { |
| mvwaddnstr(edit, line, 0, converted, -1); |
| continue; |
| } else if (md == CBEGINBEFORE) { |
| regexec(tmpcolor->end, fileptr->data, 1, &endmatch, 0); |
| paintlen = actual_x(converted, strnlenpt(fileptr->data, |
| endmatch.rm_eo) - start); |
| mvwaddnstr(edit, line, 0, converted, paintlen); |
| continue; |
| } |
| |
| 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; |
| } |
| |
| /* Skip over a zero-length regex match. */ |
| if (startmatch.rm_so == startmatch.rm_eo) |
| startmatch.rm_eo++; |
| else { |
| /* 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 the start of |
| * fileptr is on a different line from the end, |
| * paintlen is -1, meaning that everything on the |
| * line gets painted. Otherwise, paintlen is the |
| * expanded location of the end of the match minus |
| * the expanded location of the beginning of the |
| * page. */ |
| if (end_line != fileptr) { |
| paintlen = -1; |
| fileptr->multidata[tmpcolor->id] = CWHOLELINE; |
| } else { |
| paintlen = actual_x(converted, |
| strnlenpt(fileptr->data, |
| endmatch.rm_eo) - start); |
| fileptr->multidata[tmpcolor->id] = CBEGINBEFORE; |
| } |
| mvwaddnstr(edit, line, 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; |
| |
| x_start = (startmatch.rm_so <= startpos) ? 0 : |
| 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, line, x_start, |
| converted + index, paintlen); |
| if (paintlen > 0) |
| fileptr->multidata[tmpcolor->id] = CSTARTENDHERE; |
| |
| } |
| } 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, line, x_start, |
| converted + index, -1); |
| /* We painted to the end of the line, so |
| * don't bother checking any more |
| * starts. */ |
| fileptr->multidata[tmpcolor->id] = CENDAFTER; |
| break; |
| } |
| } |
| start_col = startmatch.rm_so + 1; |
| } |
| } |
| } |
| |
| wattroff(edit, A_BOLD); |
| wattroff(edit, COLOR_PAIR(tmpcolor->pairnum)); |
| } |
| } |
| #endif /* ENABLE_COLOR */ |
| |
| #ifndef NANO_TINY |
| /* If the mark is on, we need to display it. */ |
| if (openfile->mark_set && (fileptr->lineno <= |
| openfile->mark_begin->lineno || fileptr->lineno <= |
| openfile->current->lineno) && (fileptr->lineno >= |
| openfile->mark_begin->lineno || fileptr->lineno >= |
| openfile->current->lineno)) { |
| /* fileptr is at least partially selected. */ |
| const filestruct *top; |
| /* Either current or mark_begin, whichever is first. */ |
| size_t top_x; |
| /* current_x or mark_begin_x, corresponding to top. */ |
| const filestruct *bot; |
| size_t bot_x; |
| int x_start; |
| /* Starting column for mvwaddnstr(). Zero-based. */ |
| int paintlen; |
| /* Number of characters 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 the end of the mark is off the page, paintlen is -1, |
| * meaning that everything on the line gets painted. |
| * Otherwise, paintlen is the expanded location of the end |
| * of the mark minus the expanded location of the beginning |
| * of the mark. */ |
| if (bot_x >= endpos) |
| paintlen = -1; |
| else |
| 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, reverse_attr); |
| mvwaddnstr(edit, line, x_start, converted + index, |
| paintlen); |
| wattroff(edit, reverse_attr); |
| } |
| } |
| #endif /* !NANO_TINY */ |
| } |
| |
| /* Just update one line in the edit buffer. This is basically a wrapper |
| * for edit_draw(). The line will be displayed starting with |
| * fileptr->data[index]. Likely arguments are current_x or zero. |
| * Returns: Number of additiona lines consumed (needed for SOFTWRAP) |
| */ |
| int update_line(filestruct *fileptr, size_t index) |
| { |
| int line = 0; |
| int extralinesused = 0; |
| /* 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; |
| filestruct *tmp; |
| |
| assert(fileptr != NULL); |
| |
| if (ISSET(SOFTWRAP)) { |
| for (tmp = openfile->edittop; tmp && tmp != fileptr; tmp = tmp->next) { |
| line += 1 + (strlenpt(tmp->data) / COLS); |
| } |
| } else |
| line = fileptr->lineno - openfile->edittop->lineno; |
| |
| if (line < 0 || line >= editwinrows) |
| return 1; |
| |
| /* First, blank out the line. */ |
| blank_line(edit, line, 0, COLS); |
| |
| /* Next, convert variables that index the line to their equivalent |
| * positions in the expanded line. */ |
| if (ISSET(SOFTWRAP)) |
| index = 0; |
| else |
| index = strnlenpt(fileptr->data, index); |
| 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, !ISSET(SOFTWRAP)); |
| |
| #ifdef DEBUG |
| if (ISSET(SOFTWRAP) && strlen(converted) >= COLS - 2) |
| fprintf(stderr, "update_line(): converted(1) line = %s\n", converted); |
| #endif |
| |
| |
| /* Paint the line. */ |
| edit_draw(fileptr, converted, line, page_start); |
| free(converted); |
| |
| if (!ISSET(SOFTWRAP)) { |
| if (page_start > 0) |
| mvwaddch(edit, line, 0, '$'); |
| if (strlenpt(fileptr->data) > page_start + COLS) |
| mvwaddch(edit, line, COLS - 1, '$'); |
| } else { |
| int full_length = strlenpt(fileptr->data); |
| for (index += COLS; index <= full_length && line < editwinrows; index += COLS) { |
| line++; |
| #ifdef DEBUG |
| fprintf(stderr, "update_line(): Softwrap code, moving to %d index %lu\n", line, (unsigned long) index); |
| #endif |
| blank_line(edit, line, 0, COLS); |
| |
| /* Expand the line, replacing tabs with spaces, and control |
| * characters with their displayed forms. */ |
| converted = display_string(fileptr->data, index, COLS, !ISSET(SOFTWRAP)); |
| #ifdef DEBUG |
| if (ISSET(SOFTWRAP) && strlen(converted) >= COLS - 2) |
| fprintf(stderr, "update_line(): converted(2) line = %s\n", converted); |
| #endif |
| |
| /* Paint the line. */ |
| edit_draw(fileptr, converted, line, index); |
| free(converted); |
| extralinesused++; |
| } |
| } |
| return extralinesused; |
| } |
| |
| /* Return TRUE if we need an update after moving horizontally, and FALSE |
| * otherwise. We need one if the mark is on or if pww_save and |
| * placewewant are on different pages. */ |
| bool need_horizontal_update(size_t pww_save) |
| { |
| return |
| #ifndef NANO_TINY |
| openfile->mark_set || |
| #endif |
| get_page_start(pww_save) != |
| get_page_start(openfile->placewewant); |
| } |
| |
| /* Return TRUE if we need an update after moving vertically, and FALSE |
| * otherwise. We need one if the mark is on or if pww_save and |
| * placewewant are on different pages. */ |
| bool need_vertical_update(size_t pww_save) |
| { |
| return |
| #ifndef NANO_TINY |
| openfile->mark_set || |
| #endif |
| get_page_start(pww_save) != |
| get_page_start(openfile->placewewant); |
| } |
| |
| /* When edittop changes, try and figure out how many lines |
| * we really have to work with (i.e. set maxrows) |
| */ |
| void compute_maxrows(void) |
| { |
| int n; |
| filestruct *foo = openfile->edittop; |
| |
| if (!ISSET(SOFTWRAP)) { |
| maxrows = editwinrows; |
| return; |
| } |
| |
| maxrows = 0; |
| for (n = 0; n < editwinrows && foo; n++) { |
| maxrows ++; |
| n += strlenpt(foo->data) / COLS; |
| foo = foo->next; |
| } |
| |
| if (n < editwinrows) |
| maxrows += editwinrows - n; |
| |
| #ifdef DEBUG |
| fprintf(stderr, "compute_maxrows(): maxrows = %ld\n", maxrows); |
| #endif |
| } |
| |
| /* 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_DIR or |
| * DOWN_DIR, and nlines is the number of lines to scroll. We change |
| * edittop, and assume that current and current_x are up to date. We |
| * also assume that scrollok(edit) is FALSE. */ |
| void edit_scroll(scroll_dir direction, ssize_t nlines) |
| { |
| filestruct *foo; |
| ssize_t i, extracuzsoft = 0; |
| bool do_redraw = FALSE; |
| |
| /* Don't bother scrolling less than one line. */ |
| if (nlines < 1) |
| return; |
| |
| if (need_vertical_update(0)) |
| do_redraw = TRUE; |
| |
| |
| /* If using soft wrapping, we want to scroll down enough to display the entire next |
| line, if possible... */ |
| if (ISSET(SOFTWRAP) && direction == DOWN_DIR) { |
| #ifdef DEBUG |
| fprintf(stderr, "Softwrap: Entering check for extracuzsoft\n"); |
| #endif |
| for (i = editwinrows, foo = openfile->edittop; foo && i > 0; i--, foo = foo->next) { |
| ssize_t len = strlenpt(foo->data) / COLS; |
| if (len > 0) |
| do_redraw = TRUE; |
| i -= len; |
| } |
| if (foo) { |
| extracuzsoft += strlenpt(foo->data) / COLS; |
| #ifdef DEBUG |
| fprintf(stderr, "Setting extracuzsoft to %lu due to strlen %lu of line %lu\n", (unsigned long) extracuzsoft, |
| (unsigned long) strlenpt(foo->data), (unsigned long) foo->lineno); |
| #endif |
| |
| |
| /* Now account for whether the edittop line itself is >COLS, if scrolling down */ |
| for (foo = openfile->edittop; foo && extracuzsoft > 0; nlines++) { |
| extracuzsoft -= 1 + strlenpt(foo->data) / COLS; |
| #ifdef DEBUG |
| fprintf(stderr, "Edittop adjustment, setting nlines to %lu\n", (unsigned long) nlines); |
| #endif |
| if (foo == openfile->filebot) |
| break; |
| foo = foo->next; |
| } |
| } |
| } else if (ISSET(SOFTWRAP) && direction == UP_DIR) { |
| for (foo = openfile->edittop, i = editwinrows; foo && i > 0; i--, foo = foo->prev) { |
| if (strlenpt(foo->data) / COLS > 0) { |
| do_redraw = TRUE; |
| break; |
| } |
| } |
| } |
| |
| /* Part 1: nlines is the number of lines we're going to scroll the |
| * text of the edit window. */ |
| |
| /* Move the top line of the edit window up or down (depending on the |
| * value of direction) nlines lines, or as many lines as we can if |
| * there are fewer than nlines lines available. */ |
| for (i = nlines; i > 0; i--) { |
| if (direction == UP_DIR) { |
| if (openfile->edittop == openfile->fileage) |
| break; |
| openfile->edittop = openfile->edittop->prev; |
| } else { |
| if (openfile->edittop == openfile->filebot) |
| break; |
| openfile->edittop = openfile->edittop->next; |
| } |
| } |
| |
| compute_maxrows(); |
| /* Limit nlines to the number of lines we could scroll. */ |
| nlines -= i; |
| |
| /* Don't bother scrolling zero lines or more than the number of |
| * lines in the edit window minus one; in both cases, get out, and |
| * call edit_refresh() beforehand if we need to. */ |
| if (nlines == 0 || do_redraw || nlines >= editwinrows) { |
| if (do_redraw || nlines >= editwinrows) |
| edit_refresh(); |
| return; |
| } |
| |
| /* Scroll the text of the edit window up or down nlines lines, |
| * depending on the value of direction. */ |
| scrollok(edit, TRUE); |
| wscrl(edit, (direction == UP_DIR) ? -nlines : nlines); |
| scrollok(edit, FALSE); |
| |
| /* Part 2: nlines is the number of lines in the scrolled region of |
| * the edit window that we need to draw. */ |
| |
| /* If the top or bottom line of the file is now visible in the edit |
| * window, we need to draw the entire edit window. */ |
| if ((direction == UP_DIR && openfile->edittop == |
| openfile->fileage) || (direction == DOWN_DIR && |
| openfile->edittop->lineno + editwinrows - 1 >= |
| openfile->filebot->lineno)) |
| nlines = editwinrows; |
| |
| /* If the scrolled region contains only one line, and the line |
| * before it is visible in the edit window, we need to draw it too. |
| * If the scrolled region contains more than one line, and the lines |
| * before and after the scrolled region are visible in the edit |
| * window, we need to draw them too. */ |
| nlines += (nlines == 1) ? 1 : 2; |
| |
| if (nlines > editwinrows) |
| nlines = editwinrows; |
| |
| /* If we scrolled up, we're on the line before the scrolled |
| * region. */ |
| foo = openfile->edittop; |
| |
| /* If we scrolled down, move down to the line before the scrolled |
| * region. */ |
| if (direction == DOWN_DIR) { |
| for (i = editwinrows - nlines; i > 0 && foo != NULL; i--) |
| foo = foo->next; |
| } |
| |
| /* Draw new lines on any blank lines before or inside the scrolled |
| * region. If we scrolled down and we're on the top line, or if we |
| * scrolled up and we're on the bottom line, the line won't be |
| * blank, so we don't need to draw it unless the mark is on or we're |
| * not on the first page. */ |
| for (i = nlines; i > 0 && foo != NULL; i--) { |
| if ((i == nlines && direction == DOWN_DIR) || (i == 1 && |
| direction == UP_DIR)) { |
| if (do_redraw) |
| update_line(foo, (foo == openfile->current) ? |
| openfile->current_x : 0); |
| } else |
| update_line(foo, (foo == openfile->current) ? |
| openfile->current_x : 0); |
| foo = foo->next; |
| } |
| } |
| |
| /* Update any lines between old_current and current that need to be |
| * updated. Use this if we've moved without changing any text. */ |
| void edit_redraw(filestruct *old_current, size_t pww_save) |
| { |
| bool do_redraw = need_vertical_update(0) || |
| need_vertical_update(pww_save); |
| filestruct *foo = NULL; |
| |
| /* If either old_current or current is offscreen, scroll the edit |
| * window until it's onscreen and get out. */ |
| if (old_current->lineno < openfile->edittop->lineno || |
| old_current->lineno >= openfile->edittop->lineno + |
| maxrows || openfile->current->lineno < |
| openfile->edittop->lineno || openfile->current->lineno >= |
| openfile->edittop->lineno + maxrows) { |
| |
| filestruct *old_edittop = openfile->edittop; |
| ssize_t nlines; |
| |
| #ifndef NANO_TINY |
| /* If the mark is on, update all the lines between old_current |
| * and either the old first line or old last line (depending on |
| * whether we've scrolled up or down) of the edit window. */ |
| if (openfile->mark_set) { |
| ssize_t old_lineno; |
| |
| if (old_edittop->lineno < openfile->edittop->lineno) |
| old_lineno = old_edittop->lineno; |
| else |
| old_lineno = (old_edittop->lineno + maxrows <= |
| openfile->filebot->lineno) ? |
| old_edittop->lineno + editwinrows : |
| openfile->filebot->lineno; |
| |
| foo = old_current; |
| |
| while (foo->lineno != old_lineno) { |
| update_line(foo, 0); |
| |
| foo = (foo->lineno > old_lineno) ? foo->prev : |
| foo->next; |
| } |
| } |
| #endif /* !NANO_TINY */ |
| |
| /* Put edittop in range of current, get the difference in lines |
| * between the original edittop and the current edittop, and |
| * then restore the original edittop. */ |
| edit_update(NONE); |
| |
| nlines = openfile->edittop->lineno - old_edittop->lineno; |
| |
| openfile->edittop = old_edittop; |
| |
| /* Update old_current if we're not on the same page as |
| * before. */ |
| if (do_redraw) |
| update_line(old_current, 0); |
| |
| /* Scroll the edit window up or down until edittop is in range |
| * of current. */ |
| if (nlines < 0) |
| edit_scroll(UP_DIR, -nlines); |
| else |
| edit_scroll(DOWN_DIR, nlines); |
| |
| #ifndef NANO_TINY |
| /* If the mark is on, update all the lines between the old first |
| * line or old last line of the edit window (depending on |
| * whether we've scrolled up or down) and current. */ |
| if (openfile->mark_set) { |
| while (foo->lineno != openfile->current->lineno) { |
| update_line(foo, 0); |
| |
| foo = (foo->lineno > openfile->current->lineno) ? |
| foo->prev : foo->next; |
| } |
| } |
| #endif /* !NANO_TINY */ |
| |
| return; |
| } |
| |
| /* Update old_current and current if 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 != openfile->current) { |
| if (do_redraw) |
| update_line(foo, 0); |
| |
| #ifndef NANO_TINY |
| if (!openfile->mark_set) |
| #endif |
| break; |
| |
| #ifndef NANO_TINY |
| foo = (foo->lineno > openfile->current->lineno) ? foo->prev : |
| foo->next; |
| #endif |
| } |
| |
| if (do_redraw) |
| update_line(openfile->current, openfile->current_x); |
| } |
| |
| /* Refresh the screen without changing the position of lines. Use this |
| * if we've moved and changed text. */ |
| void edit_refresh(void) |
| { |
| filestruct *foo; |
| int nlines; |
| |
| /* Figure out what maxrows should really be */ |
| compute_maxrows(); |
| |
| if (openfile->current->lineno < openfile->edittop->lineno || |
| openfile->current->lineno >= openfile->edittop->lineno + |
| maxrows) { |
| |
| #ifdef DEBUG |
| fprintf(stderr, "edit_refresh(): line = %d, edittop %d + maxrows %d\n", openfile->current->lineno, openfile->edittop->lineno, maxrows); |
| #endif |
| |
| /* Put the top line of the edit window in range of the current |
| * line. */ |
| edit_update(NONE); |
| } |
| |
| foo = openfile->edittop; |
| |
| #ifdef DEBUG |
| fprintf(stderr, "edit_refresh(): edittop->lineno = %ld\n", (long)openfile->edittop->lineno); |
| #endif |
| |
| for (nlines = 0; nlines < editwinrows && foo != NULL; nlines++) { |
| nlines += update_line(foo, (foo == openfile->current) ? |
| openfile->current_x : 0); |
| foo = foo->next; |
| } |
| |
| for (; nlines < editwinrows; nlines++) |
| blank_line(edit, nlines, 0, COLS); |
| |
| reset_cursor(); |
| wnoutrefresh(edit); |
| } |
| |
| /* Move edittop to put it in range of current, keeping current in the |
| * same place. location determines how we move it: if it's CENTER, we |
| * center current, and if it's NONE, we put current current_y lines |
| * below edittop. */ |
| void edit_update(update_type location) |
| { |
| filestruct *foo = openfile->current; |
| int goal; |
| |
| /* 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. */ |
| if (location == CENTER) |
| goal = maxrows / 2; |
| else { |
| goal = openfile->current_y; |
| |
| /* Limit goal to (editwinrows - 1) lines maximum. */ |
| if (goal > maxrows - 1) |
| goal = maxrows - 1; |
| } |
| |
| for (; goal > 0 && foo->prev != NULL; goal--) { |
| if (ISSET(SOFTWRAP)) |
| goal -= 1 + strlenpt(foo->data) / COLS; |
| foo = foo->prev; |
| } |
| openfile->edittop = foo; |
| compute_maxrows(); |
| } |
| |
| /* Unconditionally redraw the entire screen. */ |
| void total_redraw(void) |
| { |
| #ifdef USE_SLANG |
| /* Slang curses emulation brain damage, part 4: Slang doesn't define |
| * curscr. */ |
| SLsmg_touch_screen(); |
| SLsmg_refresh(); |
| #else |
| wrefresh(curscr); |
| #endif |
| } |
| |
| /* Unconditionally redraw the entire screen, and then refresh it using |
| * the current file. */ |
| void total_refresh(void) |
| { |
| total_redraw(); |
| titlebar(NULL); |
| edit_refresh(); |
| bottombars(currmenu); |
| } |
| |
| /* Display the main shortcut list on the last two rows of the bottom |
| * portion of the window. */ |
| void display_main_list(void) |
| { |
| bottombars(MMAIN); |
| } |
| |
| /* If constant is TRUE, we display the current cursor position only if |
| * disable_cursorpos is FALSE. Otherwise, we display it |
| * unconditionally and set disable_cursorpos to FALSE. If constant is |
| * TRUE and disable_cursorpos is TRUE, we also set disable_cursorpos to |
| * FALSE, so that we leave the current statusbar alone this time, and |
| * display the current cursor position next time. */ |
| void do_cursorpos(bool constant) |
| { |
| filestruct *f; |
| char c; |
| size_t i, cur_xpt = xplustabs() + 1; |
| size_t cur_lenpt = strlenpt(openfile->current->data) + 1; |
| int linepct, colpct, charpct; |
| |
| assert(openfile->fileage != NULL && openfile->current != NULL); |
| |
| f = openfile->current->next; |
| c = openfile->current->data[openfile->current_x]; |
| |
| openfile->current->next = NULL; |
| openfile->current->data[openfile->current_x] = '\0'; |
| |
| i = get_totsize(openfile->fileage, openfile->current); |
| |
| openfile->current->data[openfile->current_x] = c; |
| openfile->current->next = f; |
| |
| if (constant && disable_cursorpos) { |
| disable_cursorpos = FALSE; |
| return; |
| } |
| |
| /* Display the current cursor position on the statusbar, and set |
| * disable_cursorpos to FALSE. */ |
| linepct = 100 * openfile->current->lineno / |
| openfile->filebot->lineno; |
| colpct = 100 * cur_xpt / cur_lenpt; |
| charpct = (openfile->totsize == 0) ? 0 : 100 * i / |
| openfile->totsize; |
| |
| statusbar( |
| _("line %ld/%ld (%d%%), col %lu/%lu (%d%%), char %lu/%lu (%d%%)"), |
| (long)openfile->current->lineno, |
| (long)openfile->filebot->lineno, linepct, |
| (unsigned long)cur_xpt, (unsigned long)cur_lenpt, colpct, |
| (unsigned long)i, (unsigned long)openfile->totsize, charpct); |
| |
| disable_cursorpos = FALSE; |
| } |
| |
| /* Unconditionally display the current cursor position. */ |
| void do_cursorpos_void(void) |
| { |
| do_cursorpos(FALSE); |
| } |
| |
| void enable_nodelay(void) |
| { |
| nodelay_mode = TRUE; |
| nodelay(edit, TRUE); |
| } |
| |
| void disable_nodelay(void) |
| { |
| nodelay_mode = FALSE; |
| nodelay(edit, FALSE); |
| } |
| |
| |
| /* 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, const char *word) |
| { |
| size_t y = xplustabs(), 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(); |
| wnoutrefresh(edit); |
| |
| if (highlight) |
| wattron(edit, reverse_attr); |
| |
| /* This is so we can show zero-length matches. */ |
| if (word_len == 0) |
| waddch(edit, ' '); |
| else |
| waddnstr(edit, word, actual_x(word, y)); |
| |
| if (word_len > y) |
| waddch(edit, '$'); |
| |
| if (highlight) |
| wattroff(edit, reverse_attr); |
| } |
| |
| #ifdef NANO_EXTRA |
| #define CREDIT_LEN 55 |
| #define XLCREDIT_LEN 8 |
| |
| /* Easter egg: Display credits. Assume nodelay(edit) and scrollok(edit) |
| * are FALSE. */ |
| void do_credits(void) |
| { |
| bool old_more_space = ISSET(MORE_SPACE); |
| bool old_no_help = ISSET(NO_HELP); |
| 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", |
| "Mike Frysinger", |
| "Ken Tyler", |
| "Sven Guckes", |
| NULL, /* credits[15], 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, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007", |
| "Free Software Foundation, Inc.", |
| "", |
| "", |
| "", |
| "", |
| "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[15]: Make sure this name is displayed properly, since we |
| * can't dynamically assign it above, using Unicode 00F6 (Latin |
| * Small Letter O with Diaresis) if applicable. */ |
| credits[15] = |
| #ifdef ENABLE_UTF8 |
| using_utf8() ? "Florian K\xC3\xB6nig" : |
| #endif |
| "Florian K\xF6nig"; |
| |
| if (!old_more_space || !old_no_help) { |
| SET(MORE_SPACE); |
| SET(NO_HELP); |
| window_init(); |
| } |
| |
| curs_set(0); |
| nodelay(edit, TRUE); |
| |
| blank_titlebar(); |
| blank_topbar(); |
| blank_edit(); |
| blank_statusbar(); |
| blank_bottombars(); |
| |
| wrefresh(topwin); |
| wrefresh(edit); |
| wrefresh(bottomwin); |
| napms(700); |
| |
| 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); |
| } |
| |
| wrefresh(edit); |
| |
| if ((kbinput = wgetch(edit)) != ERR) |
| break; |
| napms(700); |
| |
| scrollok(edit, TRUE); |
| wscrl(edit, 1); |
| scrollok(edit, FALSE); |
| wrefresh(edit); |
| |
| if ((kbinput = wgetch(edit)) != ERR) |
| break; |
| napms(700); |
| |
| scrollok(edit, TRUE); |
| wscrl(edit, 1); |
| scrollok(edit, FALSE); |
| wrefresh(edit); |
| } |
| |
| if (kbinput != ERR) |
| ungetch(kbinput); |
| |
| if (!old_more_space || !old_no_help) { |
| UNSET(MORE_SPACE); |
| UNSET(NO_HELP); |
| window_init(); |
| } |
| |
| curs_set(1); |
| nodelay(edit, FALSE); |
| |
| total_refresh(); |
| } |
| #endif /* NANO_EXTRA */ |