Files
screen/src/input.c
2019-11-09 14:40:37 +01:00

441 lines
13 KiB
C

/* Copyright (c) 2008, 2009
* Juergen Weigert (jnweiger@immd4.informatik.uni-erlangen.de)
* Michael Schroeder (mlschroe@immd4.informatik.uni-erlangen.de)
* Micah Cowan (micah@cowan.name)
* Sadrul Habib Chowdhury (sadrul@users.sourceforge.net)
* Copyright (c) 1993-2002, 2003, 2005, 2006, 2007
* Juergen Weigert (jnweiger@immd4.informatik.uni-erlangen.de)
* Michael Schroeder (mlschroe@immd4.informatik.uni-erlangen.de)
* Copyright (c) 1987 Oliver Laumann
*
* 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 (see the file COPYING); if not, see
* https://www.gnu.org/licenses/, or contact Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*
****************************************************************
*/
#include "config.h"
#include "input.h"
#include <stddef.h>
#include "screen.h"
#include "misc.h"
#define INPUTLINE (flayer->l_height - 1)
static void InpProcess(char **, size_t *);
static void InpAbort(void);
static void InpRedisplayLine(int, int, int, int);
struct inpline {
char buf[MAXSTR + 1]; /* text buffer */
size_t len; /* length of the editible string */
size_t pos; /* cursor position in editable string */
struct inpline *next, *prev;
};
/* 'inphist' is used to store the current input when scrolling through history.
* inpline->prev == history-prev
* inpline->next == history-next
*/
static struct inpline inphist;
struct inpdata {
struct inpline inp;
size_t inpmaxlen; /* MAXSTR, or less, if caller has shorter buffer */
char *inpstring; /* the prompt */
size_t inpstringlen; /* length of the prompt */
int inpmode; /* INP_NOECHO, INP_RAW, INP_EVERY */
void (*inpfinfunc) (char *buf, size_t len, void *priv);
char *priv; /* private data for finfunc */
int privdata; /* private data space */
char *search; /* the search string */
};
static const struct LayFuncs InpLf = {
InpProcess,
InpAbort,
InpRedisplayLine,
DefClearLine,
DefResize,
DefRestore,
NULL
};
/*
** Here is the input routine
*/
/* called once, after InitOverlayPage in Input() or Isearch() */
void inp_setprompt(char *p, char *s)
{
struct inpdata *inpdata;
inpdata = (struct inpdata *)flayer->l_data;
if (p) {
inpdata->inpstringlen = strlen(p);
inpdata->inpstring = p;
}
if (s) {
if (s != inpdata->inp.buf)
strncpy(inpdata->inp.buf, s, ARRAY_SIZE(inpdata->inp.buf) - 1);
inpdata->inp.buf[ARRAY_SIZE(inpdata->inp.buf) - 1] = 0;
inpdata->inp.pos = inpdata->inp.len = strlen(inpdata->inp.buf);
}
InpRedisplayLine(INPUTLINE, 0, flayer->l_width - 1, 0);
flayer->l_x = inpdata->inpstringlen + (inpdata->inpmode & INP_NOECHO ? 0 : inpdata->inp.pos);
flayer->l_y = INPUTLINE;
}
/*
* We dont use HS status line with Input().
* If we would use it, then we should check e_tgetflag("es") if
* we are allowed to use esc sequences there.
*
* mode is an OR of
* INP_NOECHO == suppress echoing of characters.
* INP_RAW == raw mode. call finfunc after each character typed.
* INP_EVERY == digraph mode.
*/
void Input(char *istr, size_t len, int mode, void (*finfunc) (char *buf, size_t len, void *priv), char *priv, int data)
{
size_t maxlen;
struct inpdata *inpdata;
if (!flayer)
return;
if (len > MAXSTR)
len = MAXSTR;
if (!(mode & INP_NOECHO)) {
maxlen = flayer->l_width - 1 - strlen(istr);
if (len > maxlen)
len = maxlen;
}
if (InitOverlayPage(sizeof(struct inpdata), &InpLf, 1))
return;
flayer->l_mode = 1;
inpdata = (struct inpdata *)flayer->l_data;
inpdata->inpmaxlen = len;
inpdata->inpfinfunc = finfunc;
inpdata->inp.pos = inpdata->inp.len = 0;
inpdata->inp.prev = inphist.prev;
inpdata->inpmode = mode;
inpdata->privdata = data;
if (!priv)
priv = (char *)&inpdata->privdata;
inpdata->priv = priv;
inpdata->inpstringlen = 0;
inpdata->inpstring = NULL;
inpdata->search = NULL;
if (istr)
inp_setprompt(istr, (char *)NULL);
}
static void erase_chars(struct inpdata *inpdata, char *from, char *to, int x, int mv)
{
int chng;
if ((ptrdiff_t)inpdata->inp.len > to - inpdata->inp.buf)
memmove(from, to, inpdata->inp.len - (to - inpdata->inp.buf));
chng = to - from;
if (mv) {
x -= chng;
inpdata->inp.pos -= chng;
}
inpdata->inp.len -= chng;
if (!(inpdata->inpmode & INP_NOECHO)) {
struct mchar mc;
char *s = from < to ? from : to;
mc = mchar_so;
while (s < inpdata->inp.buf + inpdata->inp.len) {
mc.image = *s++;
LPutChar(flayer, &mc, x++, INPUTLINE);
}
while (chng--)
LPutChar(flayer, &mchar_blank, x++, INPUTLINE);
x = inpdata->inpstringlen + inpdata->inp.pos;
LGotoPos(flayer, x, INPUTLINE);
}
}
static void InpProcess(char **ppbuf, size_t *plen)
{
int len, x;
char *pbuf;
char ch;
struct inpdata *inpdata;
Display *inpdisplay;
int prev, next, search = 0;
inpdata = (struct inpdata *)flayer->l_data;
inpdisplay = display;
#define RESET_SEARCH do { if (inpdata->search) Free(inpdata->search); } while (0)
LGotoPos(flayer, inpdata->inpstringlen + (inpdata->inpmode & INP_NOECHO ? 0 : inpdata->inp.pos), INPUTLINE);
if (ppbuf == NULL) {
InpAbort();
return;
}
x = inpdata->inpstringlen + inpdata->inp.pos;
len = *plen;
pbuf = *ppbuf;
while (len) {
char *p = inpdata->inp.buf + inpdata->inp.pos;
ch = *pbuf++;
len--;
if (inpdata->inpmode & INP_EVERY) {
inpdata->inp.buf[inpdata->inp.len] = ch;
if (ch) {
display = inpdisplay;
(*inpdata->inpfinfunc) (inpdata->inp.buf, inpdata->inp.len, inpdata->priv);
ch = inpdata->inp.buf[inpdata->inp.len];
}
} else if (inpdata->inpmode & INP_RAW) {
display = inpdisplay;
(*inpdata->inpfinfunc) (&ch, 1, inpdata->priv); /* raw */
if (ch)
continue;
}
if (((unsigned char)ch & 0177) >= ' ' && ch != 0177 && inpdata->inp.len < inpdata->inpmaxlen) {
if (inpdata->inp.len > inpdata->inp.pos)
memmove(p + 1, p, inpdata->inp.len - inpdata->inp.pos);
inpdata->inp.buf[inpdata->inp.pos++] = ch;
inpdata->inp.len++;
if (!(inpdata->inpmode & INP_NOECHO)) {
struct mchar mc;
mc = mchar_so;
mc.image = *p++;
LPutChar(flayer, &mc, x, INPUTLINE);
x++;
if (p < inpdata->inp.buf + inpdata->inp.len) {
while (p < inpdata->inp.buf + inpdata->inp.len) {
mc.image = *p++;
LPutChar(flayer, &mc, x++, INPUTLINE);
}
x = inpdata->inpstringlen + inpdata->inp.pos;
LGotoPos(flayer, x, INPUTLINE);
}
}
RESET_SEARCH;
} else if ((ch == '\b' || ch == 0177) && inpdata->inp.pos > 0) {
erase_chars(inpdata, p - 1, p, x, 1);
RESET_SEARCH;
} else if (ch == '\025') { /* CTRL-U */
x = inpdata->inpstringlen;
if (inpdata->inp.len && !(inpdata->inpmode & INP_NOECHO)) {
LClearArea(flayer, x, INPUTLINE, x + inpdata->inp.len - 1, INPUTLINE, 0, 0);
LGotoPos(flayer, x, INPUTLINE);
}
inpdata->inp.len = inpdata->inp.pos = 0;
} else if (ch == '\013') { /* CTRL-K */
x = inpdata->inpstringlen + inpdata->inp.pos;
if (inpdata->inp.len > inpdata->inp.pos && !(inpdata->inpmode & INP_NOECHO)) {
LClearArea(flayer, x, INPUTLINE, x + inpdata->inp.len - inpdata->inp.pos - 1, INPUTLINE,
0, 0);
LGotoPos(flayer, x, INPUTLINE);
}
inpdata->inp.len = inpdata->inp.pos;
} else if (ch == '\027' && inpdata->inp.pos > 0) { /* CTRL-W */
char *oldp = p--;
while (p > inpdata->inp.buf && *p == ' ')
p--;
while (p > inpdata->inp.buf && *(p - 1) != ' ')
p--;
erase_chars(inpdata, p, oldp, x, 1);
RESET_SEARCH;
} else if (ch == '\004' && inpdata->inp.pos < inpdata->inp.len) { /* CTRL-D */
erase_chars(inpdata, p, p + 1, x, 0);
RESET_SEARCH;
} else if (ch == '\001' || (unsigned char)ch == 0201) { /* CTRL-A */
LGotoPos(flayer, x -= inpdata->inp.pos, INPUTLINE);
inpdata->inp.pos = 0;
} else if ((ch == '\002' || (unsigned char)ch == 0202) && inpdata->inp.pos > 0) { /* CTRL-B */
LGotoPos(flayer, --x, INPUTLINE);
inpdata->inp.pos--;
} else if (ch == '\005' || (unsigned char)ch == 0205) { /* CTRL-E */
LGotoPos(flayer, x += inpdata->inp.len - inpdata->inp.pos, INPUTLINE);
inpdata->inp.pos = inpdata->inp.len;
} else if ((ch == '\006' || (unsigned char)ch == 0206) && inpdata->inp.pos < inpdata->inp.len) { /* CTRL-F */
LGotoPos(flayer, ++x, INPUTLINE);
inpdata->inp.pos++;
} else if ((prev = ((ch == '\020' || (unsigned char)ch == 0220) && /* CTRL-P */
inpdata->inp.prev)) || (next = ((ch == '\016' || (unsigned char)ch == 0216) && /* CTRL-N */
inpdata->inp.next)) ||
(search = ((ch == '\022' || (unsigned char)ch == 0222) && inpdata->inp.prev))) {
struct mchar mc;
struct inpline *sel;
int pos = -1;
mc = mchar_so;
if (prev)
sel = inpdata->inp.prev;
else if (next)
sel = inpdata->inp.next;
else {
/* search */
inpdata->inp.buf[inpdata->inp.len] = 0; /* Remove the ctrl-r from the end */
if (!inpdata->search)
inpdata->search = SaveStr(inpdata->inp.buf);
for (sel = inpdata->inp.prev; sel; sel = sel->prev) {
char *f;
if ((f = strstr(sel->buf, inpdata->search))) {
pos = f - sel->buf;
break;
}
}
if (!sel)
continue; /* Did not find a match. Process the next input. */
}
if (inpdata->inp.len && !(inpdata->inpmode & INP_NOECHO))
LClearArea(flayer, inpdata->inpstringlen, INPUTLINE,
inpdata->inpstringlen + inpdata->inp.len - 1, INPUTLINE, 0, 0);
if ((prev || search) && !inpdata->inp.next)
inphist = inpdata->inp;
memmove(&inpdata->inp, sel, sizeof(struct inpline));
if (pos != -1)
inpdata->inp.pos = pos;
if (inpdata->inp.len > inpdata->inpmaxlen)
inpdata->inp.len = inpdata->inpmaxlen;
if (inpdata->inp.pos > inpdata->inp.len)
inpdata->inp.pos = inpdata->inp.len;
x = inpdata->inpstringlen;
p = inpdata->inp.buf;
if (!(inpdata->inpmode & INP_NOECHO)) {
while (p < inpdata->inp.buf + inpdata->inp.len) {
mc.image = *p++;
LPutChar(flayer, &mc, x++, INPUTLINE);
}
}
x = inpdata->inpstringlen + inpdata->inp.pos;
LGotoPos(flayer, x, INPUTLINE);
}
else if (ch == '\003' || ch == '\007' || ch == '\033' || ch == '\000' || ch == '\n' || ch == '\r') {
if (ch != '\n' && ch != '\r')
inpdata->inp.len = 0;
inpdata->inp.buf[inpdata->inp.len] = 0;
if (inpdata->inp.len && !(inpdata->inpmode & (INP_NOECHO | INP_RAW))) {
struct inpline *store;
/* Look for a duplicate first */
for (store = inphist.prev; store; store = store->prev) {
if (strcmp(store->buf, inpdata->inp.buf) == 0) {
if (store->next)
store->next->prev = store->prev;
if (store->prev)
store->prev->next = store->next;
store->pos = inpdata->inp.pos;
break;
}
}
if (!store) {
store = malloc(sizeof(struct inpline));
memmove(store, &inpdata->inp, sizeof(struct inpline));
}
store->next = &inphist;
store->prev = inphist.prev;
if (inphist.prev)
inphist.prev->next = store;
inphist.prev = store;
}
flayer->l_data = NULL; /* so inpdata does not get freed */
InpAbort(); /* redisplays... */
*ppbuf = pbuf;
*plen = len;
display = inpdisplay;
if ((inpdata->inpmode & INP_RAW) == 0)
(*inpdata->inpfinfunc) (inpdata->inp.buf, inpdata->inp.len, inpdata->priv);
else
(*inpdata->inpfinfunc) (pbuf - 1, 0, inpdata->priv);
if (inpdata->search)
free(inpdata->search);
free(inpdata);
return;
} else {
/* The user was searching, and then pressed some non-control input. So reset
* the search string. */
RESET_SEARCH;
}
}
if (!(inpdata->inpmode & INP_RAW)) {
flayer->l_x = inpdata->inpstringlen + (inpdata->inpmode & INP_NOECHO ? 0 : inpdata->inp.pos);
flayer->l_y = INPUTLINE;
}
*ppbuf = pbuf;
*plen = len;
}
static void InpAbort(void)
{
LAY_CALL_UP(LayRedisplayLine(INPUTLINE, 0, flayer->l_width - 1, 0));
ExitOverlayPage();
}
static void InpRedisplayLine(int y, int xs, int xe, int isblank)
{
int q, r, s, l, v;
struct inpdata *inpdata;
inpdata = (struct inpdata *)flayer->l_data;
if (y != INPUTLINE) {
LAY_CALL_UP(LayRedisplayLine(y, xs, xe, isblank));
return;
}
inpdata->inp.buf[inpdata->inp.len] = 0;
q = xs;
v = xe - xs + 1;
s = 0;
r = inpdata->inpstringlen;
if (v > 0 && q < r) {
l = v;
if (l > r - q)
l = r - q;
LPutStr(flayer, inpdata->inpstring + q - s, l, &mchar_so, q, y);
q += l;
v -= l;
}
s = r;
r += inpdata->inp.len;
if (!(inpdata->inpmode & INP_NOECHO) && v > 0 && q < r) {
l = v;
if (l > r - q)
l = r - q;
LPutStr(flayer, inpdata->inp.buf + q - s, l, &mchar_so, q, y);
q += l;
v -= l;
}
r = flayer->l_width;
if (!isblank && v > 0 && q < r) {
l = v;
if (l > r - q)
l = r - q;
LClearArea(flayer, q, y, q + l - 1, y, 0, 0);
}
}