/* $Id$ */
#include "bbs.h"
/*
* visio.c
* piaip's new implementation of visio
* (visio: virtual screen input output, the name from Maple3)
*
* This is not the original visio.c from Maple3.
* We just borrowed its file name and few API names/prototypes
* then re-implemented everything from scratch :)
*
* We will try to keep the API behavior similiar (to help porting)
* but won't stick to it.
* Maybe at the end only 'vmsg' and 'vmsgf' will still be compatible....
*
* m3 visio = (ptt) visio+screen/term.
*
* Author: Hung-Te Lin (piaip), April 2008.
*
* Copyright (c) 2008 Hung-Te Lin <piaip@csie.ntu.edu.tw>
* All rights reserved.
*
* Distributed under BSD license (GPL compatible).
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* To add API here, please...
* (1) name the API in prefix of 'v'.
* (2) use only screen.c APIs.
* (3) take care of wide screen and DBCS.
* (4) utilize the colos in visio.h, and name asa VCLR_* (visio color)
*/
// ---- DEFINITION ---------------------------------------------------
#define MAX_COL (t_columns-1)
#define SAFE_MAX_COL (MAX_COL-1)
#define VBUFLEN (ANSILINELEN)
// this is a special strlen to speed up processing.
// warning: x MUST be #define x "msg".
// otherwise you need to use real strlen.
#define MACROSTRLEN(x) (sizeof(x)-1)
// ---- UTILITIES ----------------------------------------------------
inline void
outnc(int n, unsigned char c)
{
while (n-- > 0)
outc(c);
}
inline void
nblank(int n)
{
outnc(n, ' ');
}
static inline void
fillns(int n, const char *s)
{
while (n > 0 && *s)
outc(*s++), n--;
if (n > 0)
outnc(n, ' ');
}
static inline void
fillns_ansi(int n, const char *s)
{
int d = strat_ansi(n, s);
if (d < 0) {
outs(s); nblank(-d);
} else {
outns(s, d);
}
}
// ---- VREF API --------------------------------------------------
/**
* vscr_save(): 傳回目前畫面的備份物件。
*/
VREFSCR
vscr_save(void)
{
// TODO optimize memory allocation someday.
screen_backup_t *o = (screen_backup_t*)malloc(sizeof(screen_backup_t));
assert(o);
scr_dump(o);
return o;
}
/**
* vscr_restore(obj): 使用並刪除畫面的備份物件。
*/
void
vscr_restore(VREFSCR obj)
{
screen_backup_t *o = (screen_backup_t*)obj;
if (o)
{
scr_restore(o);
memset(o, 0, sizeof(screen_backup_t));
free(o);
}
}
/**
* vcur_save(): 傳回目前游標的備份物件。
*/
VREFCUR
vcur_save(void)
{
// XXX 偷懶不 new object 了, pointer 夠大
int y, x;
VREFCUR v;
getyx(&y, &x);
v = ((unsigned short)y << 16) | (unsigned short)x;
return v;
}
/**
* vcur_restore(obj): 使用並刪除游標的備份物件。
*/
void
vcur_restore(VREFCUR o)
{
int y, x;
y = (unsigned int)(o);
x = (unsigned short)(y & 0xFFFF);
y = (unsigned short)(y >> 16);
move(y, x);
}
// ---- LOW LEVEL API -----------------------------------------------
/**
* prints(fmt, ...): 使用 outs/outc 輸出並格式化字串。
*/
void
prints(const char *fmt,...)
{
va_list args;
char buff[VBUFLEN];
va_start(args, fmt);
vsnprintf(buff, sizeof(buff), fmt, args);
va_end(args);
outs(buff);
}
/**
* mvprints(int y, int x, fmt, ...): 使用 mvouts 輸出並格式化字串。
*/
void
mvprints(int y, int x, const char *fmt, ...)
{
va_list args;
char buff[VBUFLEN];
va_start(args, fmt);
vsnprintf(buff, sizeof(buff), fmt, args);
va_end(args);
mvouts(x, y, buff);
}
/**
* mvouts(y, x, str): = mvaddstr
*/
void
mvouts(int y, int x, const char *str)
{
move(y, x);
clrtoeol();
SOLVE_ANSI_CACHE();
outs(str);
}
/**
* vfill(n, flags, s): 印出並填滿 n 個字元的空間
*
* @param n space to occupy
* @param flags VFILL_* parameters
* @param s string to display
*/
void
vfill(int n, int flags, const char *s)
{
// warning: flag determination must take care of default values.
char has_ansi = ((flags & VFILL_HAS_ANSI) || (*s == ESC_CHR));
char has_border = !(flags & VFILL_NO_BORDER);
if (n < 1)
return;
// quick return
if (!*s)
{
nblank(n);
return;
}
// calculate border size (always draw because n > 0)
if (has_border)
n--;
if (n > 0)
{
if (flags & VFILL_RIGHT_ALIGN)
{
// right-align
int l = has_ansi ? strlen_noansi(s) : strlen(s);
if (l >= n) // '=' prevents blanks
l = n;
else {
nblank(n - l);
n = l;
}
// leave the task to left-align
}
// left-align
if (has_ansi)
fillns_ansi(n, s);
else
fillns(n, s);
}
// print border if required
if (has_border)
outc(' ');
// close fill.
if (has_ansi)
outs(ANSI_RESET);
}
/**
* vfill(n, flags, fmt, ...): 使用 vfill 輸出並格式化字串。
*
* @param n space to occupy
* @param flags VFILL_* parameters
* @param fmt string to display
*/
void
vfillf(int n, int flags, const char *s, ...)
{
va_list args;
char buff[VBUFLEN];
va_start(args, s);
vsnprintf(buff, sizeof(buff), s, args);
va_end(args);
vfill(n, flags, s);
}
/**
* vpad(n, pattern): 填滿 n 個字元 (使用的格式為 pattern)
*
* @param n 要填滿的字元數 (無法填滿時會使用空白填補)
* @param pattern 填充用的字串
*/
inline void
vpad(int n, const char *pattern)
{
int len = strlen(pattern);
// assert(len > 0);
while (n >= len)
{
outs(pattern);
n -= len;
}
if (n) nblank(n);
}
/**
* vgety(): 取得目前所在位置的行數
*
* 考慮到 ANSI 系統,getyx() 較為少用且危險。
* vgety() 安全而明確。
*/
inline int
vgety(void)
{
int y, x;
getyx(&y, &x);
return y;
}
// ---- HIGH LEVEL API -----------------------------------------------
/**
* vshowmsg(s): 在底部印出指定訊息或單純的暫停訊息
*
* @param s 指定訊息。 NULL: 任意鍵繼續。 s: 若有 \t 則後面字串靠右 (若無則顯示任意鍵)
*/
void
vshowmsg(const char *msg)
{
int w = SAFE_MAX_COL;
move(b_lines, 0); clrtoeol();
if (!msg)
{
// print default message in middle
outs(VCLR_PAUSE_PAD);
outc(' '); // initial one space
// VMSG_PAUSE MUST BE A #define STRING.
w -= MACROSTRLEN(VMSG_PAUSE); // strlen_noansi(VMSG_PAUSE);
w--; // initial space
vpad(w/2, VMSG_PAUSE_PAD);
outs(VCLR_PAUSE);
outs(VMSG_PAUSE);
outs(VCLR_PAUSE_PAD);
vpad(w - w/2, VMSG_PAUSE_PAD);
} else {
// print in left, with floating (if \t exists)
char *pfloat = strchr(msg, '\t');
int szfloat = 0;
int nmsg = 0;
// print prefix
w -= MACROSTRLEN(VMSG_MSG_PREFIX); // strlen_noansi(VMSG_MSG_PREFIX);
outs(VCLR_MSG);
outs(VMSG_MSG_PREFIX);
// if have float, calculate float size
if (pfloat) {
nmsg = pfloat - msg;
w -= strlen_noansi(msg) -1; // -1 for \t
pfloat ++; // skip \t
szfloat = strlen_noansi(pfloat);
} else {
pfloat = VMSG_MSG_FLOAT;
szfloat = MACROSTRLEN(VMSG_MSG_FLOAT); // strlen_noansi()
w -= strlen_noansi(msg) + szfloat;
}
// calculate if we can display float
if (w < 0)
{
w += szfloat;
szfloat = 0;
}
// print msg body
if (nmsg)
outns(msg, nmsg);
else
outs(msg);
// print padding for floats
if (w > 0)
nblank(w);
// able to print float?
if (szfloat)
{
outs(VCLR_MSG_FLOAT);
outs(pfloat);
}
}
// safe blank
outs(" " ANSI_RESET);
}
/**
* vans(s): 在底部印出訊息與小輸入欄,並傳回使用者的輸入(轉為小寫)。
*
* @param s 指定訊息,見 vshowmsg
*/
int
vans(const char *msg)
{
char buf[3];
move(b_lines, 0);
clrtoeol(); SOLVE_ANSI_CACHE();
outs(msg);
vgets(buf, sizeof(buf), VGET_LOWERCASE);
return (unsigned char)buf[0];
}
/**
* vansf(s, ...): 在底部印出訊息與小輸入欄,並傳回使用者的輸入(轉為小寫)。
*
* @param s 指定訊息,見 vshowmsg
*/
int
vansf(const char *fmt, ...)
{
char msg[VBUFLEN];
va_list ap;
va_start(ap, fmt);
vsnprintf(msg, sizeof(msg), fmt, ap);
va_end(ap);
return vans(msg);
}
/**
* vmsg(s): 在底部印出指定訊息或單純的暫停訊息,並傳回使用者的按鍵。
*
* @param s 指定訊息,見 vshowmsg
*/
int
vmsg(const char *msg)
{
int i = 0;
vshowmsg(msg);
// wait for key
do {
i = igetch();
} while( i == 0 );
// clear message bar
move(b_lines, 0);
clrtoeol();
return i;
}
/**
* vmsgf(s, ...): 格式化輸出暫停訊息並呼叫 vmsg)。
*
* @param s 格式化的訊息
*/
int
vmsgf(const char *fmt,...)
{
char msg[VBUFLEN];
va_list ap;
va_start(ap, fmt);
vsnprintf(msg, sizeof(msg), fmt, ap);
va_end(ap);
return vmsg(msg);
}
/**
* vbarf(s, ...): 格式化輸出左右對齊的字串 (MAX_COL)
*
* @param s 格式化字串 (\t 後的內容會對齊右端)
*/
void
vbarf(const char *s, ...)
{
char msg[VBUFLEN], *s2;
va_list ap;
va_start(ap, s);
vsnprintf(msg, sizeof(msg), s, ap);
va_end(ap);
s2 = strchr(msg, '\t');
if (s2) *s2++ = 0;
else s2 = "";
return vbarlr(msg, s2);
}
/**
* vbarlr(l, r): 左右對齊畫滿螢幕 (MAX_COL)
* 注意: 目前的實作自動認定游標已在行開頭。
*
* @param l 靠左對齊的字串 (可含 ANSI 碼)
* @param r 靠右對齊的字串 (可含 ANSI 碼,後面不會補空白)
*/
void
vbarlr(const char *l, const char *r)
{
// TODO strlen_noansi 跑兩次... 其實 l 可以邊 output 邊算。
int szl = strlen_noansi(l),
szr = strlen_noansi(r);
// assume we are already in (y, 0)
clrtoeol();
outs(l);
szl = MAX_COL - szl;
if (szl > szr)
{
nblank(szl - szr);
szl = szr;
}
if (szl == szr)
outs(r);
else if (szl > 0)
nblank(szl);
outs(ANSI_RESET);
}
void
vs_rectangle_simple(int l, int t, int r, int b)
{
int ol = l;
assert( l + 4 <= r &&
t + 2 <= b);
// draw top line
move(t++, ol); l = ol+2;
outs("┌");
while (l < r-2) { outs("─"); l+= 2; }
outs("┐");
if (l+2 < r) outs(" ");
while (t < b)
{
move(t++, ol); l = ol+2;
outs("│");
// while (l < r-2) { outs(" "); l+= 2; }
l += (r-l-1)/2*2;
move_ansi(t-1, l);
outs("│");
if (l+2 < r) outs(" ");
}
// draw bottom line
move(t++, ol); l = ol+2;
outs("└");
while (l < r-2) { outs("─"); l+= 2; }
outs("┘");
if (l+2 < r) outs(" ");
}
// ---- THEMED FORMATTING OUTPUT -------------------------------------
/**
* vs_header(title, mid, right): 清空螢幕並輸出完整標題 (MAX_COL)
*
* @param title: 靠左的主要標題,不會被切斷。
* @param mid: 置中說明,可被切齊。
* @param right: 靠右的說明,空間夠才會顯示。
*/
void
vs_header(const char *title, const char *mid, const char *right)
{
int w = MAX_COL;
int szmid = mid ? strlen(mid) : 0;
int szright = right ? strlen(right) : 0;
clear();
outs(VCLR_HEADER);
if (title)
{
outs(VMSG_HDR_PREFIX);
outs(title);
outs(VMSG_HDR_POSTFIX);
w -= MACROSTRLEN(VMSG_HDR_PREFIX) + MACROSTRLEN(VMSG_HDR_POSTFIX);
w -= strlen(title);
}
// determine if we can display right message, and
// if if we need to truncate mid.
if (szmid + szright > w)
szright = 0;
if (szmid >= w)
szmid = w;
else {
int l = (MAX_COL-szmid)/2;
l -= (MAX_COL-w);
if (l > 0)
nblank(l), w -= l;
}
if (szmid) {
outs(VCLR_HEADER_MID);
outns(mid, szmid);
outs(VCLR_HEADER);
}
nblank(w - szmid);
if (szright) {
outs(VCLR_HEADER_RIGHT);
outs(right);
}
outs(ANSI_RESET "\n");
}
/**
* vs_hdr(title): 清空螢幕並輸出簡易的標題
*
* @param title
*/
void
vs_hdr(const char *title)
{
clear();
outs(VCLR_HDR VMSG_HDR_PREFIX);
outs(title);
outs(VMSG_HDR_POSTFIX ANSI_RESET "\n");
}
/**
* vs_footer(caption, msg): 在螢幕底部印出格式化的 caption msg (不可含 ANSI 碼)
*
* @param caption 左邊的分類字串
* @param msg 訊息字串, \t 後文字靠右、最後面會自動留一個空白。
*/
void
vs_footer(const char *caption, const char *msg)
{
int i = 0;
move(b_lines, 0); clrtoeol();
if (caption)
{
outs(VCLR_FOOTER_CAPTION);
outs(caption);
i += (*caption == ESC_CHR) ?
strlen_noansi(caption) :
strlen(caption);
}
if (!msg) msg = "";
outs(VCLR_FOOTER);
while (*msg && i < SAFE_MAX_COL)
{
if (*msg == '(')
{
outs(VCLR_FOOTER_QUOTE);
}
else if (*msg == '\t')
{
// if we don't have enough space, ignore whole.
int l = strlen(++msg);
if (i + l > SAFE_MAX_COL) break;
l = SAFE_MAX_COL - l - i;
nblank(l);
i += l;
continue;
}
outc(*msg); i++;
if (*msg == ')')
outs(VCLR_FOOTER);
msg ++;
}
nblank(SAFE_MAX_COL-i);
outc(' ');
outs(ANSI_RESET);
}
/**
* vs_cols_layout(cols, ws, n): 依據 cols (大小 n) 的定義計算適合的行寬於 ws
*/
void
vs_cols_layout(const VCOL *cols, VCOLW *ws, int n)
{
int i, tw;
VCOLPRI pri1 = cols[0].pri;
memset(ws, 0, sizeof(VCOLW) * n);
// first run, calculate minimal size
for (i = 0, tw = 0; i < n; i++)
{
// drop any trailing if required
if (tw + cols[i].minw > MAX_COL)
break;
ws[i] = cols[i].minw;
tw += ws[i];
}
if (tw < MAX_COL) {
// calculate highest priorities
// (pri1 already set to col[0].pri)
for (i = 1; i < n; i++)
{
if (cols[i].pri > pri1)
pri1 = cols[i].pri;
}
}
// try to iterate through all.
while (tw < MAX_COL) {
char run = 0;
// also update pri2 here for next run.
VCOLPRI pri2 = cols[0].pri;
for (i = 0; i < n; i++)
{
// if reach max, skip.
if (ws[i] >= cols[i].maxw)
continue;
// lower priority, update pri2 and skip.
if (cols[i].pri < pri1)
{
if (cols[i].pri > pri2)
pri2 = cols[i].pri;
continue;
}
// now increase fields
ws[i] ++;
if (++tw >= MAX_COL) break;
run ++;
}
// if no more fields...
if (!run) {
if (pri1 <= pri2) // no more priorities
break;
pri1 = pri2; // try lower priority.
}
}
}
/**
* vs_cols: 依照已經算好的欄位大小進行輸出
*/
void
vs_cols(const VCOL *cols, const VCOLW *ws, int n, ...)
{
int i = 0, w = 0;
char *s = NULL;
va_list ap;
va_start(ap, n);
for (i = 0; i < n; i++, cols++, ws++)
{
int flags = 0;
s = va_arg(ap, char*);
// quick check input.
if (!s)
{
s = "";
}
w = *ws;
if (cols->attr)
outs(cols->attr);
// build vfill flag
if (cols->flags.right_align) flags |= VFILL_RIGHT_ALIGN;
if (cols->flags.usewhole) flags |= VFILL_NO_BORDER;
vfill(w, flags, s);
if (cols->attr)
outs(ANSI_RESET);
}
va_end(ap);
// end line
outs(ANSI_RESET "\n");
}
////////////////////////////////////////////////////////////////////////
// DBCS Aware Helpers
////////////////////////////////////////////////////////////////////////
#ifdef DBCSAWARE
# define CHKDBCSTRAIL(_buf,_i) (ISDBCSAWARE() && DBCS_Status(_buf, _i) == DBCS_TRAILING)
#else // !DBCSAWARE
# define CHKDBCSTRAIL(buf,i) (0)
#endif // !DBCSAWARE
////////////////////////////////////////////////////////////////////////
// History Helpers
////////////////////////////////////////////////////////////////////////
//
#define IH_MAX_ENTRIES (12) // buffer size = approx. 1k
#define IH_MIN_SIZE (2) // only keep string >= 2 bytes
typedef struct {
int icurr; // current retrival pointer
int iappend; // new location to append
char buf[IH_MAX_ENTRIES][STRLEN];
} InputHistory;
static InputHistory ih; // everything intialized to zero.
int
InputHistoryExists(const char *s)
{
int i = 0;
for (i = 0; i < IH_MAX_ENTRIES; i++)
if (strcmp(s, ih.buf[i]) == 0)
return i+1;
return 0;
}
int
InputHistoryAdd(const char *s)
{
int i = 0;
int l = strlen(s);
if (l < IH_MIN_SIZE)
return 0;
i = InputHistoryExists(s);
if (i > 0) // found
{
i--; // i points to valid index
assert(i < IH_MAX_ENTRIES);
// change order: just delete it.
ih.buf[i][0] = 0;
}
// now append s.
strlcpy(ih.buf[ih.iappend], s, sizeof(ih.buf[ih.iappend]));
ih.iappend ++;
ih.iappend %= IH_MAX_ENTRIES;
ih.icurr = ih.iappend;
return 1;
}
static void
InputHistoryDelta(char *s, int sz, int d)
{
int i, xcurr = 0;
for (i = 1; i <= IH_MAX_ENTRIES; i++)
{
xcurr = (ih.icurr+ IH_MAX_ENTRIES + d*i)%IH_MAX_ENTRIES;
if (ih.buf[xcurr][0])
{
ih.icurr = xcurr;
// copy buffer
strlcpy(s, ih.buf[ih.icurr], sz);
// DBCS safe
i = strlen(s);
if (DBCS_Status(s, i) == DBCS_TRAILING)
s[i-1] = 0;
break;
}
}
}
void
InputHistoryPrev(char *s, int sz)
{
InputHistoryDelta(s, sz, -1);
}
void
InputHistoryNext(char *s, int sz)
{
InputHistoryDelta(s, sz, +1);
}
////////////////////////////////////////////////////////////////////////
// vget*: mini editbox
////////////////////////////////////////////////////////////////////////
static inline int
_vgetcbhandler(VGET_FCALLBACK cbptr, int *pabort, int c, VGET_RUNTIME *prt, void *instance)
{
if (!cbptr)
return 0;
switch(cbptr(c, prt, instance))
{
case VGETCB_NONE:
assert( prt->icurr >= 0 && prt->icurr <= prt->iend && prt->iend <= prt->len );
return 0;
case VGETCB_NEXT:
assert( prt->icurr >= 0 && prt->icurr <= prt->iend && prt->iend <= prt->len );
return 1;
case VGETCB_END:
assert( prt->icurr >= 0 && prt->icurr <= prt->iend && prt->iend <= prt->len );
*pabort = 1;
return 1;
case VGETCB_ABORT:
assert( prt->icurr >= 0 && prt->icurr <= prt->iend && prt->iend <= prt->len );
*pabort = 1;
prt->icurr = 0;
prt->iend = 0;
prt->buf[0] = 0;
return 1;
}
assert(0); // shall never reach here
return 0;
}
int
vgetstring(char *_buf, int len, int flags, const char *defstr, const VGET_CALLBACKS *pcbs, void *instance)
{
// rt.iend points to NUL address, and
// rt.icurr points to cursor.
int line, col;
int abort = 0;
int c;
char ismsgline = 0;
// callback
VGET_CALLBACKS cb = {NULL};
// always use internal buffer to prevent temporary input issue.
char buf[STRLEN] = ""; // zero whole.
// runtime structure
VGET_RUNTIME rt = { buf, len > STRLEN ? STRLEN : len };
// it is wrong to design input with larger buffer
// than STRLEN. Although we support large screen,
// inputting huge line will just make troubles...
if (len > STRLEN) len = STRLEN;
assert(len <= sizeof(buf) && len >= 2);
// memset(buf, 0, len);
if (defstr && *defstr)
{
strlcpy(buf, defstr, len);
strip_ansi(buf, buf, STRIP_ALL); // safer...
rt.icurr = rt.iend = strlen(buf);
}
// setup callbacks
if (pcbs)
cb = *pcbs;
getyx(&line, &col); // now (line,col) is the beginning of our new fields.
// XXX be compatible with traditional...
if (line == b_lines - msg_occupied)
ismsgline = 1;
if (ismsgline)
msg_occupied ++;
// main loop
while (!abort)
{
if (!(flags & VGET_NOECHO))
{
// print current buffer
move(line, col);
clrtoeol();
SOLVE_ANSI_CACHE();
if (!(flags & VGET_TRANSPARENT))
outs(VCLR_INPUT_FIELD); // change color to prompt fields
vfill(len, 0, buf);
if (!(flags & VGET_TRANSPARENT))
outs(ANSI_RESET);
// move to cursor position
move(line, col+rt.icurr);
} else {
// to simulate the "clrtoeol" behavior...
// XXX make this call only once? or not?
clrtoeol();
}
c = vkey();
// callback 1: peek
if (_vgetcbhandler(cb.peek, &abort, c, &rt, instance))
continue;
// standard key bindings
switch(c) {
// history navigation
case KEY_DOWN: case Ctrl('N'):
c = KEY_DOWN;
// let UP do the magic.
case KEY_UP: case Ctrl('P'):
if ((flags & VGET_NOECHO) ||
(flags & VGET_DIGITS))
{
bell();
continue;
}
// NOECHO is already checked...
if (!InputHistoryExists(buf))
InputHistoryAdd(buf);
if (c == KEY_DOWN)
InputHistoryNext(buf, len);
else
InputHistoryPrev(buf, len);
rt.icurr = rt.iend = strlen(buf);
break;
// exiting keys
case KEY_ENTER:
abort = 1;
break;
case Ctrl('C'):
rt.icurr = rt.iend = 0;
buf[0] = 0;
buf[1] = c;
abort = 1;
break;
// standard navigation
case KEY_HOME: case Ctrl('A'):
rt.icurr = 0;
break;
case KEY_END: case Ctrl('E'):
rt.icurr = rt.iend;
break;
case KEY_LEFT: case Ctrl('B'):
if (rt.icurr > 0)
rt.icurr--;
else
bell();
if (rt.icurr > 0 && CHKDBCSTRAIL(buf, rt.icurr))
rt.icurr--;
break;
case KEY_RIGHT: case Ctrl('F'):
if (rt.icurr < rt.iend)
rt.icurr++;
else
bell();
if (rt.icurr < rt.iend && CHKDBCSTRAIL(buf, rt.icurr))
rt.icurr++;
break;
// editing keys
case KEY_DEL: case Ctrl('D'):
if (rt.icurr+1 < rt.iend && CHKDBCSTRAIL(buf, rt.icurr+1)) {
// kill next one character.
memmove(buf+rt.icurr, buf+rt.icurr+1, rt.iend-rt.icurr);
rt.iend--;
}
if (rt.icurr < rt.iend) {
// kill next one character.
memmove(buf+rt.icurr, buf+rt.icurr+1, rt.iend-rt.icurr);
rt.iend--;
}
break;
case KEY_BS:
if (rt.icurr > 0) {
// kill previous one charracter.
memmove(buf+rt.icurr-1, buf+rt.icurr, rt.iend-rt.icurr+1);
rt.icurr--; rt.iend--;
} else
bell();
if (rt.icurr > 0 && CHKDBCSTRAIL(buf, rt.icurr)) {
// kill previous one charracter.
memmove(buf+rt.icurr-1, buf+rt.icurr, rt.iend-rt.icurr+1);
rt.icurr--; rt.iend--;
}
break;
case Ctrl('Y'):
rt.icurr = 0;
// reuse Ctrl-K code
case Ctrl('K'):
rt.iend = rt.icurr;
buf[rt.iend] = 0;
break;
// defaults
default:
// content filter
if (c < ' ' || c >= 0xFF)
{
bell(); continue;
}
if ((flags & VGET_DIGITS) &&
( !isascii(c) || !isdigit(c)))
{
bell(); continue;
}
if (flags & VGET_LOWERCASE)
{
if (!isascii(c))
{
bell(); continue;
}
c = tolower(c);
}
if (flags & VGET_ASCII_ONLY)
{
if (!isprint(c))
{
bell(); continue;
}
}
// size check
if(rt.iend+1 >= len)
{
bell(); continue;
}
// prevent incomplete DBCS
if (c > 0x80 && num_in_buf() &&
len - rt.iend < 3) // we need 3 for DBCS+NUL.
{
drop_input();
bell();
continue;
}
// callback 2: data
if (_vgetcbhandler(cb.data, &abort, c, &rt, instance))
continue;
// size check again, due to data callback.
if(rt.iend+1 >= len)
{
bell(); continue;
}
// add one character.
memmove(buf+rt.icurr+1, buf+rt.icurr, rt.iend-rt.icurr+1);
buf[rt.icurr++] = c;
rt.iend++;
// callback 3: post
if (_vgetcbhandler(cb.post, &abort, c, &rt, instance))
continue;
break;
}
}
assert(rt.iend >= 0 && rt.iend < len);
buf[rt.iend] = 0;
// final filtering
if (rt.iend && (flags & VGET_LOWERCASE))
buf[0] = tolower(buf[0]);
// save the history except password mode
if (buf[0] && !(flags & VGET_NOECHO))
InputHistoryAdd(buf);
// copy buffer!
memcpy(_buf, buf, len);
// XXX update screen display
if (ismsgline)
msg_occupied --;
/* because some code then outs so change new line.*/
move(line+1, 0);
return rt.iend;
}
int
vgets(char *buf, int len, int flags)
{
return vgetstr(buf, len, flags, "");
}
int
vgetstr(char *buf, int len, int flags, const char *defstr)
{
return vgetstring(buf, len, flags, defstr, NULL, NULL);
}