e806ab8473
the last few characters of bottomwin when the screen width isn't a clean multiple of the column width, per Benno Schulenberg's patch (with a few tweaks by me) git-svn-id: svn://svn.savannah.gnu.org/nano/trunk/nano@3390 35c25a1d-7b9e-4130-9fde-d3aeb78583b8
3249 lines
95 KiB
C
3249 lines
95 KiB
C
/* $Id$ */
|
|
/**************************************************************************
|
|
* winio.c *
|
|
* *
|
|
* Copyright (C) 1999-2004 Chris Allegretta *
|
|
* Copyright (C) 2005-2006 David Lawrence Ramsey *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2, or (at your option) *
|
|
* any later version. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, but *
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
|
* General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public License *
|
|
* along with this program; if not, write to the Free Software *
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA *
|
|
* 02110-1301, USA. *
|
|
* *
|
|
**************************************************************************/
|
|
|
|
#include "proto.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <ctype.h>
|
|
|
|
static int *key_buffer = NULL;
|
|
/* The default keystroke buffer,
|
|
* containing all the keystrokes we have
|
|
* at a given point. */
|
|
static size_t key_buffer_len = 0;
|
|
/* The length of the default keystroke
|
|
* buffer. */
|
|
static int statusblank = 0;
|
|
/* The number of keystrokes left after
|
|
* we call statusbar(), before we
|
|
* actually blank the statusbar. */
|
|
static 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 (a.k.a. the Hurd
|
|
* console), xterm, rxvt, and Eterm. Among these, there are several
|
|
* conflicts and omissions, outlined as follows:
|
|
*
|
|
* - Tab on ANSI == PageUp on FreeBSD console; the former is omitted.
|
|
* (Ctrl-I is also Tab on ANSI, which we already support.)
|
|
* - PageDown on FreeBSD console == Center (5) on numeric keypad with
|
|
* NumLock off on Linux console; the latter is omitted. (The editing
|
|
* keypad key is more important to have working than the numeric
|
|
* keypad key, because the latter has no value when NumLock is off.)
|
|
* - F1 on FreeBSD console == the mouse key on xterm/rxvt/Eterm; the
|
|
* latter is omitted. (Mouse input will only work properly if the
|
|
* extended keypad value KEY_MOUSE is generated on mouse events
|
|
* instead of the escape sequence.)
|
|
* - F9 on FreeBSD console == PageDown on Mach console; the former is
|
|
* omitted. (The editing keypad is more important to have working
|
|
* than the function keys, because the functions of the former are not
|
|
* arbitrary and the functions of the latter are.)
|
|
* - F10 on FreeBSD console == PageUp on Mach console; the former is
|
|
* omitted. (Same as above.)
|
|
* - F13 on FreeBSD console == End on Mach console; the former is
|
|
* omitted. (Same as above.)
|
|
* - F15 on FreeBSD console == Shift-Up on rxvt/Eterm; the former is
|
|
* omitted. (The arrow keys, with or without modifiers, are more
|
|
* important to have working than the function keys, because the
|
|
* functions of the former are not arbitrary and the functions of the
|
|
* latter are.)
|
|
* - F16 on FreeBSD console == Shift-Down on rxvt/Eterm; the former is
|
|
* omitted. (Same as above.)
|
|
*
|
|
* Note that Center (5) on the numeric keypad with NumLock off can also
|
|
* be the Begin key. */
|
|
|
|
#ifndef NANO_TINY
|
|
/* Reset all the input routines that rely on character sequences. */
|
|
void reset_kbinput(void)
|
|
{
|
|
parse_kbinput(NULL, NULL, NULL, TRUE);
|
|
get_byte_kbinput(0, TRUE);
|
|
get_unicode_kbinput(0, TRUE);
|
|
}
|
|
#endif
|
|
|
|
/* Read in a sequence of keystrokes from win and save them in the
|
|
* default keystroke buffer. This should only be called when the
|
|
* default keystroke buffer is empty. */
|
|
void get_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;
|
|
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, save the value of
|
|
* the keystroke in key, and set key_code to TRUE if the keystroke
|
|
* is an extended keypad value or FALSE if it isn't. */
|
|
key_buffer_len++;
|
|
key_buffer = (int *)nmalloc(sizeof(int));
|
|
key_buffer[0] = input;
|
|
|
|
/* Read in the remaining characters using non-blocking input. */
|
|
nodelay(win, TRUE);
|
|
|
|
while (TRUE) {
|
|
#ifndef NANO_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, save
|
|
* the value of the keystroke in key, and set key_code to TRUE
|
|
* if the keystroke is an extended keypad value or FALSE if it
|
|
* isn't. */
|
|
key_buffer_len++;
|
|
key_buffer = (int *)nrealloc(key_buffer, key_buffer_len *
|
|
sizeof(int));
|
|
key_buffer[key_buffer_len - 1] = input;
|
|
|
|
#ifndef NANO_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 default keystroke buffer. */
|
|
size_t get_key_buffer_len(void)
|
|
{
|
|
return key_buffer_len;
|
|
}
|
|
|
|
/* Add the contents of the keystroke buffer input to the default
|
|
* keystroke buffer. */
|
|
void unget_input(int *input, size_t input_len)
|
|
{
|
|
#ifndef NANO_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 default keystroke buffer beyond
|
|
* maximum capacity, only add enough of input to put it at maximum
|
|
* capacity. */
|
|
if (key_buffer_len + input_len < key_buffer_len)
|
|
input_len = (size_t)-1 - key_buffer_len;
|
|
|
|
/* Add the length of input to the length of the default keystroke
|
|
* buffer, and reallocate the default keystroke buffer so that it
|
|
* has enough room for input. */
|
|
key_buffer_len += input_len;
|
|
key_buffer = (int *)nrealloc(key_buffer, key_buffer_len *
|
|
sizeof(int));
|
|
|
|
/* If the default keystroke buffer wasn't empty before, move its
|
|
* beginning forward far enough so that we can add input to its
|
|
* beginning. */
|
|
if (key_buffer_len > input_len)
|
|
memmove(key_buffer + input_len, key_buffer,
|
|
(key_buffer_len - input_len) * sizeof(int));
|
|
|
|
/* Copy input to the beginning of the default keystroke buffer. */
|
|
memcpy(key_buffer, input, input_len * sizeof(int));
|
|
}
|
|
|
|
/* Put back the character stored in kbinput, putting it in byte range
|
|
* beforehand. If meta_key is TRUE, put back the Escape character after
|
|
* putting back kbinput. If func_key is TRUE, put back the function key
|
|
* (a value outside byte range) without putting it in byte range. */
|
|
void unget_kbinput(int kbinput, bool meta_key, bool func_key)
|
|
{
|
|
if (!func_key)
|
|
kbinput = (char)kbinput;
|
|
|
|
unget_input(&kbinput, 1);
|
|
|
|
if (meta_key) {
|
|
kbinput = NANO_CONTROL_3;
|
|
unget_input(&kbinput, 1);
|
|
}
|
|
}
|
|
|
|
/* Try to read input_len characters from the default keystroke buffer.
|
|
* If the default keystroke buffer is empty and win isn't NULL, try to
|
|
* read in more characters from win and add them to the default
|
|
* keystroke buffer before doing anything else. If the default
|
|
* keystroke buffer is empty and win is NULL, return NULL. */
|
|
int *get_input(WINDOW *win, size_t input_len)
|
|
{
|
|
int *input;
|
|
|
|
#ifndef NANO_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;
|
|
}
|
|
|
|
/* If input_len is greater than the length of the default keystroke
|
|
* buffer, only read the number of characters in the default
|
|
* keystroke buffer. */
|
|
if (input_len > key_buffer_len)
|
|
input_len = key_buffer_len;
|
|
|
|
/* Subtract input_len from the length of the default keystroke
|
|
* buffer, and allocate the keystroke buffer input so that it
|
|
* has enough room for input_len keystrokes. */
|
|
key_buffer_len -= input_len;
|
|
input = (int *)nmalloc(input_len * sizeof(int));
|
|
|
|
/* Copy input_len characters from the beginning of the default
|
|
* keystroke buffer into input. */
|
|
memcpy(input, key_buffer, input_len * sizeof(int));
|
|
|
|
/* If the default keystroke buffer is empty, mark it as such. */
|
|
if (key_buffer_len == 0) {
|
|
free(key_buffer);
|
|
key_buffer = NULL;
|
|
/* If the default keystroke buffer isn't empty, move its
|
|
* beginning forward far enough so that the keystrokes in input are
|
|
* no longer at its beginning. */
|
|
} else {
|
|
memmove(key_buffer, key_buffer + input_len, key_buffer_len *
|
|
sizeof(int));
|
|
key_buffer = (int *)nrealloc(key_buffer, key_buffer_len *
|
|
sizeof(int));
|
|
}
|
|
|
|
return input;
|
|
}
|
|
|
|
/* Read in a single character. If it's ignored, swallow it and go on.
|
|
* Otherwise, try to translate it from ASCII, meta key sequences, escape
|
|
* sequences, and/or extended keypad values. Set meta_key to TRUE when
|
|
* we get a meta key sequence, and set func_key to TRUE when we get an
|
|
* extended keypad value. Supported extended keypad values consist of
|
|
* [arrow key], Ctrl-[arrow key], Shift-[arrow key], Enter, Backspace,
|
|
* the editing keypad (Insert, Delete, Home, End, PageUp, and PageDown),
|
|
* the function keypad (F1-F16), and the numeric keypad with NumLock
|
|
* off. Assume nodelay(win) is FALSE. */
|
|
int get_kbinput(WINDOW *win, bool *meta_key, bool *func_key)
|
|
{
|
|
int kbinput;
|
|
|
|
/* Read in a character and interpret it. Continue doing this until
|
|
* we get a recognized value or sequence. */
|
|
while ((kbinput = parse_kbinput(win, meta_key, func_key
|
|
#ifndef NANO_TINY
|
|
, FALSE
|
|
#endif
|
|
)) == ERR);
|
|
|
|
return kbinput;
|
|
}
|
|
|
|
/* Translate ASCII characters, extended keypad values, and escape
|
|
* sequences into their corresponding key values. Set meta_key to TRUE
|
|
* when we get a meta key sequence, and set func_key to TRUE when we get
|
|
* a function key. Assume nodelay(win) is FALSE. */
|
|
int parse_kbinput(WINDOW *win, bool *meta_key, bool *func_key
|
|
#ifndef NANO_TINY
|
|
, bool reset
|
|
#endif
|
|
)
|
|
|
|
{
|
|
static int escapes = 0, byte_digits = 0;
|
|
int *kbinput, retval = ERR;
|
|
|
|
#ifndef NANO_TINY
|
|
if (reset) {
|
|
escapes = 0;
|
|
byte_digits = 0;
|
|
return ERR;
|
|
}
|
|
#endif
|
|
|
|
*meta_key = FALSE;
|
|
*func_key = FALSE;
|
|
|
|
/* Read in a character. */
|
|
while ((kbinput = get_input(win, 1)) == NULL);
|
|
|
|
switch (*kbinput) {
|
|
case ERR:
|
|
break;
|
|
case NANO_CONTROL_3:
|
|
/* Increment the escape counter. */
|
|
escapes++;
|
|
switch (escapes) {
|
|
case 1:
|
|
/* One escape: wait for more input. */
|
|
case 2:
|
|
/* Two escapes: wait for more input. */
|
|
break;
|
|
default:
|
|
/* More than two escapes: reset the escape counter
|
|
* and wait for more input. */
|
|
escapes = 0;
|
|
}
|
|
break;
|
|
#if !defined(NANO_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:
|
|
break;
|
|
#endif
|
|
#ifdef PDCURSES
|
|
case KEY_SHIFT_L:
|
|
case KEY_SHIFT_R:
|
|
case KEY_CONTROL_L:
|
|
case KEY_CONTROL_R:
|
|
case KEY_ALT_L:
|
|
case KEY_ALT_R:
|
|
break;
|
|
#endif
|
|
default:
|
|
switch (escapes) {
|
|
case 0:
|
|
switch (*kbinput) {
|
|
case NANO_CONTROL_8:
|
|
retval = ISSET(REBIND_DELETE) ?
|
|
NANO_DELETE_KEY : NANO_BACKSPACE_KEY;
|
|
break;
|
|
case KEY_DOWN:
|
|
retval = NANO_NEXTLINE_KEY;
|
|
break;
|
|
case KEY_UP:
|
|
retval = NANO_PREVLINE_KEY;
|
|
break;
|
|
case KEY_LEFT:
|
|
retval = NANO_BACK_KEY;
|
|
break;
|
|
case KEY_RIGHT:
|
|
retval = NANO_FORWARD_KEY;
|
|
break;
|
|
#ifdef KEY_HOME
|
|
/* HP-UX 10 and 11 don't support KEY_HOME. */
|
|
case KEY_HOME:
|
|
retval = NANO_HOME_KEY;
|
|
break;
|
|
#endif
|
|
case KEY_BACKSPACE:
|
|
retval = NANO_BACKSPACE_KEY;
|
|
break;
|
|
case KEY_DC:
|
|
retval = ISSET(REBIND_DELETE) ?
|
|
NANO_BACKSPACE_KEY : NANO_DELETE_KEY;
|
|
break;
|
|
case KEY_IC:
|
|
retval = NANO_INSERTFILE_KEY;
|
|
break;
|
|
case KEY_NPAGE:
|
|
retval = NANO_NEXTPAGE_KEY;
|
|
break;
|
|
case KEY_PPAGE:
|
|
retval = NANO_PREVPAGE_KEY;
|
|
break;
|
|
case KEY_ENTER:
|
|
retval = NANO_ENTER_KEY;
|
|
break;
|
|
case KEY_A1: /* Home (7) on numeric keypad
|
|
* with NumLock off. */
|
|
retval = NANO_HOME_KEY;
|
|
break;
|
|
case KEY_A3: /* PageUp (9) on numeric keypad
|
|
* with NumLock off. */
|
|
retval = NANO_PREVPAGE_KEY;
|
|
break;
|
|
case KEY_B2: /* Center (5) on numeric keypad
|
|
* with NumLock off. */
|
|
break;
|
|
case KEY_C1: /* End (1) on numeric keypad
|
|
* with NumLock off. */
|
|
retval = NANO_END_KEY;
|
|
break;
|
|
case KEY_C3: /* PageDown (4) on numeric
|
|
* keypad with NumLock off. */
|
|
retval = NANO_NEXTPAGE_KEY;
|
|
break;
|
|
#ifdef KEY_BEG
|
|
/* Slang doesn't support KEY_BEG. */
|
|
case KEY_BEG: /* Center (5) on numeric keypad
|
|
* with NumLock off. */
|
|
break;
|
|
#endif
|
|
#ifdef KEY_END
|
|
/* HP-UX 10 and 11 don't support KEY_END. */
|
|
case KEY_END:
|
|
retval = NANO_END_KEY;
|
|
break;
|
|
#endif
|
|
#ifdef KEY_SBEG
|
|
/* Slang doesn't support KEY_SBEG. */
|
|
case KEY_SBEG: /* Center (5) on numeric keypad
|
|
* with NumLock off. */
|
|
break;
|
|
#endif
|
|
#ifdef KEY_SDC
|
|
/* Slang doesn't support KEY_SDC. */
|
|
case KEY_SDC:
|
|
retval = ISSET(REBIND_DELETE) ?
|
|
NANO_BACKSPACE_KEY : NANO_DELETE_KEY;
|
|
break;
|
|
#endif
|
|
#ifdef KEY_SEND
|
|
/* HP-UX 10 and 11 don't support KEY_SEND. */
|
|
case KEY_SEND:
|
|
retval = NANO_END_KEY;
|
|
break;
|
|
#endif
|
|
#ifdef KEY_SHOME
|
|
/* HP-UX 10 and 11 and Slang don't support
|
|
* KEY_SHOME. */
|
|
case KEY_SHOME:
|
|
retval = NANO_HOME_KEY;
|
|
break;
|
|
#endif
|
|
#ifdef KEY_SIC
|
|
/* Slang doesn't support KEY_SIC. */
|
|
case KEY_SIC:
|
|
retval = NANO_INSERTFILE_KEY;
|
|
break;
|
|
#endif
|
|
#ifdef KEY_SLEFT
|
|
/* Slang doesn't support KEY_SLEFT. */
|
|
case KEY_SLEFT:
|
|
retval = NANO_BACK_KEY;
|
|
break;
|
|
#endif
|
|
#ifdef KEY_SRIGHT
|
|
/* Slang doesn't support KEY_SRIGHT. */
|
|
case KEY_SRIGHT:
|
|
retval = NANO_FORWARD_KEY;
|
|
break;
|
|
#endif
|
|
#ifdef KEY_SSUSPEND
|
|
/* Slang doesn't support KEY_SSUSPEND. */
|
|
case KEY_SSUSPEND:
|
|
retval = NANO_SUSPEND_KEY;
|
|
break;
|
|
#endif
|
|
#ifdef KEY_SUSPEND
|
|
/* Slang doesn't support KEY_SUSPEND. */
|
|
case KEY_SUSPEND:
|
|
retval = NANO_SUSPEND_KEY;
|
|
break;
|
|
#endif
|
|
default:
|
|
retval = *kbinput;
|
|
break;
|
|
}
|
|
break;
|
|
case 1:
|
|
/* One escape followed by a non-escape: escape
|
|
* sequence mode. Reset the escape counter. If
|
|
* there aren't any other keys waiting, we have a
|
|
* meta key sequence, so set meta_key to TRUE and
|
|
* save the lowercase version of the non-escape
|
|
* character as the result. If there are other keys
|
|
* waiting, we have a true escape sequence, so
|
|
* interpret it. */
|
|
escapes = 0;
|
|
if (get_key_buffer_len() == 0) {
|
|
*meta_key = TRUE;
|
|
retval = tolower(*kbinput);
|
|
} else {
|
|
int *seq;
|
|
size_t seq_len;
|
|
bool ignore_seq;
|
|
|
|
/* Put back the non-escape character, get the
|
|
* complete escape sequence, translate the
|
|
* sequence into its corresponding key value,
|
|
* and save that as the result. */
|
|
unget_input(kbinput, 1);
|
|
seq_len = get_key_buffer_len();
|
|
seq = get_input(NULL, seq_len);
|
|
retval = get_escape_seq_kbinput(seq, seq_len,
|
|
&ignore_seq);
|
|
|
|
/* If the escape sequence is unrecognized and
|
|
* not ignored, put back all of its characters
|
|
* except for the initial escape. */
|
|
if (retval == ERR && !ignore_seq)
|
|
unget_input(seq, seq_len);
|
|
|
|
free(seq);
|
|
}
|
|
break;
|
|
case 2:
|
|
/* Two escapes followed by one or more decimal
|
|
* digits: byte sequence mode. If the 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. */
|
|
if (('0' <= *kbinput && *kbinput <= '6' &&
|
|
byte_digits == 0) || ('0' <= *kbinput &&
|
|
*kbinput <= '9' && byte_digits > 0)) {
|
|
int byte;
|
|
|
|
byte_digits++;
|
|
byte = get_byte_kbinput(*kbinput
|
|
#ifndef NANO_TINY
|
|
, FALSE
|
|
#endif
|
|
);
|
|
|
|
if (byte != ERR) {
|
|
char *byte_mb;
|
|
int byte_mb_len, *seq, i;
|
|
|
|
/* If we've read in a complete byte
|
|
* sequence, reset the byte sequence counter
|
|
* and the escape counter, and put back the
|
|
* corresponding byte value. */
|
|
byte_digits = 0;
|
|
escapes = 0;
|
|
|
|
/* Put back the multibyte equivalent of the
|
|
* byte value. */
|
|
byte_mb = make_mbchar((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,
|
|
* and we're not in the middle of a byte
|
|
* sequence: control character sequence
|
|
* mode. Interpret the control sequence and
|
|
* save the corresponding control character
|
|
* as the result. */
|
|
retval = get_control_kbinput(*kbinput);
|
|
else {
|
|
/* If we're in the middle of a byte
|
|
* sequence, reset the byte sequence counter
|
|
* and save the character we got as the
|
|
* result. */
|
|
byte_digits = 0;
|
|
retval = *kbinput;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If we have a result and it's an extended keypad value (i.e, a
|
|
* value outside of byte range), set func_key to TRUE. */
|
|
if (retval != ERR)
|
|
*func_key = !is_byte(retval);
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "parse_kbinput(): kbinput = %d, meta_key = %d, func_key = %d, escapes = %d, byte_digits = %d, retval = %d\n", *kbinput, (int)*meta_key, (int)*func_key, escapes, byte_digits, retval);
|
|
#endif
|
|
|
|
/* Return the result. */
|
|
return retval;
|
|
}
|
|
|
|
/* Translate escape sequences, most of which correspond to extended
|
|
* keypad values, into their corresponding key values. These sequences
|
|
* are generated when the keypad doesn't support the needed keys. If
|
|
* the escape sequence is recognized but we want to ignore it, return
|
|
* ERR and set ignore_seq to TRUE; if it's unrecognized, return ERR and
|
|
* set ignore_seq to FALSE. Assume that Escape has already been read
|
|
* in. */
|
|
int get_escape_seq_kbinput(const int *seq, size_t seq_len, bool
|
|
*ignore_seq)
|
|
{
|
|
int retval = ERR;
|
|
|
|
*ignore_seq = FALSE;
|
|
|
|
if (seq_len > 1) {
|
|
switch (seq[0]) {
|
|
case 'O':
|
|
switch (seq[1]) {
|
|
case '2':
|
|
if (seq_len >= 3) {
|
|
switch (seq[2]) {
|
|
case 'P': /* Esc O 2 P == F13 on
|
|
* xterm. */
|
|
retval = KEY_F(13);
|
|
break;
|
|
case 'Q': /* Esc O 2 Q == F14 on
|
|
* xterm. */
|
|
retval = KEY_F(14);
|
|
break;
|
|
case 'R': /* Esc O 2 R == F15 on
|
|
* xterm. */
|
|
retval = KEY_F(15);
|
|
break;
|
|
case 'S': /* Esc O 2 S == F16 on
|
|
* xterm. */
|
|
retval = KEY_F(16);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case 'A': /* Esc O A == Up on VT100/VT320/xterm. */
|
|
case 'B': /* Esc O B == Down on
|
|
* VT100/VT320/xterm. */
|
|
case 'C': /* Esc O C == Right on
|
|
* VT100/VT320/xterm. */
|
|
case 'D': /* Esc O D == Left on
|
|
* VT100/VT320/xterm. */
|
|
retval = get_escape_seq_abcd(seq[1]);
|
|
break;
|
|
case 'E': /* Esc O E == Center (5) on numeric keypad
|
|
* with NumLock off on xterm. */
|
|
*ignore_seq = TRUE;
|
|
break;
|
|
case 'F': /* Esc O F == End on xterm. */
|
|
retval = NANO_END_KEY;
|
|
break;
|
|
case 'H': /* Esc O H == Home on xterm. */
|
|
retval = NANO_HOME_KEY;
|
|
break;
|
|
case 'M': /* Esc O M == Enter on numeric keypad with
|
|
* NumLock off on VT100/VT220/VT320/xterm/
|
|
* Eterm. */
|
|
retval = NANO_ENTER_KEY;
|
|
break;
|
|
case 'P': /* Esc O P == F1 on VT100/VT220/VT320/Mach
|
|
* console. */
|
|
retval = KEY_F(1);
|
|
break;
|
|
case 'Q': /* Esc O Q == F2 on VT100/VT220/VT320/Mach
|
|
* console. */
|
|
retval = KEY_F(2);
|
|
break;
|
|
case 'R': /* Esc O R == F3 on VT100/VT220/VT320/Mach
|
|
* console. */
|
|
retval = KEY_F(3);
|
|
break;
|
|
case 'S': /* Esc O S == F4 on VT100/VT220/VT320/Mach
|
|
* console. */
|
|
retval = KEY_F(4);
|
|
break;
|
|
case 'T': /* Esc O T == F5 on Mach console. */
|
|
retval = KEY_F(5);
|
|
break;
|
|
case 'U': /* Esc O U == F6 on Mach console. */
|
|
retval = KEY_F(6);
|
|
break;
|
|
case 'V': /* Esc O V == F7 on Mach console. */
|
|
retval = KEY_F(7);
|
|
break;
|
|
case 'W': /* Esc O W == F8 on Mach console. */
|
|
retval = KEY_F(8);
|
|
break;
|
|
case 'X': /* Esc O X == F9 on Mach console. */
|
|
retval = KEY_F(9);
|
|
break;
|
|
case 'Y': /* Esc O Y == F10 on Mach console. */
|
|
retval = KEY_F(10);
|
|
break;
|
|
case 'a': /* Esc O a == Ctrl-Up on rxvt. */
|
|
case 'b': /* Esc O b == Ctrl-Down on rxvt. */
|
|
case 'c': /* Esc O c == Ctrl-Right on rxvt. */
|
|
case 'd': /* Esc O d == Ctrl-Left on rxvt. */
|
|
retval = get_escape_seq_abcd(seq[1]);
|
|
break;
|
|
case 'j': /* Esc O j == '*' on numeric keypad with
|
|
* NumLock off on VT100/VT220/VT320/xterm/
|
|
* rxvt. */
|
|
retval = '*';
|
|
break;
|
|
case 'k': /* Esc O k == '+' on numeric keypad with
|
|
* NumLock off on VT100/VT220/VT320/xterm/
|
|
* rxvt. */
|
|
retval = '+';
|
|
break;
|
|
case 'l': /* Esc O l == ',' on numeric keypad with
|
|
* NumLock off on VT100/VT220/VT320/xterm/
|
|
* rxvt. */
|
|
retval = ',';
|
|
break;
|
|
case 'm': /* Esc O m == '-' on numeric keypad with
|
|
* NumLock off on VT100/VT220/VT320/xterm/
|
|
* rxvt. */
|
|
retval = '-';
|
|
break;
|
|
case 'n': /* Esc O n == Delete (.) on numeric keypad
|
|
* with NumLock off on VT100/VT220/VT320/
|
|
* xterm/rxvt. */
|
|
retval = NANO_DELETE_KEY;
|
|
break;
|
|
case 'o': /* Esc O o == '/' on numeric keypad with
|
|
* NumLock off on VT100/VT220/VT320/xterm/
|
|
* rxvt. */
|
|
retval = '/';
|
|
break;
|
|
case 'p': /* Esc O p == Insert (0) on numeric keypad
|
|
* with NumLock off on VT100/VT220/VT320/
|
|
* rxvt. */
|
|
retval = NANO_INSERTFILE_KEY;
|
|
break;
|
|
case 'q': /* Esc O q == End (1) on numeric keypad
|
|
* with NumLock off on VT100/VT220/VT320/
|
|
* rxvt. */
|
|
retval = NANO_END_KEY;
|
|
break;
|
|
case 'r': /* Esc O r == Down (2) on numeric keypad
|
|
* with NumLock off on VT100/VT220/VT320/
|
|
* rxvt. */
|
|
retval = NANO_NEXTLINE_KEY;
|
|
break;
|
|
case 's': /* Esc O s == PageDown (3) on numeric
|
|
* keypad with NumLock off on VT100/VT220/
|
|
* VT320/rxvt. */
|
|
retval = NANO_NEXTPAGE_KEY;
|
|
break;
|
|
case 't': /* Esc O t == Left (4) on numeric keypad
|
|
* with NumLock off on VT100/VT220/VT320/
|
|
* rxvt. */
|
|
retval = NANO_BACK_KEY;
|
|
break;
|
|
case 'u': /* Esc O u == Center (5) on numeric keypad
|
|
* with NumLock off on VT100/VT220/VT320/
|
|
* rxvt/Eterm. */
|
|
*ignore_seq = TRUE;
|
|
break;
|
|
case 'v': /* Esc O v == Right (6) on numeric keypad
|
|
* with NumLock off on VT100/VT220/VT320/
|
|
* rxvt. */
|
|
retval = NANO_FORWARD_KEY;
|
|
break;
|
|
case 'w': /* Esc O w == Home (7) on numeric keypad
|
|
* with NumLock off on VT100/VT220/VT320/
|
|
* rxvt. */
|
|
retval = NANO_HOME_KEY;
|
|
break;
|
|
case 'x': /* Esc O x == Up (8) on numeric keypad
|
|
* with NumLock off on VT100/VT220/VT320/
|
|
* rxvt. */
|
|
retval = NANO_PREVLINE_KEY;
|
|
break;
|
|
case 'y': /* Esc O y == PageUp (9) on numeric keypad
|
|
* with NumLock off on VT100/VT220/VT320/
|
|
* rxvt. */
|
|
retval = NANO_PREVPAGE_KEY;
|
|
break;
|
|
}
|
|
break;
|
|
case 'o':
|
|
switch (seq[1]) {
|
|
case 'a': /* Esc o a == Ctrl-Up on Eterm. */
|
|
case 'b': /* Esc o b == Ctrl-Down on Eterm. */
|
|
case 'c': /* Esc o c == Ctrl-Right on Eterm. */
|
|
case 'd': /* Esc o d == Ctrl-Left on Eterm. */
|
|
retval = get_escape_seq_abcd(seq[1]);
|
|
break;
|
|
}
|
|
break;
|
|
case '[':
|
|
switch (seq[1]) {
|
|
case '1':
|
|
if (seq_len >= 3) {
|
|
switch (seq[2]) {
|
|
case '1': /* Esc [ 1 1 ~ == F1 on rxvt/
|
|
* Eterm. */
|
|
retval = KEY_F(1);
|
|
break;
|
|
case '2': /* Esc [ 1 2 ~ == F2 on rxvt/
|
|
* Eterm. */
|
|
retval = KEY_F(2);
|
|
break;
|
|
case '3': /* Esc [ 1 3 ~ == F3 on rxvt/
|
|
* Eterm. */
|
|
retval = KEY_F(3);
|
|
break;
|
|
case '4': /* Esc [ 1 4 ~ == F4 on rxvt/
|
|
* Eterm. */
|
|
retval = KEY_F(4);
|
|
break;
|
|
case '5': /* Esc [ 1 5 ~ == F5 on xterm/
|
|
* rxvt/Eterm. */
|
|
retval = KEY_F(5);
|
|
break;
|
|
case '7': /* Esc [ 1 7 ~ == F6 on
|
|
* VT220/VT320/Linux console/
|
|
* xterm/rxvt/Eterm. */
|
|
retval = KEY_F(6);
|
|
break;
|
|
case '8': /* Esc [ 1 8 ~ == F7 on
|
|
* VT220/VT320/Linux console/
|
|
* xterm/rxvt/Eterm. */
|
|
retval = KEY_F(7);
|
|
break;
|
|
case '9': /* Esc [ 1 9 ~ == F8 on
|
|
* VT220/VT320/Linux console/
|
|
* xterm/rxvt/Eterm. */
|
|
retval = KEY_F(8);
|
|
break;
|
|
case ';':
|
|
if (seq_len >= 4) {
|
|
switch (seq[3]) {
|
|
case '2':
|
|
if (seq_len >= 5) {
|
|
switch (seq[4]) {
|
|
case 'A': /* Esc [ 1 ; 2 A == Shift-Up on
|
|
* xterm. */
|
|
case 'B': /* Esc [ 1 ; 2 B == Shift-Down on
|
|
* xterm. */
|
|
case 'C': /* Esc [ 1 ; 2 C == Shift-Right on
|
|
* xterm. */
|
|
case 'D': /* Esc [ 1 ; 2 D == Shift-Left on
|
|
* xterm. */
|
|
retval = get_escape_seq_abcd(seq[4]);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case '5':
|
|
if (seq_len >= 5) {
|
|
switch (seq[4]) {
|
|
case 'A': /* Esc [ 1 ; 5 A == Ctrl-Up on
|
|
* xterm. */
|
|
case 'B': /* Esc [ 1 ; 5 B == Ctrl-Down on
|
|
* xterm. */
|
|
case 'C': /* Esc [ 1 ; 5 C == Ctrl-Right on
|
|
* xterm. */
|
|
case 'D': /* Esc [ 1 ; 5 D == Ctrl-Left on
|
|
* xterm. */
|
|
retval = get_escape_seq_abcd(seq[4]);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
default: /* Esc [ 1 ~ == Home on
|
|
* VT320/Linux console. */
|
|
retval = NANO_HOME_KEY;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case '2':
|
|
if (seq_len >= 3) {
|
|
switch (seq[2]) {
|
|
case '0': /* Esc [ 2 0 ~ == F9 on
|
|
* VT220/VT320/Linux console/
|
|
* xterm/rxvt/Eterm. */
|
|
retval = KEY_F(9);
|
|
break;
|
|
case '1': /* Esc [ 2 1 ~ == F10 on
|
|
* VT220/VT320/Linux console/
|
|
* xterm/rxvt/Eterm. */
|
|
retval = KEY_F(10);
|
|
break;
|
|
case '3': /* Esc [ 2 3 ~ == F11 on
|
|
* VT220/VT320/Linux console/
|
|
* xterm/rxvt/Eterm. */
|
|
retval = KEY_F(11);
|
|
break;
|
|
case '4': /* Esc [ 2 4 ~ == F12 on
|
|
* VT220/VT320/Linux console/
|
|
* xterm/rxvt/Eterm. */
|
|
retval = KEY_F(12);
|
|
break;
|
|
case '5': /* Esc [ 2 5 ~ == F13 on
|
|
* VT220/VT320/Linux console/
|
|
* rxvt/Eterm. */
|
|
retval = KEY_F(13);
|
|
break;
|
|
case '6': /* Esc [ 2 6 ~ == F14 on
|
|
* VT220/VT320/Linux console/
|
|
* rxvt/Eterm. */
|
|
retval = KEY_F(14);
|
|
break;
|
|
case '8': /* Esc [ 2 8 ~ == F15 on
|
|
* VT220/VT320/Linux console/
|
|
* rxvt/Eterm. */
|
|
retval = KEY_F(15);
|
|
break;
|
|
case '9': /* Esc [ 2 9 ~ == F16 on
|
|
* VT220/VT320/Linux console/
|
|
* rxvt/Eterm. */
|
|
retval = KEY_F(16);
|
|
break;
|
|
default: /* Esc [ 2 ~ == Insert on
|
|
* VT220/VT320/Linux console/
|
|
* xterm. */
|
|
retval = NANO_INSERTFILE_KEY;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case '3': /* Esc [ 3 ~ == Delete on VT220/VT320/
|
|
* Linux console/xterm. */
|
|
retval = NANO_DELETE_KEY;
|
|
break;
|
|
case '4': /* Esc [ 4 ~ == End on VT220/VT320/Linux
|
|
* console/xterm. */
|
|
retval = NANO_END_KEY;
|
|
break;
|
|
case '5': /* Esc [ 5 ~ == PageUp on VT220/VT320/
|
|
* Linux console/xterm; Esc [ 5 ^ ==
|
|
* PageUp on Eterm. */
|
|
retval = NANO_PREVPAGE_KEY;
|
|
break;
|
|
case '6': /* Esc [ 6 ~ == PageDown on VT220/VT320/
|
|
* Linux console/xterm; Esc [ 6 ^ ==
|
|
* PageDown on Eterm. */
|
|
retval = NANO_NEXTPAGE_KEY;
|
|
break;
|
|
case '7': /* Esc [ 7 ~ == Home on rxvt. */
|
|
retval = NANO_HOME_KEY;
|
|
break;
|
|
case '8': /* Esc [ 8 ~ == End on rxvt. */
|
|
retval = NANO_END_KEY;
|
|
break;
|
|
case '9': /* Esc [ 9 == Delete on Mach console. */
|
|
retval = NANO_DELETE_KEY;
|
|
break;
|
|
case '@': /* Esc [ @ == Insert on Mach console. */
|
|
retval = NANO_INSERTFILE_KEY;
|
|
break;
|
|
case 'A': /* Esc [ A == Up on ANSI/VT220/Linux
|
|
* console/FreeBSD console/Mach console/
|
|
* rxvt/Eterm. */
|
|
case 'B': /* Esc [ B == Down on ANSI/VT220/Linux
|
|
* console/FreeBSD console/Mach console/
|
|
* rxvt/Eterm. */
|
|
case 'C': /* Esc [ C == Right on ANSI/VT220/Linux
|
|
* console/FreeBSD console/Mach console/
|
|
* rxvt/Eterm. */
|
|
case 'D': /* Esc [ D == Left on ANSI/VT220/Linux
|
|
* console/FreeBSD console/Mach console/
|
|
* rxvt/Eterm. */
|
|
retval = get_escape_seq_abcd(seq[1]);
|
|
break;
|
|
case 'E': /* Esc [ E == Center (5) on numeric keypad
|
|
* with NumLock off on FreeBSD console. */
|
|
*ignore_seq = TRUE;
|
|
break;
|
|
case 'F': /* Esc [ F == End on FreeBSD
|
|
* console/Eterm. */
|
|
retval = NANO_END_KEY;
|
|
break;
|
|
case 'G': /* Esc [ G == PageDown on FreeBSD
|
|
* console. */
|
|
retval = NANO_NEXTPAGE_KEY;
|
|
break;
|
|
case 'H': /* Esc [ H == Home on ANSI/VT220/FreeBSD
|
|
* console/Mach console/Eterm. */
|
|
retval = NANO_HOME_KEY;
|
|
break;
|
|
case 'I': /* Esc [ I == PageUp on FreeBSD
|
|
* console. */
|
|
retval = NANO_PREVPAGE_KEY;
|
|
break;
|
|
case 'L': /* Esc [ L == Insert on ANSI/FreeBSD
|
|
* console. */
|
|
retval = NANO_INSERTFILE_KEY;
|
|
break;
|
|
case 'M': /* Esc [ M == F1 on FreeBSD console. */
|
|
retval = KEY_F(1);
|
|
break;
|
|
case 'N': /* Esc [ N == F2 on FreeBSD console. */
|
|
retval = KEY_F(2);
|
|
break;
|
|
case 'O':
|
|
if (seq_len >= 3) {
|
|
switch (seq[2]) {
|
|
case 'P': /* Esc [ O P == F1 on
|
|
* xterm. */
|
|
retval = KEY_F(1);
|
|
break;
|
|
case 'Q': /* Esc [ O Q == F2 on
|
|
* xterm. */
|
|
retval = KEY_F(2);
|
|
break;
|
|
case 'R': /* Esc [ O R == F3 on
|
|
* xterm. */
|
|
retval = KEY_F(3);
|
|
break;
|
|
case 'S': /* Esc [ O S == F4 on
|
|
* xterm. */
|
|
retval = KEY_F(4);
|
|
break;
|
|
}
|
|
} else
|
|
/* Esc [ O == F3 on FreeBSD console. */
|
|
retval = KEY_F(3);
|
|
break;
|
|
case 'P': /* Esc [ P == F4 on FreeBSD console. */
|
|
retval = KEY_F(4);
|
|
break;
|
|
case 'Q': /* Esc [ Q == F5 on FreeBSD console. */
|
|
retval = KEY_F(5);
|
|
break;
|
|
case 'R': /* Esc [ R == F6 on FreeBSD console. */
|
|
retval = KEY_F(6);
|
|
break;
|
|
case 'S': /* Esc [ S == F7 on FreeBSD console. */
|
|
retval = KEY_F(7);
|
|
break;
|
|
case 'T': /* Esc [ T == F8 on FreeBSD console. */
|
|
retval = KEY_F(8);
|
|
break;
|
|
case 'U': /* Esc [ U == PageDown on Mach console. */
|
|
retval = NANO_NEXTPAGE_KEY;
|
|
break;
|
|
case 'V': /* Esc [ V == PageUp on Mach console. */
|
|
retval = NANO_PREVPAGE_KEY;
|
|
break;
|
|
case 'W': /* Esc [ W == F11 on FreeBSD console. */
|
|
retval = KEY_F(11);
|
|
break;
|
|
case 'X': /* Esc [ X == F12 on FreeBSD console. */
|
|
retval = KEY_F(12);
|
|
break;
|
|
case 'Y': /* Esc [ Y == End on Mach console. */
|
|
retval = NANO_END_KEY;
|
|
break;
|
|
case 'Z': /* Esc [ Z == F14 on FreeBSD console. */
|
|
retval = KEY_F(14);
|
|
break;
|
|
case 'a': /* Esc [ a == Shift-Up on rxvt/Eterm. */
|
|
case 'b': /* Esc [ b == Shift-Down on rxvt/Eterm. */
|
|
case 'c': /* Esc [ c == Shift-Right on rxvt/
|
|
* Eterm. */
|
|
case 'd': /* Esc [ d == Shift-Left on rxvt/Eterm. */
|
|
retval = get_escape_seq_abcd(seq[1]);
|
|
break;
|
|
case '[':
|
|
if (seq_len >= 3) {
|
|
switch (seq[2]) {
|
|
case 'A': /* Esc [ [ A == F1 on Linux
|
|
* console. */
|
|
retval = KEY_F(1);
|
|
break;
|
|
case 'B': /* Esc [ [ B == F2 on Linux
|
|
* console. */
|
|
retval = KEY_F(2);
|
|
break;
|
|
case 'C': /* Esc [ [ C == F3 on Linux
|
|
* console. */
|
|
retval = KEY_F(3);
|
|
break;
|
|
case 'D': /* Esc [ [ D == F4 on Linux
|
|
* console. */
|
|
retval = KEY_F(4);
|
|
break;
|
|
case 'E': /* Esc [ [ E == F5 on Linux
|
|
* console. */
|
|
retval = KEY_F(5);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "get_escape_seq_kbinput(): retval = %d, ignore_seq = %d\n", retval, (int)*ignore_seq);
|
|
#endif
|
|
|
|
return retval;
|
|
}
|
|
|
|
/* Return the equivalent arrow key value for the case-insensitive
|
|
* letters A (up), B (down), C (right), and D (left). These are common
|
|
* to many escape sequences. */
|
|
int get_escape_seq_abcd(int kbinput)
|
|
{
|
|
switch (tolower(kbinput)) {
|
|
case 'a':
|
|
return NANO_PREVLINE_KEY;
|
|
case 'b':
|
|
return NANO_NEXTLINE_KEY;
|
|
case 'c':
|
|
return NANO_FORWARD_KEY;
|
|
case 'd':
|
|
return NANO_BACK_KEY;
|
|
default:
|
|
return ERR;
|
|
}
|
|
}
|
|
|
|
/* Translate a byte sequence: turn a three-digit decimal number from
|
|
* 000 to 255 into its corresponding byte value. */
|
|
int get_byte_kbinput(int kbinput
|
|
#ifndef NANO_TINY
|
|
, bool reset
|
|
#endif
|
|
)
|
|
{
|
|
static int byte_digits = 0, byte = 0;
|
|
int retval = ERR;
|
|
|
|
#ifndef NANO_TINY
|
|
if (reset) {
|
|
byte_digits = 0;
|
|
byte = 0;
|
|
return ERR;
|
|
}
|
|
#endif
|
|
|
|
/* Increment the byte digit counter. */
|
|
byte_digits++;
|
|
|
|
switch (byte_digits) {
|
|
case 1:
|
|
/* One digit: reset the byte sequence holder and add the
|
|
* digit we got to the 100's position of the byte sequence
|
|
* holder. */
|
|
byte = 0;
|
|
if ('0' <= kbinput && kbinput <= '2')
|
|
byte += (kbinput - '0') * 100;
|
|
else
|
|
/* If the character we got isn't a decimal digit, or if
|
|
* it is and it would put the byte sequence out of byte
|
|
* range, save it as the result. */
|
|
retval = kbinput;
|
|
break;
|
|
case 2:
|
|
/* Two digits: add the digit we got to the 10's position of
|
|
* the byte sequence holder. */
|
|
if (('0' <= kbinput && kbinput <= '5') || (byte < 200 &&
|
|
'6' <= kbinput && kbinput <= '9'))
|
|
byte += (kbinput - '0') * 10;
|
|
else
|
|
/* If the character we got isn't a decimal digit, or if
|
|
* it is and it would put the byte sequence out of byte
|
|
* range, save it as the result. */
|
|
retval = kbinput;
|
|
break;
|
|
case 3:
|
|
/* Three digits: add the digit we got to the 1's position of
|
|
* the byte sequence holder, and save the corresponding word
|
|
* value as the result. */
|
|
if (('0' <= kbinput && kbinput <= '5') || (byte < 250 &&
|
|
'6' <= kbinput && kbinput <= '9')) {
|
|
byte += (kbinput - '0');
|
|
retval = byte;
|
|
} else
|
|
/* If the character we got isn't a decimal digit, or if
|
|
* it is and it would put the byte sequence out of word
|
|
* range, save it as the result. */
|
|
retval = kbinput;
|
|
break;
|
|
default:
|
|
/* More than three digits: save the character we got as the
|
|
* result. */
|
|
retval = kbinput;
|
|
break;
|
|
}
|
|
|
|
/* If we have a result, reset the byte digit counter and the byte
|
|
* sequence holder. */
|
|
if (retval != ERR) {
|
|
byte_digits = 0;
|
|
byte = 0;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "get_byte_kbinput(): kbinput = %d, byte_digits = %d, byte = %d, retval = %d\n", kbinput, byte_digits, byte, retval);
|
|
#endif
|
|
|
|
return retval;
|
|
}
|
|
|
|
/* Translate a 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
|
|
#ifndef NANO_TINY
|
|
, bool reset
|
|
#endif
|
|
)
|
|
{
|
|
static int uni_digits = 0;
|
|
static long uni = 0;
|
|
long retval = ERR;
|
|
|
|
#ifndef NANO_TINY
|
|
if (reset) {
|
|
uni_digits = 0;
|
|
uni = 0;
|
|
return ERR;
|
|
}
|
|
#endif
|
|
|
|
/* Increment the Unicode digit counter. */
|
|
uni_digits++;
|
|
|
|
switch (uni_digits) {
|
|
case 1:
|
|
/* One digit: reset the Unicode sequence holder and add the
|
|
* digit we got to the 0x100000's position of the Unicode
|
|
* sequence holder. */
|
|
uni = 0;
|
|
if ('0' <= kbinput && kbinput <= '1')
|
|
uni += (kbinput - '0') * 0x100000;
|
|
else
|
|
/* If the character we got isn't a hexadecimal digit, or
|
|
* if it is and it would put the Unicode sequence out of
|
|
* valid range, save it as the result. */
|
|
retval = kbinput;
|
|
break;
|
|
case 2:
|
|
/* Two digits: add the digit we got to the 0x10000's
|
|
* position of the Unicode sequence holder. */
|
|
if ('0' == kbinput || (uni < 0x100000 && '1' <= kbinput &&
|
|
kbinput <= '9'))
|
|
uni += (kbinput - '0') * 0x10000;
|
|
else if (uni < 0x100000 && 'a' <= tolower(kbinput) &&
|
|
tolower(kbinput) <= 'f')
|
|
uni += (tolower(kbinput) + 10 - 'a') * 0x10000;
|
|
else
|
|
/* If the character we got isn't a hexadecimal digit, or
|
|
* if it is and it would put the Unicode sequence out of
|
|
* valid range, save it as the result. */
|
|
retval = kbinput;
|
|
break;
|
|
case 3:
|
|
/* Three digits: add the digit we got to the 0x1000's
|
|
* position of the Unicode sequence holder. */
|
|
if ('0' <= kbinput && kbinput <= '9')
|
|
uni += (kbinput - '0') * 0x1000;
|
|
else if ('a' <= tolower(kbinput) && tolower(kbinput) <= 'f')
|
|
uni += (tolower(kbinput) + 10 - 'a') * 0x1000;
|
|
else
|
|
/* If the character we got isn't a hexadecimal digit, or
|
|
* if it is and it would put the Unicode sequence out of
|
|
* valid range, save it as the result. */
|
|
retval = kbinput;
|
|
break;
|
|
case 4:
|
|
/* Four digits: add the digit we got to the 0x100's position
|
|
* of the Unicode sequence holder. */
|
|
if ('0' <= kbinput && kbinput <= '9')
|
|
uni += (kbinput - '0') * 0x100;
|
|
else if ('a' <= tolower(kbinput) && tolower(kbinput) <= 'f')
|
|
uni += (tolower(kbinput) + 10 - 'a') * 0x100;
|
|
else
|
|
/* If the character we got isn't a hexadecimal digit, or
|
|
* if it is and it would put the Unicode sequence out of
|
|
* valid range, save it as the result. */
|
|
retval = kbinput;
|
|
break;
|
|
case 5:
|
|
/* Five digits: add the digit we got to the 0x10's position
|
|
* of the Unicode sequence holder. */
|
|
if ('0' <= kbinput && kbinput <= '9')
|
|
uni += (kbinput - '0') * 0x10;
|
|
else if ('a' <= tolower(kbinput) && tolower(kbinput) <= 'f')
|
|
uni += (tolower(kbinput) + 10 - 'a') * 0x10;
|
|
else
|
|
/* If the character we got isn't a hexadecimal digit, or
|
|
* if it is and it would put the Unicode sequence out of
|
|
* valid range, save it as the result. */
|
|
retval = kbinput;
|
|
break;
|
|
case 6:
|
|
/* Six digits: add the digit we got to the 1's position of
|
|
* the Unicode sequence holder, and save the corresponding
|
|
* Unicode value as the result. */
|
|
if ('0' <= kbinput && kbinput <= '9') {
|
|
uni += (kbinput - '0');
|
|
retval = uni;
|
|
} else if ('a' <= tolower(kbinput) && tolower(kbinput) <=
|
|
'f') {
|
|
uni += (tolower(kbinput) + 10 - 'a');
|
|
retval = uni;
|
|
} else
|
|
/* If the character we got isn't a hexadecimal digit, or
|
|
* if it is and it would put the Unicode sequence out of
|
|
* valid range, save it as the result. */
|
|
retval = kbinput;
|
|
break;
|
|
default:
|
|
/* More than six digits: save the character we got as the
|
|
* result. */
|
|
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;
|
|
}
|
|
|
|
/* Translate a control character sequence: turn an ASCII non-control
|
|
* character into its corresponding control character. */
|
|
int get_control_kbinput(int kbinput)
|
|
{
|
|
int retval;
|
|
|
|
/* Ctrl-2 (Ctrl-Space, Ctrl-@, Ctrl-`) */
|
|
if (kbinput == '2' || kbinput == ' ' || kbinput == '@' ||
|
|
kbinput == '`')
|
|
retval = NANO_CONTROL_SPACE;
|
|
/* Ctrl-3 (Ctrl-[, Esc) to Ctrl-7 (Ctrl-_) */
|
|
else if ('3' <= kbinput && kbinput <= '7')
|
|
retval = kbinput - 24;
|
|
/* Ctrl-8 (Ctrl-?) */
|
|
else if (kbinput == '8' || kbinput == '?')
|
|
retval = NANO_CONTROL_8;
|
|
/* Ctrl-A to Ctrl-_ */
|
|
else if ('A' <= kbinput && kbinput <= '_')
|
|
retval = kbinput - 64;
|
|
/* Ctrl-a to Ctrl-~ */
|
|
else if ('a' <= kbinput && kbinput <= '~')
|
|
retval = kbinput - 96;
|
|
else
|
|
retval = kbinput;
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "get_control_kbinput(): kbinput = %d, retval = %d\n", kbinput, retval);
|
|
#endif
|
|
|
|
return retval;
|
|
}
|
|
|
|
/* Put the output-formatted characters in output back into the default
|
|
* keystroke buffer, so that they can be parsed and displayed as output
|
|
* again. */
|
|
void unparse_kbinput(char *output, size_t output_len)
|
|
{
|
|
int *input;
|
|
size_t i;
|
|
|
|
if (output_len == 0)
|
|
return;
|
|
|
|
input = (int *)nmalloc(output_len * sizeof(int));
|
|
for (i = 0; i < output_len; i++)
|
|
input[i] = (int)output[i];
|
|
unget_input(input, output_len);
|
|
free(input);
|
|
}
|
|
|
|
/* Read in a stream of characters verbatim, and return the length of the
|
|
* string in kbinput_len. Assume nodelay(win) is FALSE. */
|
|
int *get_verbatim_kbinput(WINDOW *win, size_t *kbinput_len)
|
|
{
|
|
int *retval;
|
|
|
|
/* Turn off flow control characters if necessary so that we can type
|
|
* them in verbatim, and turn the keypad off 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;
|
|
long uni;
|
|
|
|
/* Read in the first keystroke. */
|
|
while ((kbinput = get_input(win, 1)) == NULL);
|
|
|
|
/* Check whether the first keystroke is a hexadecimal digit. */
|
|
uni = get_unicode_kbinput(*kbinput
|
|
#ifndef NANO_TINY
|
|
, FALSE
|
|
#endif
|
|
);
|
|
|
|
/* If the first keystroke isn't a hexadecimal digit, put back the
|
|
* first keystroke. */
|
|
if (uni != ERR)
|
|
unget_input(kbinput, 1);
|
|
/* Otherwise, read in keystrokes until we have a complete word
|
|
* sequence, and put back the corresponding word value. */
|
|
else {
|
|
char *uni_mb;
|
|
int uni_mb_len, *seq, i;
|
|
|
|
while (uni == ERR) {
|
|
while ((kbinput = get_input(win, 1)) == NULL);
|
|
|
|
uni = get_unicode_kbinput(*kbinput
|
|
#ifndef NANO_TINY
|
|
, FALSE
|
|
#endif
|
|
);
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
/* 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
|
|
/* Check for a mouse event, and if one's taken place, save the
|
|
* coordinates where it took place in mouse_x and mouse_y. After that,
|
|
* assuming allow_shortcuts is FALSE, if the shortcut list on the
|
|
* bottom two lines of the screen is visible and the mouse event took
|
|
* place on it, figure out which shortcut was clicked and put back the
|
|
* equivalent keystroke(s). Return FALSE if no keystrokes were
|
|
* put back, or TRUE if at least one was. Assume that KEY_MOUSE has
|
|
* already been read in. */
|
|
bool get_mouseinput(int *mouse_x, int *mouse_y, bool allow_shortcuts)
|
|
{
|
|
MEVENT mevent;
|
|
|
|
*mouse_x = -1;
|
|
*mouse_y = -1;
|
|
|
|
/* First, get the actual mouse event. */
|
|
if (getmouse(&mevent) == ERR)
|
|
return FALSE;
|
|
|
|
/* Save the screen coordinates where the mouse event took place. */
|
|
*mouse_x = mevent.x;
|
|
*mouse_y = mevent.y;
|
|
|
|
/* If we're allowing shortcuts, the current shortcut list is being
|
|
* displayed on the last two lines of the screen, and the mouse
|
|
* event took place inside it, we need to figure out which shortcut
|
|
* was clicked and put back the equivalent keystroke(s) for it. */
|
|
if (allow_shortcuts && !ISSET(NO_HELP) && wenclose(bottomwin,
|
|
*mouse_y, *mouse_x)) {
|
|
int i, j;
|
|
size_t currslen;
|
|
/* The number of shortcuts in the current shortcut list. */
|
|
const shortcut *s = currshortcut;
|
|
/* The actual shortcut we clicked on, starting at the first
|
|
* one in the current shortcut list. */
|
|
|
|
/* Get the shortcut lists' length. */
|
|
if (currshortcut == main_list)
|
|
currslen = MAIN_VISIBLE;
|
|
else {
|
|
currslen = length_of_list(currshortcut);
|
|
|
|
/* We don't show any more shortcuts than the main list
|
|
* does. */
|
|
if (currslen > MAIN_VISIBLE)
|
|
currslen = MAIN_VISIBLE;
|
|
}
|
|
|
|
/* Calculate the width of 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 y-coordinate relative to the beginning of
|
|
* the shortcut list in bottomwin, i.e, with the sizes of
|
|
* topwin, edit, and the first line of bottomwin subtracted
|
|
* out, and set j to it. */
|
|
j = *mouse_y - (2 - no_more_space()) - editwinrows - 1;
|
|
|
|
/* If we're on the statusbar, don't do anything. */
|
|
if (j < 0)
|
|
return FALSE;
|
|
|
|
/* 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 clicked. */
|
|
j = (*mouse_x / i) * 2 + j;
|
|
|
|
/* Adjust j if we clicked in the last two shortcuts. */
|
|
if ((j >= currslen) && (*mouse_x % i < COLS % i))
|
|
j -= 2;
|
|
|
|
/* If we're beyond the last shortcut, don't do anything. */
|
|
if (j >= currslen)
|
|
return FALSE;
|
|
|
|
/* Go through the shortcut list to determine which shortcut was
|
|
* clicked. */
|
|
for (; j > 0; j--)
|
|
s = s->next;
|
|
|
|
/* And put back the equivalent key. Assume that each shortcut
|
|
* has, at the very least, an equivalent control key, an
|
|
* equivalent primary meta key sequence, or both. */
|
|
if (s->ctrlval != NANO_NO_KEY) {
|
|
unget_kbinput(s->ctrlval, FALSE, FALSE);
|
|
return TRUE;
|
|
} else if (s->metaval != NANO_NO_KEY) {
|
|
unget_kbinput(s->metaval, TRUE, FALSE);
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
#endif /* !DISABLE_MOUSE */
|
|
|
|
/* 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 shortcut *get_shortcut(const shortcut *s_list, int *kbinput, bool
|
|
*meta_key, bool *func_key)
|
|
{
|
|
const shortcut *s = s_list;
|
|
size_t slen = length_of_list(s_list);
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "get_shortcut(): kbinput = %d, meta_key = %d, func_key = %d\n", *kbinput, (int)*meta_key, (int)*func_key);
|
|
#endif
|
|
|
|
/* Check for shortcuts. */
|
|
for (; slen > 0; slen--) {
|
|
/* We've found a shortcut if:
|
|
*
|
|
* 1. The key exists.
|
|
* 2. The key is a control key in the shortcut list.
|
|
* 3. meta_key is TRUE and the key is the primary or
|
|
* miscellaneous meta sequence in the shortcut list.
|
|
* 4. func_key is TRUE and the key is a function key in the
|
|
* shortcut list. */
|
|
|
|
if (*kbinput != NANO_NO_KEY && (*kbinput == s->ctrlval ||
|
|
(*meta_key == TRUE && (*kbinput == s->metaval ||
|
|
*kbinput == s->miscval)) || (*func_key == TRUE &&
|
|
*kbinput == s->funcval))) {
|
|
break;
|
|
}
|
|
|
|
s = s->next;
|
|
}
|
|
|
|
/* Translate the shortcut to either its control key or its meta key
|
|
* equivalent. Assume that the shortcut has an equivalent control
|
|
* key, an equivalent primary meta key sequence, or both. */
|
|
if (slen > 0) {
|
|
if (s->ctrlval != NANO_NO_KEY) {
|
|
*meta_key = FALSE;
|
|
*func_key = FALSE;
|
|
*kbinput = s->ctrlval;
|
|
return s;
|
|
} else if (s->metaval != NANO_NO_KEY) {
|
|
*meta_key = TRUE;
|
|
*func_key = FALSE;
|
|
*kbinput = s->metaval;
|
|
return s;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
#ifndef NANO_TINY
|
|
/* Return the global toggle corresponding to the values of kbinput (the
|
|
* key itself) and meta_key (whether the key is a meta sequence), if
|
|
* any. */
|
|
const toggle *get_toggle(int kbinput, bool meta_key)
|
|
{
|
|
const toggle *t = toggles;
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "get_toggle(): kbinput = %d, meta_key = %d\n", kbinput, (int)meta_key);
|
|
#endif
|
|
|
|
/* Check for toggles. */
|
|
for (; t != NULL; t = t->next) {
|
|
/* We've found a toggle if meta_key is TRUE and the key is in
|
|
* the meta key toggle list. */
|
|
if (meta_key && kbinput == t->val)
|
|
break;
|
|
}
|
|
|
|
return t;
|
|
}
|
|
#endif /* !NANO_TINY */
|
|
|
|
/* 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] != '\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) > 1) {
|
|
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;
|
|
}
|
|
|
|
/* Display the path specified in path on the titlebar, along with the
|
|
* current version of nano and whether the current file has been
|
|
* modified. If path is NULL, assume we're in normal editing mode and
|
|
* display the current filename instead. Otherwise, assume we're in the
|
|
* file browser, and don't display whether the current file has been
|
|
* modified. */
|
|
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. */
|
|
char *exppath = NULL;
|
|
/* The file name, expanded for display. */
|
|
bool newfie = FALSE;
|
|
/* Do we say "New Buffer"? */
|
|
bool dots = FALSE;
|
|
/* Do we put an ellipsis before the path? */
|
|
|
|
assert(path != NULL || 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] != '\0') ? state : _("Modified"));
|
|
|
|
/* 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, path should be 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;
|
|
|
|
dots = (lenpt >= space);
|
|
|
|
if (dots) {
|
|
start_col = lenpt - space + 3;
|
|
space -= 3;
|
|
} else
|
|
start_col = 0;
|
|
|
|
exppath = display_string(path, start_col, space, FALSE);
|
|
}
|
|
|
|
if (!dots) {
|
|
size_t exppathlen = newfie ? 0 : strlenpt(exppath);
|
|
/* The length of the expanded filename. */
|
|
|
|
/* There is room for the whole filename, so we center it. */
|
|
mvwaddnstr(topwin, 0, verlen + ((space - exppathlen) / 3),
|
|
prefix, actual_x(prefix, prefixlen));
|
|
if (!newfie) {
|
|
waddch(topwin, ' ');
|
|
waddstr(topwin, exppath);
|
|
}
|
|
} else {
|
|
/* We will say something like "File: ...ename". */
|
|
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);
|
|
}
|
|
|
|
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 (curses_ended) {
|
|
vfprintf(stderr, msg, ap);
|
|
va_end(ap);
|
|
return;
|
|
}
|
|
|
|
/* Blank out the line. */
|
|
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-five keystrokes,
|
|
* as Pico does. */
|
|
statusblank =
|
|
#ifndef NANO_TINY
|
|
ISSET(QUICK_BLANK) && !ISSET(CONST_UPDATE) ? 1 :
|
|
#endif
|
|
25;
|
|
}
|
|
|
|
/* Display the shortcut list in s on the last two rows of the bottom
|
|
* portion of the window. */
|
|
void bottombars(const shortcut *s)
|
|
{
|
|
size_t i, colwidth, slen;
|
|
|
|
if (ISSET(NO_HELP))
|
|
return;
|
|
|
|
if (s == main_list) {
|
|
slen = MAIN_VISIBLE;
|
|
|
|
assert(slen <= length_of_list(s));
|
|
} else {
|
|
slen = length_of_list(s);
|
|
|
|
/* Don't show any more shortcuts than the main list does. */
|
|
if (slen > MAIN_VISIBLE)
|
|
slen = MAIN_VISIBLE;
|
|
}
|
|
|
|
/* There will be this many characters per column, 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();
|
|
|
|
for (i = 0; i < slen; i++, s = s->next) {
|
|
const char *keystr;
|
|
char foo[4] = "";
|
|
|
|
/* Yucky sentinel values that we can't handle a better way. */
|
|
if (s->ctrlval == NANO_CONTROL_SPACE)
|
|
strcpy(foo, "^ ");
|
|
else if (s->ctrlval == NANO_CONTROL_8)
|
|
strcpy(foo, "^?");
|
|
/* Normal values. Assume that the shortcut has an equivalent
|
|
* control key, meta key sequence, or both. */
|
|
else if (s->ctrlval != NANO_NO_KEY)
|
|
sprintf(foo, "^%c", s->ctrlval + 64);
|
|
else if (s->metaval != NANO_NO_KEY)
|
|
sprintf(foo, "M-%c", toupper(s->metaval));
|
|
|
|
keystr = foo;
|
|
|
|
wmove(bottomwin, 1 + i % 2, (i / 2) * colwidth);
|
|
onekey(keystr, s->desc, colwidth + (COLS % colwidth));
|
|
}
|
|
|
|
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)
|
|
{
|
|
/* 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;
|
|
}
|
|
|
|
openfile->current_y = openfile->current->lineno -
|
|
openfile->edittop->lineno;
|
|
if (openfile->current_y < editwinrows) {
|
|
size_t xpt = xplustabs();
|
|
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(const 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;
|
|
|
|
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
|
|
* C-strings correctly. */
|
|
while (k < endpos) {
|
|
/* Note the fifth parameter to regexec(). It says
|
|
* not to match the beginning-of-line character
|
|
* unless k is 0. If regexec() returns REG_NOMATCH,
|
|
* there are no more matches in the line. */
|
|
if (regexec(tmpcolor->start, &fileptr->data[k], 1,
|
|
&startmatch, (k == 0) ? 0 : REG_NOTBOL) ==
|
|
REG_NOMATCH)
|
|
break;
|
|
/* Translate the match to the beginning of the
|
|
* line. */
|
|
startmatch.rm_so += k;
|
|
startmatch.rm_eo += k;
|
|
if (startmatch.rm_so == startmatch.rm_eo) {
|
|
startmatch.rm_eo++;
|
|
statusbar(
|
|
_("Refusing zero-length regex match"));
|
|
} else if (startmatch.rm_so < endpos &&
|
|
startmatch.rm_eo > startpos) {
|
|
if (startmatch.rm_so <= startpos)
|
|
x_start = 0;
|
|
else
|
|
x_start = strnlenpt(fileptr->data,
|
|
startmatch.rm_so) - start;
|
|
|
|
index = actual_x(converted, x_start);
|
|
|
|
paintlen = actual_x(converted + index,
|
|
strnlenpt(fileptr->data,
|
|
startmatch.rm_eo) - start - x_start);
|
|
|
|
assert(0 <= x_start && 0 <= paintlen);
|
|
|
|
mvwaddnstr(edit, line, x_start, converted +
|
|
index, paintlen);
|
|
}
|
|
k = startmatch.rm_eo;
|
|
}
|
|
} else {
|
|
/* This is a multi-line regex. There are two steps.
|
|
* First, we have to see if the beginning of the line is
|
|
* colored by a start on an earlier line, and an end on
|
|
* this line or later.
|
|
*
|
|
* We find the first line before fileptr matching the
|
|
* start. If every match on that line is followed by an
|
|
* end, then go to step two. Otherwise, find the next
|
|
* line after start_line matching the end. If that line
|
|
* is not before fileptr, then paint the beginning of
|
|
* this line. */
|
|
const filestruct *start_line = fileptr->prev;
|
|
/* The first line before fileptr matching start. */
|
|
regoff_t start_col;
|
|
/* Where it starts in that line. */
|
|
const filestruct *end_line;
|
|
|
|
while (start_line != NULL && regexec(tmpcolor->start,
|
|
start_line->data, 1, &startmatch, 0) ==
|
|
REG_NOMATCH) {
|
|
/* If there is an end on this line, there is no need
|
|
* to look for starts on earlier lines. */
|
|
if (regexec(tmpcolor->end, start_line->data, 0,
|
|
NULL, 0) == 0)
|
|
goto step_two;
|
|
start_line = start_line->prev;
|
|
}
|
|
/* No start found, so skip to the next step. */
|
|
if (start_line == NULL)
|
|
goto step_two;
|
|
/* Now start_line is the first line before fileptr
|
|
* containing a start match. Is there a start on this
|
|
* line not followed by an end on this line? */
|
|
start_col = 0;
|
|
while (TRUE) {
|
|
start_col += startmatch.rm_so;
|
|
startmatch.rm_eo -= startmatch.rm_so;
|
|
if (regexec(tmpcolor->end, start_line->data +
|
|
start_col + startmatch.rm_eo, 0, NULL,
|
|
(start_col + startmatch.rm_eo == 0) ? 0 :
|
|
REG_NOTBOL) == REG_NOMATCH)
|
|
/* No end found after this start. */
|
|
break;
|
|
start_col++;
|
|
if (regexec(tmpcolor->start, start_line->data +
|
|
start_col, 1, &startmatch,
|
|
REG_NOTBOL) == REG_NOMATCH)
|
|
/* No later start on this line. */
|
|
goto step_two;
|
|
}
|
|
/* Indeed, there is a start not followed on this line by
|
|
* an end. */
|
|
|
|
/* We have already checked that there is no end before
|
|
* fileptr and after the start. Is there an end after
|
|
* the start at all? We don't paint unterminated
|
|
* starts. */
|
|
end_line = fileptr;
|
|
while (end_line != NULL && regexec(tmpcolor->end,
|
|
end_line->data, 1, &endmatch, 0) == REG_NOMATCH)
|
|
end_line = end_line->next;
|
|
|
|
/* No end found, or it is too early. */
|
|
if (end_line == NULL || (end_line == fileptr &&
|
|
endmatch.rm_eo <= startpos))
|
|
goto step_two;
|
|
|
|
/* Now paint the start of fileptr. */
|
|
if (end_line != fileptr)
|
|
/* If the start of fileptr is on a different line
|
|
* from the end, paintlen is -1, meaning that
|
|
* everything on the line gets painted. */
|
|
paintlen = -1;
|
|
else
|
|
/* Otherwise, paintlen is the expanded location of
|
|
* the end of the match minus the expanded location
|
|
* of the beginning of the page. */
|
|
paintlen = actual_x(converted,
|
|
strnlenpt(fileptr->data, endmatch.rm_eo) -
|
|
start);
|
|
|
|
mvwaddnstr(edit, 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;
|
|
|
|
if (startmatch.rm_so <= startpos)
|
|
x_start = 0;
|
|
else
|
|
x_start = strnlenpt(fileptr->data,
|
|
startmatch.rm_so) - start;
|
|
|
|
index = actual_x(converted, x_start);
|
|
|
|
if (regexec(tmpcolor->end, fileptr->data +
|
|
startmatch.rm_eo, 1, &endmatch,
|
|
(startmatch.rm_eo == 0) ? 0 : REG_NOTBOL) ==
|
|
0) {
|
|
/* Translate the end match to be relative to the
|
|
* beginning of the line. */
|
|
endmatch.rm_so += startmatch.rm_eo;
|
|
endmatch.rm_eo += startmatch.rm_eo;
|
|
/* There is an end on this line. But does it
|
|
* appear on this page, and is the match more
|
|
* than zero characters long? */
|
|
if (endmatch.rm_eo > startpos &&
|
|
endmatch.rm_eo > startmatch.rm_so) {
|
|
paintlen = actual_x(converted + index,
|
|
strnlenpt(fileptr->data,
|
|
endmatch.rm_eo) - start - x_start);
|
|
|
|
assert(0 <= x_start && x_start < COLS);
|
|
|
|
mvwaddnstr(edit, line, x_start, converted +
|
|
index, paintlen);
|
|
}
|
|
} else {
|
|
/* There is no end on this line. But we haven't
|
|
* yet looked for one on later lines. */
|
|
end_line = fileptr->next;
|
|
|
|
while (end_line != NULL &&
|
|
regexec(tmpcolor->end, end_line->data,
|
|
0, NULL, 0) == REG_NOMATCH)
|
|
end_line = end_line->next;
|
|
|
|
if (end_line != NULL) {
|
|
assert(0 <= x_start && x_start < COLS);
|
|
|
|
mvwaddnstr(edit, line, x_start, converted +
|
|
index, -1);
|
|
/* We painted to the end of the line, so
|
|
* don't bother checking any more starts. */
|
|
break;
|
|
}
|
|
}
|
|
start_col = startmatch.rm_so + 1;
|
|
}
|
|
}
|
|
|
|
wattroff(edit, A_BOLD);
|
|
wattroff(edit, COLOR_PAIR(tmpcolor->pairnum));
|
|
}
|
|
}
|
|
#endif /* ENABLE_COLOR */
|
|
|
|
#ifndef NANO_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 chars to paint on this line. There are COLS
|
|
* characters on a whole line. */
|
|
size_t index;
|
|
/* Index in converted where we paint. */
|
|
|
|
mark_order(&top, &top_x, &bot, &bot_x, NULL);
|
|
|
|
if (top->lineno < fileptr->lineno || top_x < startpos)
|
|
top_x = startpos;
|
|
if (bot->lineno > fileptr->lineno || bot_x > endpos)
|
|
bot_x = endpos;
|
|
|
|
/* The selected bit of fileptr is on this page. */
|
|
if (top_x < endpos && bot_x > startpos) {
|
|
assert(startpos <= top_x);
|
|
|
|
/* x_start is the expanded location of the beginning of the
|
|
* mark minus the beginning of the page. */
|
|
x_start = strnlenpt(fileptr->data, top_x) - start;
|
|
|
|
if (bot_x >= endpos)
|
|
/* If the end of the mark is off the page, paintlen is
|
|
* -1, meaning that everything on the line gets
|
|
* painted. */
|
|
paintlen = -1;
|
|
else
|
|
/* Otherwise, paintlen is the expanded location of the
|
|
* end of the mark minus the expanded location of the
|
|
* beginning of the mark. */
|
|
paintlen = strnlenpt(fileptr->data, bot_x) -
|
|
(x_start + start);
|
|
|
|
/* If x_start is before the beginning of the page, shift
|
|
* paintlen x_start characters to compensate, and put
|
|
* x_start at the beginning of the page. */
|
|
if (x_start < 0) {
|
|
paintlen += x_start;
|
|
x_start = 0;
|
|
}
|
|
|
|
assert(x_start >= 0 && x_start <= strlen(converted));
|
|
|
|
index = actual_x(converted, x_start);
|
|
|
|
if (paintlen > 0)
|
|
paintlen = actual_x(converted + index, paintlen);
|
|
|
|
wattron(edit, 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. */
|
|
void update_line(const filestruct *fileptr, size_t index)
|
|
{
|
|
int line;
|
|
/* The line in the edit window that we want to update. */
|
|
char *converted;
|
|
/* fileptr->data converted to have tabs and control characters
|
|
* expanded. */
|
|
size_t page_start;
|
|
|
|
assert(fileptr != NULL);
|
|
|
|
line = fileptr->lineno - openfile->edittop->lineno;
|
|
|
|
/* We assume the line numbers are valid. Is that really true? */
|
|
assert(line < 0 || line == check_linenumbers(fileptr));
|
|
|
|
if (line < 0 || line >= editwinrows)
|
|
return;
|
|
|
|
/* First, blank out the line. */
|
|
blank_line(edit, line, 0, COLS);
|
|
|
|
/* Next, convert variables that index the line to their equivalent
|
|
* positions in the expanded line. */
|
|
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, TRUE);
|
|
|
|
/* Paint the line. */
|
|
edit_draw(fileptr, converted, line, page_start);
|
|
free(converted);
|
|
|
|
if (page_start > 0)
|
|
mvwaddch(edit, line, 0, '$');
|
|
if (strlenpt(fileptr->data) > page_start + COLS)
|
|
mvwaddch(edit, line, COLS - 1, '$');
|
|
}
|
|
|
|
/* Return TRUE if we need an update after moving horizontally, and FALSE
|
|
* otherwise. We need one if the mark is on or if old_pww and
|
|
* placewewant are on different pages. */
|
|
bool need_horizontal_update(size_t old_pww)
|
|
{
|
|
return
|
|
#ifndef NANO_TINY
|
|
openfile->mark_set ||
|
|
#endif
|
|
get_page_start(old_pww) !=
|
|
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 old_pww and
|
|
* placewewant are on different pages. */
|
|
bool need_vertical_update(size_t old_pww)
|
|
{
|
|
return
|
|
#ifndef NANO_TINY
|
|
openfile->mark_set ||
|
|
#endif
|
|
get_page_start(old_pww) !=
|
|
get_page_start(openfile->placewewant);
|
|
}
|
|
|
|
/* Scroll the edit window in the given direction and the given number
|
|
* of lines, and draw new lines on the blank lines left after the
|
|
* scrolling. direction is the direction to scroll, either UP or DOWN,
|
|
* and nlines is the number of lines to scroll. 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)
|
|
{
|
|
bool do_redraw = need_vertical_update(0);
|
|
const filestruct *foo;
|
|
ssize_t i;
|
|
|
|
/* Don't bother scrolling less than one line. */
|
|
if (nlines < 1)
|
|
return;
|
|
|
|
/* 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) {
|
|
if (openfile->edittop == openfile->fileage)
|
|
break;
|
|
openfile->edittop = openfile->edittop->prev;
|
|
} else {
|
|
if (openfile->edittop == openfile->filebot)
|
|
break;
|
|
openfile->edittop = openfile->edittop->next;
|
|
}
|
|
}
|
|
|
|
/* 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
|
|
* in the latter case, call edit_refresh() beforehand. */
|
|
if (nlines == 0)
|
|
return;
|
|
|
|
if (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) ? -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 && openfile->edittop == openfile->fileage) ||
|
|
(direction == DOWN && 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) {
|
|
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) || (i == 1 &&
|
|
direction == UP)) {
|
|
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(const filestruct *old_current, size_t old_pww)
|
|
{
|
|
bool do_redraw = need_vertical_update(0) ||
|
|
need_vertical_update(old_pww);
|
|
const filestruct *foo;
|
|
|
|
/* 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 +
|
|
editwinrows || openfile->current->lineno <
|
|
openfile->edittop->lineno || openfile->current->lineno >=
|
|
openfile->edittop->lineno + editwinrows) {
|
|
filestruct *old_edittop = openfile->edittop;
|
|
ssize_t nlines;
|
|
|
|
/* 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(
|
|
#ifndef NANO_TINY
|
|
ISSET(SMOOTH_SCROLL) ? NONE :
|
|
#endif
|
|
CENTER);
|
|
|
|
nlines = openfile->edittop->lineno - old_edittop->lineno;
|
|
|
|
openfile->edittop = old_edittop;
|
|
|
|
/* Update old_current if we're not on the first page and/or
|
|
* 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, -nlines);
|
|
else
|
|
edit_scroll(DOWN, nlines);
|
|
|
|
return;
|
|
}
|
|
|
|
/* Update old_current and current if we're not on the first page
|
|
* and/or we're not on the same page as before. If the mark is on,
|
|
* update all the lines between old_current and current too. */
|
|
foo = old_current;
|
|
|
|
while (foo != 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)
|
|
{
|
|
const filestruct *foo;
|
|
int nlines;
|
|
|
|
if (openfile->current->lineno < openfile->edittop->lineno ||
|
|
openfile->current->lineno >= openfile->edittop->lineno +
|
|
editwinrows)
|
|
/* Put the top line of the edit window in range of the current
|
|
* line. */
|
|
edit_update(
|
|
#ifndef NANO_TINY
|
|
ISSET(SMOOTH_SCROLL) ? NONE :
|
|
#endif
|
|
CENTER);
|
|
|
|
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++) {
|
|
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. |