/*************************************************************************
* TinyFugue - programmable mud client
* Copyright (C) 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2002, 2003, 2004, 2005, 2006-2007 Ken Keys
*
* TinyFugue (aka "tf") is protected under the terms of the GNU
* General Public License. See the file "COPYING" for details.
************************************************************************/
static const char RCSid[] = "$Id: output.c,v 35004.242 2007/01/14 00:44:19 kkeys Exp $";
/*****************************************************************
* Fugue output handling
*
* Written by Ken Keys (Hawkeye).
* Handles all screen-related phenomena.
*****************************************************************/
#define TERM_vt100 1
#define TERM_vt220 2
#define TERM_ansi 3
#include "tfconfig.h"
#include "port.h"
#include "tf.h"
#include "util.h"
#include "pattern.h"
#include "search.h"
#include "tfio.h"
#include "socket.h" /* fgprompt(), fgname() */
#include "output.h"
#include "attr.h"
#include "macro.h" /* add_ibind(), rebind_key_macros() */
#include "tty.h" /* init_tty(), get_window_wize() */
#include "variable.h"
#include "expand.h" /* current_command */
#include "parse.h" /* expr_value_safe() */
#include "keyboard.h" /* keyboard_pos */
#include "cmdlist.h"
#ifdef EMXANSI
# define INCL_VIO
# include <os2.h>
#endif
/* Terminal codes and capabilities.
* Visual mode requires at least clear_screen and cursor_address. The other
* codes are good to have, but are not strictly necessary.
* Scrolling in visual mode requires set_scroll_region (preferred), or
* insert_line and delete_line (may appear jumpy), or EMXANSI.
* Fast insert in visual mode requires insert_start and insert_end (preferred),
* or insert_char.
* Fast delete in visual mode requires delete_char.
* Attributes require the appropriate attribute code and attr_off; but if
* attr_off is not defined, underline and standout (replacement for bold)
* can still be used if underline_off and standout_off are defined.
*/
#define DEFAULT_LINES 24
#define DEFAULT_COLUMNS 80
#if HARDCODE
# define origin 1 /* top left corner is (1,1) */
# if HARDCODE == TERM_vt100
# define TERMCODE(id, vt100, vt220, ansi) static const char *(id) = (vt100);
# elif HARDCODE == TERM_vt220
# define TERMCODE(id, vt100, vt220, ansi) static const char *(id) = (vt220);
# elif HARDCODE == TERM_ansi
# define TERMCODE(id, vt100, vt220, ansi) static const char *(id) = (ansi);
# endif
#else /* !HARDCODE */
# define origin 0 /* top left corner is (0,0) */
# define TERMCODE(id, vt100, vt220, ansi) static const char *(id) = NULL;
#endif /* HARDCODE */
/* vt100 vt220 ansi */
/* ----- ----- ---- */
#ifdef __CYGWIN32__ /* "\033[J" is broken in CYGWIN32 b18. */
TERMCODE (clear_screen, NULL, NULL, NULL)
TERMCODE (clear_to_eos, NULL, NULL, NULL)
#else
TERMCODE (clear_screen, "\033[H\033[J", "\033[H\033[J", "\033[H\033[J")
TERMCODE (clear_to_eos, "\033[J", "\033[J", "\033[J")
#endif
TERMCODE (clear_to_eol, "\033[K", "\033[K", "\033[K")
TERMCODE (cursor_address, "\033[%d;%dH", "\033[%d;%dH", "\033[%d;%dH")
TERMCODE (enter_ca_mode, NULL, NULL, NULL)
TERMCODE (exit_ca_mode, NULL, NULL, NULL)
TERMCODE (set_scroll_region, "\033[%d;%dr", "\033[%d;%dr", NULL)
TERMCODE (insert_line, NULL, "\033[L", "\033[L")
TERMCODE (delete_line, NULL, "\033[M", "\033[M")
TERMCODE (delete_char, NULL, "\033[P", "\033[P")
#ifdef __CYGWIN32__ /* "\033[@" is broken in CYGWIN32 b18. */
TERMCODE (insert_char, NULL, NULL, NULL)
#else
TERMCODE (insert_char, NULL, "\033[@", "\033[@")
#endif
TERMCODE (insert_start, NULL, NULL, "\033[4h")
TERMCODE (insert_end, NULL, NULL, "\033[4l")
TERMCODE (keypad_on, "\033[?1h\033=",NULL, NULL)
TERMCODE (keypad_off, "\033[?1l\033>",NULL, NULL)
TERMCODE (bell, "\007", "\007", "\007")
TERMCODE (underline, "\033[4m", "\033[4m", "\033[4m")
TERMCODE (reverse, "\033[7m", "\033[7m", "\033[7m")
TERMCODE (flash, "\033[5m", "\033[5m", "\033[5m")
TERMCODE (dim, NULL, NULL, NULL)
TERMCODE (bold, "\033[1m", "\033[1m", "\033[1m")
TERMCODE (attr_off, "\033[m", "\033[m", "\033[m")
TERMCODE (attr_on, NULL, NULL, NULL)
/* these are only used if others are missing */
TERMCODE (underline_off, NULL, NULL, NULL)
TERMCODE (standout, NULL, NULL, NULL)
TERMCODE (standout_off, NULL, NULL, NULL)
/* end HARDCODE section */
/* If var==NULL and internal<0, the status is a constant string */
typedef struct statusfield {
char *name;
stat_id_t internal; /* index of internal status being watched */
Var *var; /* variable being watched */
Var *fmtvar; /* variable containing format expression */
Var *attrvar; /* variable containing attribute string */
int width;
int rightjust;
int column;
int row;
attr_t attrs; /* attibutes from status_fields */
attr_t vattrs; /* attibutes from status_attr_{int,var}_<name> */
} StatusField;
static Var bogusvar; /* placeholder for StatusField->var */
#define true_status_pad (status_pad && *status_pad ? *status_pad : ' ')
#define sidescroll \
(intvar(VAR_sidescroll) <= Wrap/2 ? intvar(VAR_sidescroll) : Wrap/2)
static void init_term(void);
static int fbufputc(int c);
static void bufflush(void);
static void tbufputs(const char *str);
static void tdirectputs(const char *str);
static void xy(int x, int y);
static void clr(void);
static void clear_line(void);
static void clear_input_line(void);
static void clear_lines(int start, int end);
static void clear_input_window(void);
static void setscroll(int top, int bottom);
static void scroll_input(int n);
static void ictrl_put(const char *s, int n);
static int ioutputs(const char *str, int len);
static void ioutall(int kpos);
static void attributes_off(attr_t attrs);
static void attributes_on(attr_t attrs);
static void color_on(const char *prefix, long color);
static void hwrite(conString *line, int start, int len, int indent);
static int check_more(Screen *screen);
static int next_physline(Screen *screen);
static void output_novisual(PhysLine *pl);
#ifdef SCREEN
static void output_noscroll(PhysLine *pl);
static void output_scroll(PhysLine *pl);
#endif
static void (*tp)(const char *str);
#if TERMCAP
#define tpgoto(seq,x,y) tp(tgoto(seq, (x)-1+origin, (y)-1+origin))
#else
#define tpgoto(seq,x,y) Sappendf(outbuf,seq,(y)-1+origin,(x)-1+origin)
#endif
#define ipos() xy(ix, iy)
/* Buffered output */
#define bufputStr(Str) SStringcat(outbuf, Str)
#define bufputs(s) Stringcat(outbuf, s)
#define bufputns(s, n) Stringncat(outbuf, s, n)
#define bufputc(c) Stringadd(outbuf, c)
#define bufputnc(c, n) Stringnadd(outbuf, c, n)
#ifdef EMXANSI /* OS2 */
static void crnl(int n);
#else
# if USE_SGTTY /* CRMOD is off (tty.c) */
# define crnl(count) do { bufputc('\r'); bufputnc('\n', count); } while (0)
# else /* ONLCR is on (tty.c) */
# define crnl(count) bufputnc('\n', count)
# endif
#endif
/* Others */
#define Wrap (wrapsize ? wrapsize : columns)
#define moremin 1
#define morewait 50
String status_line[][1] = { /* formatted status lines, without alert */
STATIC_BUFFER_INIT, STATIC_BUFFER_INIT, STATIC_BUFFER_INIT,
STATIC_BUFFER_INIT, STATIC_BUFFER_INIT, STATIC_BUFFER_INIT };
#define max_status_height (sizeof(status_line)/sizeof(String))
static StatusField *variable_width_field[max_status_height];
STATIC_BUFFER(outbuf); /* output buffer */
static int top_margin = -1, bottom_margin = -1; /* scroll region */
static int cx = -1, cy = -1; /* Real cursor ((-1,-1)==unknown) */
static int ox = 1, oy = 1; /* Output cursor */
static int ix, iy; /* Input cursor */
static int old_ix = -1; /* original ix before output clobbered it */
static int in_top, in_bot; /* top & bottom line # of input window */
#define out_top 1 /* top line # of output window */
static int out_bot; /* bottom line # of output window */
static int stat_top, stat_bot; /* top & bottom line # of status area */
static int istarty, iendy, iendx; /* start/end of current input line */
static conString *prompt; /* current prompt */
static attr_t have_attr = 0; /* available attributes */
static int screen_mode = -1; /* -1=unset, 0=nonvisual, 1=visual */
static int output_disabled = 1; /* is it safe to oflush()? */
static int can_have_visual = FALSE;
static int can_have_expnonvis = FALSE;
static List statusfield_list[max_status_height][1];
static int status_left[max_status_height]; /* size of status line left part */
static int status_right[max_status_height]; /* size of status line right part */
static int alert_row = 0; /* row of status area where alert appears */
static int alert_pos = 0; /* column where alert appears */
static int alert_len = 0; /* length of current alert message */
STATIC_STRING(moreprompt, "--More--", F_BOLD | F_REVERSE); /* pager prompt */
#ifndef EMXANSI
# define has_scroll_region (set_scroll_region != NULL)
#else
# define has_scroll_region (1)
#endif
#if HARDCODE
# if HARDCODE == TERM_vt100
# define KEYCODE(vt100, vt220, ansi) (vt100)
# else
# if HARDCODE == TERM_vt220
# define KEYCODE(vt100, vt220, ansi) (vt220)
# else
# if HARDCODE == TERM_ansi
# define KEYCODE(vt100, vt220, ansi) (ansi)
# endif
# endif
# endif
#else
# define KEYCODE(vt100, vt220, ansi) NULL
#endif
typedef struct Keycode {
const char *name, *capname, *code;
} Keycode;
static Keycode keycodes[] = { /* this list is sorted by tolower(name)! */
/* vt100 vt220 ansi */
/* ----- ----- ---- */
/* { "Back Tab", "kB", KEYCODE( NULL, NULL, NULL ) }, */
{ "Backspace", "kb", KEYCODE( "\010", "\010", "\010" ) },
/* { "Clear All Tabs", "ka", KEYCODE( NULL, NULL, NULL ) }, */
{ "Clear EOL", "kE", KEYCODE( NULL, NULL, NULL ) },
{ "Clear EOS", "kS", KEYCODE( NULL, NULL, NULL ) },
{ "Clear Screen", "kC", KEYCODE( NULL, NULL, NULL ) },
/* { "Clear Tab", "kt", KEYCODE( NULL, NULL, NULL ) }, */
{ "Delete", "kD", KEYCODE( NULL, "\033[3~", NULL ) },
{ "Delete Line", "kL", KEYCODE( NULL, NULL, NULL ) },
{ "Down", "kd", KEYCODE( "\033OB", "\033[B", "\033[B" ) },
/* { "End Insert", "kM", KEYCODE( NULL, NULL, NULL ) }, */
{ "F0", "k0", KEYCODE( "\033Oy", NULL, NULL ) },
{ "F1", "k1", KEYCODE( "\033OP", "\033OP", "\033[M" ) },
{ "F10", "k;", KEYCODE( NULL, NULL, NULL ) },
{ "F11", "F1", KEYCODE( NULL, NULL, NULL ) },
{ "F12", "F2", KEYCODE( NULL, NULL, NULL ) },
{ "F13", "F3", KEYCODE( NULL, NULL, NULL ) },
{ "F14", "F4", KEYCODE( NULL, NULL, NULL ) },
{ "F15", "F5", KEYCODE( NULL, NULL, NULL ) },
{ "F16", "F6", KEYCODE( NULL, NULL, NULL ) },
{ "F17", "F7", KEYCODE( NULL, NULL, NULL ) },
{ "F18", "F8", KEYCODE( NULL, NULL, NULL ) },
{ "F19", "F9", KEYCODE( NULL, NULL, NULL ) },
{ "F2", "k2", KEYCODE( "\033OQ", "\033OQ", "\033[N" ) },
{ "F3", "k3", KEYCODE( "\033OR", "\033OR", "\033[O" ) },
{ "F4", "k4", KEYCODE( "\033OS", "\033OS", "\033[P" ) },
{ "F5", "k5", KEYCODE( "\033Ot", "\033[17~", "\033[Q" ) },
{ "F6", "k6", KEYCODE( "\033Ou", "\033[18~", "\033[R" ) },
{ "F7", "k7", KEYCODE( "\033Ov", "\033[19~", "\033[S" ) },
{ "F8", "k8", KEYCODE( "\033Ol", "\033[20~", "\033[T" ) },
{ "F9", "k9", KEYCODE( "\033Ow", "\033[21~", "\033[U" ) },
{ "Home", "kh", KEYCODE( NULL, "\033[H", "\033[H" ) },
{ "Home Down", "kH", KEYCODE( NULL, NULL, NULL ) },
{ "Insert", "kI", KEYCODE( NULL, "\033[2~", "\033[L" ) },
{ "Insert Line", "kA", KEYCODE( NULL, NULL, NULL ) },
{ "KP1", "K1", KEYCODE( "\033Oq", NULL, NULL ) },
{ "KP2", "K2", KEYCODE( "\033Or", NULL, NULL ) },
{ "KP3", "K3", KEYCODE( "\033Os", NULL, NULL ) },
{ "KP4", "K4", KEYCODE( "\033Op", NULL, NULL ) },
{ "KP5", "K5", KEYCODE( "\033Oq", NULL, NULL ) },
{ "Left", "kl", KEYCODE( "\033OD", "\033[D", "\033[D" ) },
{ "PgDn", "kN", KEYCODE( NULL, "\033[6~", NULL ) },
{ "PgUp", "kP", KEYCODE( NULL, "\033[5~", NULL ) },
{ "Right", "kr", KEYCODE( "\033OC", "\033[C", "\033[C" ) },
{ "Scroll Down", "kF", KEYCODE( NULL, NULL, NULL ) },
{ "Scroll Up", "kR", KEYCODE( NULL, NULL, NULL ) },
/* { "Set Tab", "kT", KEYCODE( NULL, NULL, NULL ) }, */
{ "Up", "ku", KEYCODE( "\033OA", "\033[A", "\033[A" ) }
};
#define N_KEYCODES (sizeof(keycodes)/sizeof(Keycode))
int lines = DEFAULT_LINES;
int columns = DEFAULT_COLUMNS;
ref_type_t need_refresh = 0; /* does input need refresh? */
int need_more_refresh = 0; /* does visual more prompt need refresh? */
struct timeval alert_timeout = { 0, 0 }; /* when to clear alert */
unsigned long alert_id = 0;
struct timeval clock_update ={0,0}; /* when clock needs to be updated */
PhysLine *plpool = NULL; /* freelist of PhysLines */
#if TERMCAP
extern int tgetent(char *buf, const char *name);
extern int tgetnum(const char *id);
extern char *tgetstr(const char *id, char **area);
extern char *tgoto(const char *code, int destcol, int destline);
extern char *tparm(const char *code, ...);
extern int tputs(const char *cp, int affcnt, int (*outc)(int));
static int func_putchar(int c);
#endif
/****************************
* BUFFERED OUTPUT ROUTINES *
****************************/
static void bufflush(void)
{
int written = 0, result, n;
while (written < outbuf->len) {
n = outbuf->len - written;
if (n > 2048)
n = 2048;
result = write(STDOUT_FILENO, outbuf->data + written, n);
if (result < 0) break;
written += result;
}
Stringtrunc(outbuf, 0);
}
static int fbufputc(int c)
{
Stringadd(outbuf, c);
return c;
}
#ifdef EMXANSI
void crnl(int n)
{
int off = (cy + n) - bottom_margin;
if (off < 0 || !visual) off = 0;
bufputnc('\n', n - off);
if (off) {
bufflush();
VioScrollUp(top_margin-1, 0, bottom_margin-1, columns, off, " \x07", 0);
bufputc('\r');
}
}
#endif
void dobell(int n)
{
if (beep) {
while (n--) bufputs(bell);
bufflush();
}
}
/********************/
int change_term(Var *var)
{
fix_screen();
init_term();
redraw();
rebind_key_macros();
return 1;
}
static void init_line_numbers(void)
{
/* out_top = 1; */
out_bot = lines - isize - status_height;
stat_top = out_bot + 1;
stat_bot = out_bot + status_height;
in_top = stat_bot + 1;
in_bot = stat_bot + isize;
}
/* Initialize output data. */
void init_output(void)
{
const char *str;
int i;
tp = tbufputs;
init_tty();
/* Window size: clear defaults, then try:
* environment, ioctl TIOCGWINSZ, termcap, defaults.
*/
lines = ((str = getvar("LINES"))) ? atoi(str) : 0;
columns = ((str = getvar("COLUMNS"))) ? atoi(str) : 0;
if (lines <= 0 || columns <= 0) get_window_size();
init_line_numbers();
top_margin = 1;
bottom_margin = lines;
prompt = fgprompt();
old_ix = -1;
init_term();
for (i = 0; i < max_status_height; i++) {
init_list(statusfield_list[i]);
Stringninit(status_line[i], columns);
check_charattrs(status_line[i], columns, 0, __FILE__, __LINE__);
}
redraw();
output_disabled = 0;
}
/********************
* TERMCAP ROUTINES *
********************/
static void init_term(void)
{
#if TERMCAP
int i;
/* Termcap entries are supposed to fit in 1024 bytes. But, if a 'tc'
* field is present, some termcap libraries will just append the second
* entry to the original. Also, some overzealous versions of tset will
* also expand 'tc', so there might be *2* entries appended to the
* original. And, newer termcap databases have additional fields (e.g.,
* linux adds 'li' and 'co'), so it could get even longer. To top it all
* off, some termcap libraries don't do any length checking in tgetent().
* We should be safe with 4096.
*/
char termcap_entry[4096];
/* termcap_buffer will hold at most 1 copy of any field; 1024 is enough. */
static char termcap_buffer[1024];
char *area = termcap_buffer;
have_attr = 0;
can_have_visual = FALSE;
can_have_expnonvis = FALSE;
clear_screen = clear_to_eos = clear_to_eol = NULL;
set_scroll_region = insert_line = delete_line = NULL;
delete_char = insert_char = insert_start = insert_end = NULL;
enter_ca_mode = exit_ca_mode = cursor_address = NULL;
keypad_on = keypad_off = NULL;
standout = underline = reverse = flash = dim = bold = bell = NULL;
standout_off = underline_off = attr_off = attr_on = NULL;
{
/* Sanity check: a valid termcap entry should end in ':'. In
* particular, csh and tcsh put a limit of 1023 bytes on the TERMCAP
* variable, which may cause libpcap to give us garbage. (I really
* hate it when people blame tf for a bug in another program,
* especially when it's the evil csh or tcsh.)
*/
const char *str, *shell;
int len, is_csh;
if ((str = getvar("TERMCAP")) && (len = strlen(str)) > 0) {
is_csh = ((shell = getenv("SHELL")) && smatch("*csh", shell) == 0);
if (str[len-1] != ':') {
wprintf("unsetting invalid TERMCAP variable%s.",
is_csh ? ", which appears to have been corrupted by your "
"broken *csh shell" : "");
unsetvar(ffindglobalvar("TERMCAP"));
} else if (len == 1023 && (!getenv("TF_FORCE_TERMCAP")) && is_csh) {
wprintf("unsetting the TERMCAP environment variable because "
"it looks like it has been truncated by your broken *csh "
"shell. To force TF to use TERMCAP, restart TF with the "
"TF_FORCE_TERMCAP environment variable set.");
unsetvar(ffindglobalvar("TERMCAP"));
}
}
}
if (!TERM || !*TERM) {
wprintf("TERM undefined.");
} else if (tgetent(termcap_entry, TERM) <= 0) {
wprintf("\"%s\" terminal unsupported.", TERM);
} else {
if (columns <= 0) columns = tgetnum("co");
if (lines <= 0) lines = tgetnum("li");
clear_screen = tgetstr("cl", &area);
clear_to_eol = tgetstr("ce", &area);
clear_to_eos = tgetstr("cd", &area);
enter_ca_mode = tgetstr("ti", &area);
exit_ca_mode = tgetstr("te", &area);
cursor_address = tgetstr("cm", &area);
set_scroll_region = tgetstr("cs", &area);
delete_char = tgetstr("dc", &area);
insert_char = tgetstr("ic", &area);
insert_start = tgetstr("im", &area);
insert_end = tgetstr("ei", &area);
insert_line = tgetstr("al", &area);
delete_line = tgetstr("dl", &area);
keypad_on = tgetstr("ks", &area);
keypad_off = tgetstr("ke", &area);
bell = tgetstr("bl", &area);
underline = tgetstr("us", &area);
reverse = tgetstr("mr", &area);
flash = tgetstr("mb", &area);
dim = tgetstr("mh", &area);
bold = tgetstr("md", &area);
standout = tgetstr("so", &area);
underline_off = tgetstr("ue", &area);
standout_off = tgetstr("se", &area);
attr_off = tgetstr("me", &area);
attr_on = tgetstr("sa", &area);
if (!attr_off) {
/* can't exit all attrs, but maybe can exit underline/standout */
reverse = flash = dim = bold = NULL;
if (!underline_off) underline = NULL;
if (!standout_off) standout = NULL;
}
for (i = 0; i < N_KEYCODES; i++) {
keycodes[i].code = tgetstr(keycodes[i].capname, &area);
#if 0
fprintf(stderr, "(%2s) %-12s = %s\n",
keycodes[i].capname, keycodes[i].name,
keycodes[i].code ? ascii_to_print(keycodes[i].code)->data : "NULL");
#endif
}
if (!keypad_off) keypad_on = NULL;
if (strcmp(TERM, "xterm") == 0) {
#if 0 /* Now that tf has virtual screens, the secondary buffer is ok. */
enter_ca_mode = exit_ca_mode = NULL; /* Avoid secondary buffer. */
#endif
/* Many old "xterm" termcaps mistakenly omit "cs". */
if (!set_scroll_region)
set_scroll_region = "\033[%i%d;%dr";
}
if (strcmp(TERM, "linux") == 0) {
/* Many "linux" termcaps mistakenly omit "ks" and "ke". */
if (!keypad_on && !keypad_off) {
keypad_on = "\033[?1h\033=";
keypad_off = "\033[?1l\033>";
}
}
}
if (!bell) bell = "\007";
#ifdef EMXANSI
VioSetAnsi(1,0); /* ensure ansi-mode */
#endif /* EMXANSI */
#endif /* TERMCAP */
#if 1
/* The insert_start code in iput() is apparently buggy. Until it is
* fixed, we ignore the insert_start capability. */
insert_start = NULL;
#endif
if (!insert_end) insert_start = NULL;
if (columns <= 0) columns = DEFAULT_COLUMNS;
if (lines <= 0) lines = DEFAULT_LINES;
set_var_by_id(VAR_wrapsize, columns - 1);
ix = 1;
can_have_visual = (clear_screen || clear_to_eol) && cursor_address;
can_have_expnonvis = delete_char && cursor_address;
set_var_by_id(VAR_scroll, has_scroll_region||(insert_line&&delete_line));
set_var_by_id(VAR_keypad, !!keypad_on);
have_attr = F_BELL;
if (underline) have_attr |= F_UNDERLINE;
if (reverse) have_attr |= F_REVERSE;
if (flash) have_attr |= F_FLASH;
if (dim) have_attr |= F_DIM;
if (bold) have_attr |= F_BOLD;
if (standout) have_attr |= F_BOLD;
}
static void setscroll(int top, int bottom)
{
if (top_margin == top && bottom_margin == bottom) return; /* optimization */
#ifdef EMXANSI
bufflush();
#else
if (!set_scroll_region) return;
tpgoto(set_scroll_region, (bottom), (top));
cx = cy = -1; /* cursor position is undefined */
#endif
bottom_margin = bottom;
top_margin = top;
if (top != 1 || bottom != lines) set_refresh_pending(REF_PHYSICAL);
}
static void xy(int x, int y)
{
if (x == cx && y == cy) return; /* already there */
if (cy < 0 || cx < 0 || cx > columns) { /* direct movement */
tpgoto(cursor_address, x, y);
} else if (x == 1 && y == cy) { /* optimization */
bufputc('\r');
} else if (x == 1 && y > cy && y < cy + 5 && /* optimization... */
cy >= top_margin && y <= bottom_margin) /* if '\n' is safe */
{
/* Some broken emulators (including CYGWIN32 b18) lose
* attributes when \r\n is printed, so we print \n\r instead.
*/
bufputnc('\n', y - cy);
if (cx != 1) bufputc('\r');
} else if (y == cy && x < cx && x > cx - 7) { /* optimization */
bufputnc('\010', cx - x);
} else { /* direct movement */
tpgoto(cursor_address, x, y);
}
cx = x; cy = y;
}
static void clr(void)
{
if (clear_screen)
tp(clear_screen);
else {
clear_lines(1, lines);
xy(1, 1);
}
cx = 1; cy = 1;
}
static void clear_line(void)
{
if (cx != 1) bufputc('\r');
cx = 1;
if (clear_to_eol) {
tp(clear_to_eol);
} else {
bufputnc(' ', Wrap);
bufputc('\r');
}
}
#if TERMCAP
/* in case broken lib defines a putchar macro but not a putchar function */
/* Note: WATCOM compiler (QNX) has a function named fputchar. */
static int func_putchar(int c)
{
return putchar(c);
}
#endif
static void tdirectputs(const char *str)
{
if (str)
#if TERMCAP
tputs(str, 1, func_putchar);
#else
puts(str);
#endif
}
static void tbufputs(const char *str)
{
if (str)
#if TERMCAP
tputs(str, 1, fbufputc);
#else
bufputs(str);
#endif
}
const char *get_keycode(const char *name)
{
Keycode *keycode;
keycode = (Keycode *)bsearch(name, keycodes, N_KEYCODES,
sizeof(Keycode), cstrstructcmp);
return !keycode ? NULL : keycode->code ? keycode->code : "";
}
/*******************
* WINDOW HANDLING *
*******************/
void setup_screen(void)
{
setscroll(1, lines);
output_disabled++;
if (visual && (!can_have_visual)) {
eprintf("Visual mode is not supported on this terminal.");
set_var_by_id(VAR_visual, 0); /* XXX recursion problem?? */
} else if (visual && lines < 1/*input*/ + 1/*status*/ + 2/*output*/) {
eprintf("Screen is too small for visual mode.");
set_var_by_id(VAR_visual, 0); /* XXX recursion problem?? */
}
screen_mode = visual;
if (!visual) {
prompt = display_screen->paused ? moreprompt : fgprompt();
old_ix = -1;
#ifdef SCREEN
} else {
prompt = fgprompt();
if (isize + status_height + 2 > lines) {
if ((1 + status_height + 2) <= lines) {
set_var_by_id(VAR_isize, lines - (status_height + 2));
} else {
set_var_by_id(VAR_isize, 1);
set_var_by_id(VAR_stat_height, lines - (isize + 2));
}
}
init_line_numbers();
#if 0
outcount = out_bot - out_top + 1;
#endif
if (enter_ca_mode) tp(enter_ca_mode);
if (scroll && !(has_scroll_region || (insert_line && delete_line))) {
set_var_by_id(VAR_scroll, 0);
}
update_status_line(NULL);
ix = iendx = oy = 1;
iy = iendy = istarty = in_top;
ipos();
#endif
}
if (keypad && keypad_on)
tp(keypad_on);
set_refresh_pending(REF_LOGICAL);
output_disabled--;
}
#define interesting(pl) ((pl)->attrs & F_HWRITE || (pl)->charattrs)
/* returns true if str passes filter */
static int screen_filter(Screen *screen, conString *str)
{
if (str == textdiv_str) return visual;
return (!screen->selflush || interesting(str))
&&
(!screen->filter_enabled ||
(screen->filter_attr ? interesting(str) :
patmatch(&screen->filter_pat, str, NULL) == screen->filter_sense));
}
#define purge_old_lines(screen) \
do { \
if (screen->nlline > screen->maxlline) \
f_purge_old_lines(screen); \
} while (0) \
static void f_purge_old_lines(Screen *screen)
{
ListEntry *node;
PhysLine *pl;
/* Free old lines if over maximum and they're off the top of the screen.
* (Freeing them when they fall out of history doesn't work: a long-lived
* line from a different history could trap lines from the history
* corresponding to this screen.) */
node = screen->pline.head;
while (screen->nlline > screen->maxlline && node != screen->top) {
node = node->next;
pl = node->datum;
if (pl->start == 0) { /* beginning of a new lline */
/* free all plines (corresponding to a single lline) above node */
screen->nlline--;
while (screen->pline.head != node) {
pl = unlist(screen->pline.head, &screen->pline);
conStringfree(pl->str);
pfree(pl, plpool, str);
}
}
}
}
/* Add a line to the bottom. bot does not move if nothing below it matches
* filter. */
static int nextbot(Screen *screen)
{
PhysLine *pl;
ListEntry *bot;
int nback = screen->nback;
int passed_maxbot;
if (screen->bot) {
passed_maxbot = (screen->bot == screen->maxbot);
bot = screen->bot->next;
} else {
bot = screen->pline.head;
passed_maxbot = 1;
}
while (bot) {
pl = bot->datum;
nback--;
/* shouldn't need to recalculate visible, but there's a bug somewhere
* in maintaining visible flags in (bot,tail] after /unlimit. */
if ((pl->visible = screen_filter(screen, pl->str))) {
screen->bot = bot;
screen->nback_filtered--;
screen->nback = nback;
if (passed_maxbot) {
screen->maxbot = bot;
screen->nnew_filtered--;
screen->nnew = nback;
}
if (!screen->top)
screen->top = screen->bot;
screen->viewsize++;
if (screen->viewsize >= winlines())
screen->partialview = 0;
return 1;
}
if (bot == screen->maxbot)
passed_maxbot = 1;
bot = bot->next;
}
return 0;
}
/* Add a line to the top. top does not move if nothing above it matches
* filter. */
static int prevtop(Screen *screen)
{
PhysLine *pl;
ListEntry *prev;
/* (top == NULL && bot != NULL) happens after clear_display_screen() */
if (!screen->bot) return 0;
prev = screen->top ? screen->top->prev : screen->bot;
while (prev) {
pl = prev->datum;
/* visible flags are not maintained above top, must recalculate */
if ((pl->visible = screen_filter(screen, pl->str))) {
screen->top = prev;
screen->viewsize++;
if (screen->viewsize >= winlines())
screen->partialview = 0;
return 1;
}
/* XXX optimize for pl's that share same ll */
prev = prev->prev;
}
return 0;
}
/* Remove a line from the bottom. bot always moves. */
static int prevbot(Screen *screen)
{
ListEntry *node;
PhysLine *pl;
int viewsize_changed = 0;
if (!screen->bot) return 0;
while (screen->bot->prev) {
pl = (node = screen->bot)->datum;
/* visible flags are maintained in [top,bot], we needn't recalculate */
if (pl->visible) {
/* line being knocked off was visible */
screen->viewsize--;
screen->nback_filtered += !pl->tmp;
viewsize_changed = 1;
}
screen->bot = screen->bot->prev;
screen->nback += !pl->tmp;
if (pl->tmp) {
/* line being knocked off was temporary */
if (screen->maxbot == node)
screen->maxbot = screen->maxbot->prev;
unlist(node, &screen->pline);
conStringfree(pl->str);
pfree(pl, plpool, str);
}
/* stop if the viewsize has changed and we've found a new visible bot */
pl = screen->bot->datum;
if (viewsize_changed && pl->visible)
break;
}
return viewsize_changed;
}
/* Remove a line from the top. top always moves. */
static int nexttop(Screen *screen)
{
PhysLine *pl;
ListEntry *newtop;
int viewsize_changed = 0;
if (!screen->top) return 0;
while (screen->top->next) {
pl = screen->top->datum;
newtop = screen->top->next;
/* visible flags are maintained in [top,bot], we needn't recalculate */
if (pl->visible) {
/* line being knocked off was visible */
screen->viewsize--;
viewsize_changed = 1;
}
if (pl->tmp) {
/* line being knocked off was temporary */
unlist(screen->top, &screen->pline);
conStringfree(pl->str);
pfree(pl, plpool, str);
}
screen->top = newtop;
/* stop if the viewsize has changed and we've found a new visible top */
pl = screen->top->datum;
if (viewsize_changed && pl->visible)
break;
}
purge_old_lines(screen);
return viewsize_changed;
}
/* recalculate counters and visible flags in (bot, tail] */
static void screen_refilter_bottom(Screen *screen)
{
PhysLine *pl;
ListEntry *node;
if (screen_has_filter(screen)) {
screen->nback_filtered = 0;
screen->nnew_filtered = 0;
node = screen->pline.tail;
for ( ; node != screen->maxbot; node = node->prev) {
pl = node->datum;
if ((pl->visible = screen_filter(screen, pl->str)))
screen->nnew_filtered++;
}
screen->nback_filtered = screen->nnew_filtered;
for ( ; node != screen->bot; node = node->prev) {
pl = node->datum;
if ((pl->visible = screen_filter(screen, pl->str)))
screen->nback_filtered++;
}
} else {
screen->nback_filtered = screen->nback;
screen->nnew_filtered = screen->nnew;
}
}
static int screen_refilter(Screen *screen)
{
PhysLine *pl;
int want;
Screen oldscreen;
ListEntry *lowestvisible;
screen->needs_refilter = 0;
if (!screen->bot)
if (!(screen->bot = screen->pline.tail))
return 0;
oldscreen = *screen;
/* NB: screen->bot should stay in the same place, for when the filter
* is removed or changed. */
lowestvisible = screen->bot;
while (!screen_filter(screen, (pl = lowestvisible->datum)->str)) {
pl->visible = 0;
lowestvisible = lowestvisible->prev;
if (!lowestvisible) {
if (screen_has_filter(screen)) {
*screen = oldscreen; /* restore original state */
clear_screen_filter(screen);
screen_refilter(screen);
}
return 0;
}
}
(pl = lowestvisible->datum)->visible = 1;
/* recalculate top: start at bot and move top up until view is full */
screen->viewsize = 1;
screen->partialview = 0;
screen->top = lowestvisible;
want = winlines();
while ((screen->viewsize < want) && prevtop(screen))
/* empty loop */;
screen_refilter_bottom(screen);
return screen->viewsize;
}
int screen_has_filter(Screen *screen)
{
return screen->filter_enabled || screen->selflush;
}
void clear_screen_filter(Screen *screen)
{
screen->filter_enabled = 0;
screen->selflush = 0;
screen->needs_refilter = 1;
}
void set_screen_filter(Screen *screen, Pattern *pat, attr_t attr_flag,
int sense)
{
if (screen->filter_pat.str)
free_pattern(&screen->filter_pat);
if (pat) screen->filter_pat = *pat;
screen->filter_attr = attr_flag;
screen->filter_sense = sense;
screen->filter_enabled = 1;
screen->selflush = 0;
screen->needs_refilter = 1;
}
int enable_screen_filter(Screen *screen)
{
if (!screen->filter_pat.str && !screen->filter_attr)
return 0;
screen->filter_enabled = 1;
screen->selflush = 0;
screen->needs_refilter = 1;
return 1;
}
int winlines(void)
{
return visual ? out_bot - out_top + 1 : lines - 1;
}
/* wraplines
* Split logical line <ll> into physical lines and append them to <plines>.
*/
static int wraplines(conString *ll, List *plines, int visible)
{
PhysLine *pl;
int offset = 0, n = 0;
do {
palloc(pl, PhysLine, plpool, str, __FILE__, __LINE__);
pl->visible = visible;
pl->tmp = 0;
(pl->str = ll)->links++;
pl->start = offset;
pl->indent = wrapflag && pl->start && wrapspace < Wrap ? wrapspace : 0;
pl->len = wraplen(ll->data + offset, ll->len - offset, pl->indent);
offset += pl->len;
inlist(pl, plines, plines->tail);
n++;
} while (offset < ll->len);
return n;
}
static void rewrap(Screen *screen)
{
PhysLine *pl;
ListEntry *node;
List old_pline;
int wrapped, old_bot_visible, old_maxbot_visible;
if (!screen->bot) return;
hide_screen(screen); /* delete temp lines */
old_pline.head = screen->pline.head;
old_pline.tail = screen->pline.tail;
screen->pline.head = screen->pline.tail = NULL;
screen->nnew = screen->nnew_filtered = 0;
screen->nback = screen->nback_filtered = 0;
pl = screen->bot->datum;
old_bot_visible = pl->start + pl->len;
pl = screen->maxbot->datum;
old_maxbot_visible = pl->start + pl->len;
/* XXX possible optimization: don't rewrap [head, top) until needed.
* [top, bot] is needed for display, and (bot, tail] is needed for
* nback and nnew. */
/* rewrap llines corresponding to [head, bot] */
do {
node = old_pline.head;
pl = unlist(old_pline.head, &old_pline);
if (pl->start == 0) {
wrapped = wraplines(pl->str, &screen->pline, pl->visible);
screen->npline += wrapped;
}
conStringfree(pl->str);
pfree(pl, plpool, str);
} while (node != screen->bot);
/* recalculate bot within last lline */
screen->bot = screen->pline.tail;
while (screen->bot->prev) {
pl = screen->bot->datum;
if (pl->start == 0 || pl->start < old_bot_visible) break;
screen->bot = screen->bot->prev;
screen->nback++;
}
/* rewrap llines corresponding to (bot, maxbot] */
while (node != screen->maxbot) {
node = old_pline.head;
pl = unlist(old_pline.head, &old_pline);
if (pl->start == 0) {
wrapped = wraplines(pl->str, &screen->pline, pl->visible);
screen->npline += wrapped;
screen->nback += wrapped;
}
conStringfree(pl->str);
pfree(pl, plpool, str);
}
/* recalculate maxbot within last lline */
screen->maxbot = screen->pline.tail;
while (screen->maxbot->prev) {
pl = screen->maxbot->datum;
if (pl->start == 0 || pl->start < old_maxbot_visible) break;
screen->maxbot = screen->maxbot->prev;
screen->nnew++;
}
/* rewrap llines corresponding to (maxbot, tail] */
while (old_pline.head) {
pl = unlist(old_pline.head, &old_pline);
if (pl->start == 0) {
wrapped = wraplines(pl->str, &screen->pline, pl->visible);
screen->npline += wrapped;
screen->nback += wrapped;
screen->nnew += wrapped;
}
conStringfree(pl->str);
pfree(pl, plpool, str);
}
/* recalculate nback_filtered, nnew_filtered, viewsize, top */
/* XXX TODO: should honor screen->partialview */
screen_refilter(screen);
screen->scr_wrapflag = wrapflag;
screen->scr_wrapsize = Wrap;
screen->scr_wrapspace = wrapspace;
screen->scr_wrappunct = wrappunct;
}
int redraw_window(Screen *screen, int already_clear)
{
if (screen->needs_refilter && !screen_refilter(screen))
return 0;
if (!already_clear)
clear_lines(1, visual ? out_bot : lines);
if (screen->scr_wrapflag != wrapflag ||
screen->scr_wrapsize != Wrap ||
screen->scr_wrapspace != wrapspace ||
screen->scr_wrappunct != wrappunct)
{
rewrap(screen);
} else {
/* if terminal height decreased or textdiv was inserted */
while (screen->viewsize > winlines())
nexttop(screen);
/* if terminal height increased */
if (!screen->partialview) {
while (screen->viewsize < winlines())
if (!prevtop(screen)) break;
}
}
if (!visual) xy(1, lines);
/* viewsize may be 0 after clear_display_screen(), even if bot != NULL
* or nplines > 0 */
if (screen->viewsize) {
PhysLine *pl;
ListEntry *node;
int first;
if (visual) xy(1, out_bot - (screen->viewsize - 1));
node = screen->top;
first = 1;
while (1) {
pl = node->datum;
if (pl->visible) {
if (!first) crnl(1);
first = 0;
hwrite(pl->str, pl->start,
pl->len < Wrap - pl->indent ? pl->len : Wrap - pl->indent,
pl->indent);
}
if (node == screen->bot)
break;
node = node->next;
}
if (visual) {
cx = 1; cy = out_bot;
} else {
crnl(1);
cx = 1; cy = lines;
}
}
bufflush();
old_ix = -1;
set_refresh_pending(REF_PHYSICAL);
ox = cx;
oy = cy;
return 1;
}
int redraw(void)
{
alert_timeout = tvzero;
alert_pos = 0;
alert_len = 0;
if (visual) {
top_margin = bottom_margin = -1; /* force scroll region reset */
clr();
} else {
bufputnc('\n', lines); /* scroll region impossible */
}
setup_screen();
if (virtscreen) {
hide_screen(display_screen);
unhide_screen(display_screen);
}
return redraw_window(display_screen, 1);
}
static void clear_screen_view(Screen *screen)
{
if (screen->bot) {
screen->top = screen->bot->next;
screen->viewsize = 0;
screen->partialview = 1;
reset_outcount(screen);
}
}
int clear_display_screen(void)
{
if (!display_screen->bot) return 0;
clear_screen_view(display_screen);
return redraw_window(display_screen, 0);
}
static const char *parse_statusfield(StatusField *field, const char *s)
{
const char *t;
memset(field, 0, sizeof(*field));
field->internal = -1;
if (is_quote(*s)) { /* string literal */
STATIC_BUFFER(buffer);
if (!stringliteral(buffer, &s)) {
eprintf("%S", buffer);
return NULL;
}
field->name = STRDUP(buffer->data);
} else if (*s == '@') { /* internal */
for (t = ++s; is_alnum(*s) || *s == '_'; s++);
field->name = strncpy(XMALLOC(s - t + 1), t, s - t);
field->name[s-t] = '\0';
field->internal = enum2int(field->name, 0, enum_status,
"status_fields internal status");
FREE(field->name);
field->name = NULL;
if (field->internal < 0)
return NULL;
} else if (is_alnum(*s) || *s == '_') { /* variable */
for (t = s++; is_alnum(*s) || *s == '_'; s++);
field->name = strncpy(XMALLOC(s - t + 1), t, s - t);
field->name[s-t] = '\0';
field->var = &bogusvar;
} else { /* blank */
field->name = NULL;
}
if (*s == ':') {
while (*++s == '-')
field->rightjust = !field->rightjust;
field->width = strtoint(s, &s);
}
if (*s == ':') {
for (t = s + 1; *s && !is_space(*s); s++);
if (!parse_attrs(t, &field->attrs, ' '))
return NULL;
}
if (*s && !is_space(*s)) {
eprintf("garbage in status field: %.8s", s);
return NULL;
}
return s;
}
static Var *status_var(const char *type, const char *suffix)
{
STATIC_BUFFER(name);
Stringcat(Stringcat(Stringcpy(name, "status_"), type), suffix);
return findorcreateglobalvar(name->data);
}
conString *status_field_text(int row)
{
ListEntry *node;
StatusField *f;
String *buf = Stringnew(NULL, columns, 0);
int width;
if (row < 0 || row >= max_status_height)
return CS(buf);
for (node = statusfield_list[row]->head; node; node = node->next) {
if (node != statusfield_list[row]->head)
Stringadd(buf, ' ');
f = (StatusField*)node->datum;
width = f->width;
if (f->internal >= 0) {
Stringadd(buf, '@');
SStringcat(buf, &enum_status[f->internal]);
} else if (f->var) {
Stringcat(buf, f->var->val.name);
} else if (f->name) {
Sappendf(buf, "\"%q\"", '"', f->name);
if (width == strlen(f->name)) width = 0;
}
if (!width && !f->attrs && !f->rightjust)
continue;
Stringadd(buf, ':');
if (width || f->rightjust)
Sappendf(buf, "%s%d", f->rightjust ? "-" : "", width);
if (!f->attrs)
continue;
Stringadd(buf, ':');
attr2str(buf, f->attrs);
}
return CS(buf);
}
static void regen_status_fields(int row)
{
ListEntry *node;
StatusField *f;
int column;
/* finish calculations on status_fields_list */
for (node = statusfield_list[row]->head; node; node = node->next) {
f = (StatusField*)node->datum;
if (f->var) { /* f->var == &bogusvar */
if (f->var == &bogusvar) {
f->var = findorcreateglobalvar(f->name);
FREE(f->name);
f->name = NULL;
}
f->var->statuses++;
f->fmtvar = status_var("var_", f->var->val.name);
f->attrvar = status_var("attr_var_", f->var->val.name);
} else if (f->internal >= 0) {
f->fmtvar = status_var("int_", enum_status[f->internal].data);
f->attrvar = status_var("attr_int_", enum_status[f->internal].data);
} else {
continue;
}
f->fmtvar->statusfmts++;
f->attrvar->statusattrs++;
if (ch_attr(f->attrvar))
f->vattrs = f->attrvar->val.u.attr;
}
clock_update.tv_sec = 0;
variable_width_field[row] = NULL;
status_left[row] = status_right[row] = 0;
column = 0;
for (node = statusfield_list[row]->head; node; node = node->next) {
f = (StatusField*)node->datum;
status_left[row] = f->column = column;
if (f->width == 0) {
variable_width_field[row] = f;
break;
}
column += f->width;
}
column = 0;
if (node) {
for (node = statusfield_list[row]->tail; node; node = node->prev) {
f = (StatusField*)node->datum;
if (f->width == 0) break;
status_right[row] = -(f->column = (column -= f->width));
}
}
if (row == 0) {
/* regenerate the value of %status_fields for backward compatibility */
conString *buf = status_field_text(row);
/* set_str_var_direct avoids error checking and recursion */
set_str_var_direct(&special_var[VAR_stat_fields], TYPE_STR, buf);
}
update_status_line(NULL);
}
static ListEntry *find_statusfield_by_template(StatusField *target,
int full_comparison, const char *label)
{
ListEntry *node;
StatusField *f;
int row;
for (row = 0; row < status_height; row++) {
if (target->row >= 0 && row != target->row) continue;
for (node = statusfield_list[row]->head; node; node = node->next) {
f = (StatusField*)node->datum;
if (target->internal >= 0) {
if (target->internal != f->internal) continue;
} else if (target->var) {
if (!f->var || strcmp(target->name, f->var->val.name) != 0)
continue;
} else if (target->name) {
if (!f->name || cstrcmp(target->name, f->name) != 0)
continue;
}
if (full_comparison) {
if (target->width && target->width != f->width) continue;
if (target->attrs && target->attrs != f->attrs) continue;
}
return node;
}
}
if (label) {
eprintf(
target->row<0 ? "%s: no such field" : "%s: no such field in row %d",
label, target->row);
}
return NULL;
}
static ListEntry *find_statusfield_by_name(int row, const char *spec)
{
ListEntry *result = NULL;
StatusField target;
if (parse_statusfield(&target, spec)) {
target.row = row;
result = find_statusfield_by_template(&target, TRUE, spec);
}
if (target.name) FREE(target.name);
return result;
}
int ch_status_int(Var *var)
{
if (warn_status)
wprintf("the default value of %s has "
"changed between tf version 4 and 5.", var->val.name);
return 1;
}
static void free_statusfield(StatusField *field)
{
if (field->var && field->var != &bogusvar && !--field->var->statuses)
freevar(field->var);
if (field->fmtvar && !--field->fmtvar->statuses)
freevar(field->fmtvar);
if (field->attrvar && !--field->attrvar->statuses)
freevar(field->attrvar);
if (field->name)
FREE(field->name);
FREE(field);
}
static int status_add(int reset, int nodup, int row, ListEntry *where,
const char *s,
long spacer) /* spacer>0 goes after new field, spacer<0 goes before */
{
int totwidth = 0, vwf_found = 0;
List newlist[1];
StatusField *field;
init_list(newlist);
if (!reset) {
vwf_found = !!variable_width_field[row];
totwidth = status_left[row] + status_right[row];
}
/* validate and insert new fields */
while (1) {
while(is_space(*s)) s++;
if (!*s) break;
field = XMALLOC(sizeof(*field));
if (!(s = parse_statusfield(field, s)))
goto status_add_error;
field->row = row;
if (!field->width && !field->var && field->internal < 0 && field->name)
field->width = strlen(field->name);
if (nodup && find_statusfield_by_template(field, FALSE, NULL)) {
free_statusfield(field);
continue;
}
if (field->width != 0) {
totwidth += field->width;
} else {
if (vwf_found) {
eprintf("Only one variable width status field is allowed.");
goto status_add_error;
}
vwf_found = 1;
}
inlist(field, newlist, newlist->tail);
}
if (spacer && newlist->head && !reset && statusfield_list[row]->head) {
field = XCALLOC(sizeof(*field));
field->internal = -1;
field->row = row;
totwidth += (field->width = spacer < 0 ? -spacer : spacer);
inlist(field, newlist, spacer >= 0 ? NULL : newlist->tail);
}
if (totwidth > columns) {
wprintf("total status width (%d) is wider than screen (%d)",
totwidth, columns);
}
if (reset) {
/* delete old fields and clean up referents */
while (statusfield_list[row]->head)
free_statusfield(unlist(statusfield_list[row]->head,
statusfield_list[row]));
*statusfield_list[row] = *newlist;
} else {
/* insert new fields into list */
/* (We could be faster with ptr swapping, but this isn't critical) */
while (newlist->head) {
field = (StatusField*)unlist(newlist->head, newlist);
where = inlist(field, statusfield_list[row], where);
}
}
regen_status_fields(row);
return 1;
status_add_error:
free_statusfield(field);
while (newlist->head)
free_statusfield(unlist(newlist->head, newlist));
return 0;
}
struct Value *handle_status_add_command(String *args, int offset)
{
int opt, nodup = FALSE, row = -1, reset = 0;
ValueUnion uval;
long spacer = 1;
const char *ptr, *before = NULL, *after = NULL;
ListEntry *where;
startopt(CS(args), "A:B:s#r#xc");
while ((opt = nextopt(&ptr, &uval, NULL, &offset))) {
switch (opt) {
case 'A': after = STRDUP(ptr); before = NULL; break;
case 'B': before = STRDUP(ptr); after = NULL; break;
case 's': spacer = uval.ival; break;
case 'r': row = uval.ival; break;
case 'x': nodup = TRUE; break;
case 'c': reset = 1; break;
default: return shareval(val_zero);
}
}
if (after) {
if (*after) {
where = find_statusfield_by_name(row, after);
if (!where) return shareval(val_zero);
row = ((StatusField*)(where->datum))->row;
} else {
if (row < 0) row = 0;
where = statusfield_list[row]->tail;
}
FREE(after);
} else if (before) {
spacer = -spacer;
if (*before) {
where = find_statusfield_by_name(row, before);
if (!where) return shareval(val_zero);
row = ((StatusField*)(where->datum))->row;
where = where->prev;
} else {
if (row < 0) row = 0;
where = NULL;
}
FREE(before);
} else {
if (row < 0) row = 0;
where = statusfield_list[row]->tail;
}
return shareval(
status_add(reset, nodup, row, where, args->data + offset, spacer) ?
val_one : val_zero);
}
int ch_status_fields(Var *var)
{
if (warn_status) {
wprintf("setting status_fields directly is deprecated, "
"and may clobber useful new features introduced in version 5. "
"The recommended way to change "
"status fields is with /status_add, /status_rm, or /status_edit. "
"(This warning can be disabled with \"/set warn_status=off\".)");
}
return status_add(TRUE, FALSE, 0, NULL,
status_fields ? status_fields : "", 0);
}
static int getspacer(ListEntry *node)
{
StatusField *f;
if (!node) return 0;
f = node->datum;
return (f->var || f->internal >= 0 || f->name) ? 0 : f->width;
}
struct Value *handle_status_rm_command(String *args, int offset)
{
ValueUnion uval;
long row = -1;
int opt;
ListEntry *node;
startopt(CS(args), "r#");
while ((opt = nextopt(NULL, &uval, NULL, &offset))) {
switch (opt) {
case 'r': row = uval.ival; break;
default: return shareval(val_zero);
}
}
node = find_statusfield_by_name(row, args->data + offset);
if (!node) return shareval(val_zero);
row = ((StatusField*)(node->datum))->row;
if (variable_width_field[row] == node->datum)
variable_width_field[row] = NULL;
if (!node->prev && getspacer(node->next)) {
unlist(node->next, statusfield_list[row]); /* remove leading spacer */
} else if (!node->next && getspacer(node->prev)) {
unlist(node->prev, statusfield_list[row]); /* remove trailing spacer */
} else if (getspacer(node->prev) && getspacer(node->next)) {
/* remove smaller of two neighboring spacers */
if (getspacer(node->prev) < getspacer(node->next))
unlist(node->prev, statusfield_list[row]);
else
unlist(node->next, statusfield_list[row]);
}
unlist(node, statusfield_list[row]);
regen_status_fields(row);
return shareval(val_one);
}
struct Value *handle_status_edit_command(String *args, int offset)
{
ValueUnion uval;
long row = -1;
int opt;
StatusField *field;
ListEntry *node;
startopt(CS(args), "r#");
while ((opt = nextopt(NULL, &uval, NULL, &offset))) {
switch (opt) {
case 'r': row = uval.ival; break;
default: return shareval(val_zero);
}
}
field = XMALLOC(sizeof(*field));
if (!parse_statusfield(field, args->data + offset)) {
free_statusfield(field);
return shareval(val_zero);
}
field->row = row;
node = find_statusfield_by_template(field, FALSE, args->data + offset);
if (!node) {
free_statusfield(field);
return shareval(val_zero);
}
row = field->row = ((StatusField*)(node->datum))->row;
if (!field->width && variable_width_field[row] &&
node->datum != variable_width_field[row])
{
eprintf("Only one variable width status field per row is allowed.");
free_statusfield(field);
return shareval(val_zero);
}
free_statusfield(node->datum);
node->datum = field;
regen_status_fields(field->row);
return shareval(val_one);
}
/* returns column of field, or -1 if field is obscured by alert */
static inline int statusfield_column(StatusField *field)
{
int column;
if (field->column >= 0)
return (field->column > columns) ? columns : field->column;
column = field->column +
((status_left[field->row] + status_right[field->row] > columns) ?
status_left[field->row] + status_right[field->row] : columns);
return (column > columns) ? columns : column;
}
static int status_width(StatusField *field, int start)
{
int width;
if (start >= columns) return 0;
width = (field->width == 0) ?
columns - status_right[field->row] - status_left[field->row] :
(field->width > 0) ? field->width : -field->width;
if (width > columns - start)
width = columns - start;
if (width < 0)
width = 0;
return width;
}
int handle_status_width_func(const char *name)
{
/* XXX need to be able to specify row */
StatusField *field;
field = (StatusField*)find_statusfield_by_name(-1, name)->datum;
return field ? status_width(field, statusfield_column(field)) : 0;
}
static int format_statusfield(StatusField *field)
{
STATIC_BUFFER(scratch);
const char *old_command;
Value *fmtval, *val = NULL;
Program *prog;
int width, x, i;
output_disabled++;
Stringtrunc(scratch, 0);
if (field->internal >= 0 || field->var) {
fmtval = getvarval(field->fmtvar);
old_command = current_command;
if (fmtval) {
current_command = fmtval->name;
if (fmtval->type & TYPE_EXPR) {
prog = fmtval->u.prog;
} else if (fmtval->type == TYPE_STR) {
prog = compile_tf(valstr(fmtval), 0, -1, 1, 2);
if ((fmtval->u.prog = prog))
fmtval->type |= TYPE_EXPR;
} else {
prog = compile_tf(valstr(fmtval), 0, -1, 1, 0);
}
if (prog) {
val = expr_value_safe(prog);
if (!(fmtval->type & TYPE_EXPR))
prog_free(prog);
}
if (val) {
SStringcpy(scratch, valstr(val));
freeval(val);
} else {
SStringcpy(scratch, blankline);
}
} else if (field->var) {
current_command = field->var->val.name;
val = getvarval(field->var);
SStringcpy(scratch, val ? valstr(val) : blankline);
}
current_command = old_command;
} else if (field->name) { /* string literal */
Stringcpy(scratch, field->name);
}
x = statusfield_column(field);
width = status_width(field, x);
if (scratch->len > width)
Stringtrunc(scratch, width);
if (field->rightjust && scratch->len < width) { /* left pad */
for (i = 0; i < width - scratch->len; i++, x++) {
status_line[field->row]->data[x] = true_status_pad;
status_line[field->row]->charattrs[x] = status_attr;
}
}
if (scratch->len) { /* value */
attr_t attrs = scratch->attrs;
attrs = adj_attr(attrs, status_attr);
attrs = adj_attr(attrs, field->attrs);
attrs = adj_attr(attrs, field->vattrs);
for (i = 0; i < scratch->len; i++, x++) {
status_line[field->row]->data[x] = scratch->data[i];
status_line[field->row]->charattrs[x] = scratch->charattrs ?
adj_attr(attrs, scratch->charattrs[i]) : attrs;
}
}
if (!field->rightjust && scratch->len < width) { /* right pad */
for (i = 0; i < width - scratch->len; i++, x++) {
status_line[field->row]->data[x] = true_status_pad;
status_line[field->row]->charattrs[x] = status_attr;
}
}
if (field->internal == STAT_MORE)
need_more_refresh = 0;
if (field->internal == STAT_CLOCK) {
struct tm *local;
time_t sec = time(NULL);
/* note: localtime()->tm_sec won't compile if prototype is missing */
local = localtime(&sec);
clock_update.tv_sec = sec + 60 - local->tm_sec;
}
#if 0
if (field->var && !field->var->status) /* var was unset */
field->var = NULL;
#endif
output_disabled--;
return width;
}
static void display_status_segment(int row, int start, int width)
{
if (!alert_len || row != alert_row ||
start + width <= alert_pos || start >= alert_pos + alert_len)
{
/* no overlap with alert */
xy(start + 1, stat_top + row);
hwrite(CS(status_line[row]), start, width, 0);
} else {
if (start < alert_pos) {
/* segment starts left of alert */
xy(start + 1, stat_top + row);
hwrite(CS(status_line[row]), start, alert_pos - start, 0);
}
if (start + width >= alert_pos) {
/* segment ends right of alert */
xy(alert_pos + alert_len + 1, stat_top + row);
hwrite(CS(status_line[row]), alert_pos + alert_len,
start + width - (alert_pos + alert_len), 0);
}
}
}
void update_status_field(Var *var, stat_id_t internal)
{
ListEntry *node;
StatusField *field;
int row, column, width;
int count = 0;
if (screen_mode < 1) return;
if (var && var->statusattrs) {
if (!ch_attr(var))
return; /* error */
}
for (row = 0; row < status_height; row++) {
for (node = statusfield_list[row]->head; node; node = node->next) {
field = (StatusField*)node->datum;
if (var) {
if (field->var == var)
/* do nothing */;
else if (field->fmtvar == var)
/* do nothing */;
else if (field->attrvar == var)
field->vattrs = var->val.u.attr;
else
continue;
}
if (internal >= 0 && field->internal != internal) continue;
column = statusfield_column(field);
if (column >= columns) /* doesn't fit, nor will any later fields */
break;
count++;
width = format_statusfield(field);
display_status_segment(row, column, width);
}
}
if (count) {
bufflush();
set_refresh_pending(REF_PHYSICAL);
}
}
void format_status_line(void)
{
ListEntry *node;
StatusField *field;
int row, column, width;
for (row = 0; row < status_height; row++) {
column = 0;
width = 0;
for (node = statusfield_list[row]->head; node; node = node->next) {
field = (StatusField*)node->datum;
if ((column = statusfield_column(field)) >= columns)
break;
width = format_statusfield(field);
}
for (column += width; column < columns; column++) {
status_line[row]->data[column] = true_status_pad;
status_line[row]->charattrs[column] = status_attr;
}
}
}
int display_status_line(void)
{
int row;
if (screen_mode < 1) return 0;
for (row = 0; row < status_height; row++) {
display_status_segment(row, 0, columns);
}
bufflush();
set_refresh_pending(REF_PHYSICAL);
return 1;
}
int update_status_line(Var *var)
{
/* XXX optimization: some code that calls update_status_line() without
* any change in status_line could call display_status_line() directly,
* avoiding reformatting (in particular, status_{int,var}_* execution). */
format_status_line();
display_status_line();
return 1; /* for variable func */
}
void alert(conString *msg)
{
int row = 0;
int new_pos, new_len;
ListEntry *node;
StatusField *field;
attr_t orig_attrs;
if (msg->attrs & F_GAG && gag)
return;
alert_id++;
msg->links++; /* some callers pass msg with links==0, so we ++ and free */
if (!visual) {
tfputline(msg, tferr);
} else {
/* default to position 0 */
new_pos = 0;
new_len = msg->len > Wrap ? Wrap : msg->len;
if (msg->len < Wrap) {
/* if there's a field after @world, and msg fits there, use it */
for (node = statusfield_list[row]->head; node; node = node->next) {
field = (StatusField*)node->datum;
if (field->internal == STAT_WORLD && node->next) {
field = (StatusField*)node->next->datum;
break;
}
}
if (node) {
new_pos = (field->column < 0 ? columns : 0) + field->column;
if (new_pos + new_len > Wrap)
new_pos = 0;
}
}
if (alert_len &&
(alert_pos < new_pos || alert_pos + alert_len > new_pos + new_len))
{
/* part of old alert would still be visible under new alert */
/* XXX this could be optimized */
clear_alert();
}
alert_len = new_len;
alert_pos = new_pos;
gettime(&alert_timeout);
tvadd(&alert_timeout, &alert_timeout, &alert_time);
xy(alert_pos + 1, stat_top + alert_row);
orig_attrs = msg->attrs;
msg->attrs = adj_attr(msg->attrs, alert_attr);
hwrite(msg, 0, alert_len, 0);
msg->attrs = orig_attrs;
bufflush();
set_refresh_pending(REF_PHYSICAL);
}
conStringfree(msg);
}
void clear_alert(void)
{
if (!alert_len) return;
xy(alert_pos + 1, stat_top + alert_row);
hwrite(CS(status_line[alert_row]), alert_pos, alert_len, 0);
bufflush();
set_refresh_pending(REF_PHYSICAL);
alert_timeout = tvzero;
alert_pos = 0;
alert_len = 0;
alert_id = 0;
}
/* used by %{visual}, %{isize}, SIGWINCH */
int ch_visual(Var *var)
{
int need_redraw = 0, resized = 0, row;
for (row = 0; row < status_height; row++) {
if (status_line[row]->len < columns)
Stringnadd(status_line[row], '?', columns - status_line[row]->len);
Stringtrunc(status_line[row], columns);
}
if (screen_mode < 0) { /* e.g., called by init_variables() */
return 1;
} else if (var == &special_var[VAR_visual]) { /* %visual changed */
need_redraw = resized = 1;
alert_timeout = tvzero;
alert_pos = 0;
alert_len = 0;
} else if (var == &special_var[VAR_isize]) { /* %isize changed */
need_redraw = resized = visual;
#ifdef SCREEN
} else { /* SIGWINCH */
need_redraw = 1;
resized = 1;
cx = cy = -1; /* unknown */
top_margin = bottom_margin = -1; /* unknown */
#endif
}
if (need_redraw)
redraw();
if (resized)
transmit_window_size();
return 1;
}
int ch_status_height(Var *var)
{
if (status_height > max_status_height) {
eprintf("%s must be <= %d", var->val.name, max_status_height);
return 0;
}
if (visual) redraw();
transmit_window_size();
return 1;
}
int ch_expnonvis(Var *var)
{
if (!can_have_expnonvis && expnonvis) {
eprintf("expnonvis mode is not supported on this terminal.");
return 0;
}
if (!visual) {
old_ix = -1;
redraw();
}
return 1;
}
/* used by %{wrap}, %{wrappunct}, %{wrapsize}, %{wrapspace} */
int ch_wrap(Var *var)
{
if (screen_mode < 0) /* e.g., called by init_variables() */
return 1;
redraw_window(display_screen, 0);
transmit_window_size();
return 1;
}
void fix_screen(void)
{
oflush();
if (keypad && keypad_off)
tp(keypad_off);
if (screen_mode <= 0) {
clear_line();
#ifdef SCREEN
} else {
top_margin = bottom_margin = -1; /* force scroll region reset */
setscroll(1, lines);
clear_lines(stat_top, lines);
xy(1, stat_top);
if (exit_ca_mode) tp(exit_ca_mode);
#endif
}
cx = cy = -1;
bufflush();
screen_mode = -1;
}
/* minimal_fix_screen() avoids use of possibly corrupted structures. */
void minimal_fix_screen(void)
{
tp = tdirectputs;
fg_screen = default_screen;
default_screen->pline.head = default_screen->pline.tail = NULL;
default_screen->top = default_screen->bot = NULL;
outbuf->data = NULL;
outbuf->len = 0;
outbuf->size = 0;
output_disabled++;
fix_screen();
fflush(stdout);
}
static void clear_lines(int start, int end)
{
if (start > end) return;
xy(1, start);
if (end >= lines && clear_to_eos) {
tp(clear_to_eos); /* cx,cy were set by xy() */
} else {
clear_line();
while (start++ < end) {
bufputc('\n');
clear_line();
}
cy = end;
}
}
/* clear entire input window */
static void clear_input_window(void)
{
/* only called in visual mode */
clear_lines(in_top, lines);
ix = iendx = 1;
iy = iendy = istarty = in_top;
ipos();
}
/* clear logical input line */
static void clear_input_line(void)
{
if (!visual) clear_line();
else clear_lines(istarty, iendy);
ix = iendx = 1;
iy = iendy = istarty;
if (visual) ipos();
}
/* affects iendx, iendy, istarty. No effect on ix, iy. */
static void scroll_input(int n)
{
if (n > isize) {
clear_lines(in_top, lines);
iendy = in_top;
} else if (delete_line) {
xy(1, in_top);
for (iendy = lines + 1; iendy > lines - n + 1; iendy--)
tp(delete_line);
} else if (has_scroll_region) {
setscroll(in_top, lines);
xy(1, lines);
crnl(n); /* DON'T: cy += n; */
iendy = lines - n + 1;
}
xy(iendx = 1, iendy);
}
/***********************************************************************
* *
* INPUT WINDOW HANDLING *
* *
***********************************************************************/
/* ictrl_put
* display n characters of s, with control characters converted to printable
* bold reverse (so the physical width is also n).
*/
static void ictrl_put(const char *s, int n)
{
int attrflag;
char c;
for (attrflag = 0; n > 0; s++, n--) {
c = unmapchar(localize(*s));
if (is_cntrl(c)) {
if (!attrflag)
attributes_on(F_BOLD | F_REVERSE), attrflag = 1;
bufputc(CTRL(c));
} else {
if (attrflag)
attributes_off(F_BOLD | F_REVERSE), attrflag = 0;
bufputc(c);
}
}
if (attrflag) attributes_off(F_BOLD | F_REVERSE);
}
/* ioutputs
* Print string within bounds of input window. len is the number of
* characters to print; return value is the number actually printed,
* which may be less than len if string doesn't fit in the input window.
* precondition: iendx,iendy and real cursor are at the output position.
*/
static int ioutputs(const char *str, int len)
{
int space, written;
for (written = 0; len > 0; len -= space) {
if ((space = Wrap - iendx + 1) <= 0) {
if (!visual || iendy == lines) break; /* at end of window */
if (visual) xy(iendx = 1, ++iendy);
space = Wrap;
}
if (space > len) space = len;
ictrl_put(str, space); cx += space;
str += space;
written += space;
iendx += space;
}
return written;
}
/* ioutall
* Performs ioutputs() for input buffer starting at kpos.
* A negative kpos means to display that much of the end of the prompt.
*/
static void ioutall(int kpos)
{
int ppos;
if (kpos < 0) { /* posible only if there's a prompt */
kpos = -(-kpos % Wrap);
ppos = prompt->len + kpos;
if (ppos < 0) ppos = 0;
hwrite(prompt, ppos, prompt->len - ppos, 0);
iendx = -kpos + 1;
kpos = 0;
}
if (sockecho())
ioutputs(keybuf->data + kpos, keybuf->len - kpos);
}
void iput(int len)
{
const char *s;
int count, scrolled = 0, oiex = iendx, oiey = iendy;
s = keybuf->data + keyboard_pos - len;
if (!sockecho()) return;
if (visual) physical_refresh();
if (keybuf->len - keyboard_pos > 8 && /* faster than redisplay? */
visual && insert && clear_to_eol &&
(insert_char || insert_start) && /* can insert */
cy + (cx + len) / Wrap <= lines && /* new text will fit in window */
Wrap - len > 8) /* faster than redisplay? */
{
/* fast insert */
iy = iy + (ix - 1 + len) / Wrap;
ix = (ix - 1 + len) % Wrap + 1;
iendy = iendy + (iendx - 1 + len) / Wrap;
iendx = (iendx - 1 + len) % Wrap + 1;
if (iendy > lines) { iendy = lines; iendx = Wrap + 1; }
if (cx + len <= Wrap) {
if (insert_start) tp(insert_start);
else for (count = len; count; count--) tp(insert_char);
ictrl_put(s, len);
s += Wrap - (cx - 1);
cx += len;
} else {
ictrl_put(s, Wrap - (cx - 1));
s += Wrap - (cx - 1);
cx = Wrap + 1;
if (insert_start) tp(insert_start);
}
while (s < keybuf->data + keybuf->len) {
if (Wrap < columns && cx <= Wrap) {
xy(Wrap + 1, cy);
tp(clear_to_eol);
}
if (cy == lines) break;
xy(1, cy + 1);
if (!insert_start)
for (count = len; count; count--) tp(insert_char);
ictrl_put(s, len); cx += len;
s += Wrap;
}
if (insert_start) tp(insert_end);
ipos();
bufflush();
return;
}
iendx = ix;
iendy = iy;
/* Display as much as possible. If it doesn't fit, scroll and repeat
* until the whole string has been displayed.
*/
while (count = ioutputs(s, len), s += count, (len -= count) > 0) {
scrolled++;
if (!visual) {
if (expnonvis) {
int i;
int prompt_len = 0;
bufputc('\r');
if (prompt) {
prompt_len = prompt->len % Wrap;
hwrite(prompt, prompt->len - prompt_len, prompt_len, 0);
}
for (i = 0; i < sidescroll; i++)
tp(delete_char);
iendx -= i;
cx = cy = -1;
xy(iendx, lines);
} else {
crnl(1); cx = 1;
iendx = ix = 1;
}
} else if (scroll && !clearfull) {
scroll_input(1);
if (istarty > in_top) istarty--;
} else {
clear_input_window();
}
}
ix = iendx;
iy = iendy;
if (insert || scrolled) {
/* we must (re)display tail */
ioutputs(keybuf->data + keyboard_pos, keybuf->len - keyboard_pos);
if (visual) ipos();
else { bufputnc('\010', iendx - ix); cx -= (iendx - ix); }
} else if ((iendy - oiey) * Wrap + iendx - oiex < 0) {
/* if we didn't write past old iendx/iendy, restore them */
iendx = oiex;
iendy = oiey;
}
bufflush();
}
void inewline(void)
{
ix = iendx = 1;
if (!visual) {
if (expnonvis) {
clear_input_line();
} else {
crnl(1); cx = 1; cy++;
}
if (prompt) set_refresh_pending(REF_PHYSICAL);
} else {
if (cleardone) {
clear_input_window();
} else {
iy = iendy + 1;
if (iy > lines) {
if (scroll && !clearfull) {
scroll_input(1);
iy--;
} else {
clear_input_window();
}
}
}
istarty = iendy = iy;
set_refresh_pending(prompt ? REF_LOGICAL : REF_PHYSICAL);
}
bufflush();
}
/* idel() assumes place is in bounds and != keyboard_pos. */
void idel(int place)
{
int len;
int oiey = iendy;
if ((len = place - keyboard_pos) < 0) keyboard_pos = place;
if (!sockecho()) return;
if (len < 0) ix += len;
if (!visual) {
int prompt_len = prompt ? prompt->len % Wrap : 0;
if (ix < prompt_len + 1 || need_refresh) {
physical_refresh();
return;
}
if (expnonvis && ix == prompt_len + 1 && keyboard_pos == keybuf->len) {
/* there would be nothing left; slide the window so there is */
physical_refresh();
return;
}
if (len < 0) { bufputnc('\010', -len); cx += len; }
} else {
/* visual */
if (ix < 1) {
iy -= ((-ix) / Wrap) + 1;
ix = Wrap - ((-ix) % Wrap);
}
if (iy < in_top) {
logical_refresh();
return;
}
physical_refresh();
}
if (len < 0) len = -len;
if (visual && delete_char &&
keybuf->len - keyboard_pos > 3 && len < Wrap/3)
{
/* hardware method */
int i, space, pos;
iendy = iy;
if (ix + len <= Wrap) {
for (i = len; i; i--) tp(delete_char);
iendx = Wrap + 1 - len;
} else {
iendx = ix;
}
pos = keyboard_pos - ix + iendx;
while (pos < keybuf->len) {
if ((space = Wrap - iendx + 1) <= 0) {
if (iendy == lines) break; /* at end of window */
xy(iendx = 1, ++iendy);
for (i = len; i; i--) tp(delete_char);
space = Wrap - len;
if (space > keybuf->len - pos) space = keybuf->len - pos;
} else {
xy(iendx, iendy);
if (space > keybuf->len - pos) space = keybuf->len - pos;
ictrl_put(keybuf->data + pos, space); cx += space;
}
iendx += space;
pos += space;
}
/* erase tail */
if (iendy < oiey) {
crnl(1); cx = 1; cy++;
clear_line();
}
} else {
/* redisplay method */
iendx = ix;
iendy = iy;
ioutputs(keybuf->data + keyboard_pos, keybuf->len - keyboard_pos);
/* erase tail */
if (len > Wrap - cx + 1) len = Wrap - cx + 1;
if (visual && clear_to_eos && (len > 2 || cy < oiey)) {
tp(clear_to_eos);
} else if (clear_to_eol && len > 2) {
tp(clear_to_eol);
if (visual && cy < oiey) clear_lines(cy + 1, oiey);
} else {
bufputnc(' ', len); cx += len;
if (visual && cy < oiey) clear_lines(cy + 1, oiey);
}
}
/* restore cursor */
if (visual) ipos();
else { bufputnc('\010', cx - ix); cx = ix; }
bufflush();
}
int igoto(int place)
{
int diff;
if (place < 0)
place = 0;
if (place > keybuf->len)
place = keybuf->len;
diff = place - keyboard_pos;
keyboard_pos = place;
if (!diff) {
/* no physical change */
dobell(1);
} else if (!sockecho()) {
/* no physical change */
} else if (!visual) {
int prompt_len = prompt ? prompt->len % Wrap : 0;
ix += diff;
if (ix-1 < prompt_len) { /* off left edge of screen/prompt */
if (expnonvis && insert_char && prompt_len - (ix-1) <= Wrap/2) {
/* can scroll, and amount of scroll needed is <= half screen */
int i, n;
bufputc('\r');
if (prompt)
hwrite(prompt, prompt->len - prompt_len, prompt_len, 0);
n = prompt_len - (ix-1); /* get ix onto screen */
if (sidescroll > n) n = sidescroll; /* bring up to minimum */
if (n > keyboard_pos-diff) n = keyboard_pos-diff; /* too far? */
for (i = 0; i < n; i++)
tp(insert_char);
ix += i;
ictrl_put(keybuf->data + keyboard_pos + prompt_len - (ix-1), i);
bufputnc('\010', (prompt_len + i) - (ix - 1));
cx = ix;
cy = lines;
} else {
physical_refresh();
}
} else if (ix > Wrap) { /* off right edge of screen */
if (expnonvis) {
if (ix - Wrap > Wrap/2) {
physical_refresh();
} else {
/* amount of scroll needed is <= half screen */
int offset = place - ix + iendx;
int i;
bufputc('\r');
if (prompt)
hwrite(prompt, prompt->len - prompt_len, prompt_len, 0);
for (i = 0; i < sidescroll || i < ix - Wrap; i++)
tp(delete_char);
iendx -= i;
ix -= i;
cx = prompt_len + 1;
cy = lines;
xy(iendx, lines);
ioutputs(keybuf->data + offset, keybuf->len - offset);
diff -= i;
offset += i;
xy(ix, lines);
}
} else {
crnl(1); cx = 1; /* old text scrolls up, for continutity */
physical_refresh();
}
} else { /* on screen */
cx += diff;
if (diff < 0)
bufputnc('\010', -diff);
else
ictrl_put(keybuf->data + place - diff, diff);
}
/* visual */
} else {
int new = (ix - 1) + diff;
iy += ndiv(new, Wrap);
ix = nmod(new, Wrap) + 1;
if ((iy > lines) && (iy - lines < isize) && scroll) {
scroll_input(iy - lines);
ioutall(place - (ix - 1) - (iy - lines - 1) * Wrap);
iy = lines;
ipos();
} else if ((iy < in_top) || (iy > lines)) {
logical_refresh();
} else {
ipos();
}
}
bufflush();
return keyboard_pos;
}
void do_refresh(void)
{
if (visual && need_more_refresh) update_status_field(NULL, STAT_MORE);
if (need_refresh >= REF_LOGICAL) logical_refresh();
else if (need_refresh >= REF_PHYSICAL) physical_refresh();
}
void physical_refresh(void)
{
if (visual) {
setscroll(1, lines);
ipos();
} else {
int start;
int prompt_len = 0;
clear_input_line();
if (prompt) {
prompt_len = (prompt->len + 1) % Wrap - 1;
hwrite(prompt, prompt->len - prompt_len, prompt_len, 0);
iendx = prompt_len + 1;
}
ix = (sockecho()?keyboard_pos:0) % (Wrap - prompt_len) + 1 + prompt_len;
start = (sockecho()?keyboard_pos:0) - (ix - 1) + prompt_len;
if (start == keybuf->len && keybuf->len > 0) { /* would print nothing */
/* slide window so something is visible */
if (start > Wrap - prompt_len) {
ix += Wrap - prompt_len;
start -= Wrap - prompt_len;
} else {
ix += start;
start = 0;
}
}
ioutall(start);
bufputnc('\010', iendx - ix); cx -= (iendx - ix);
}
bufflush();
if (need_refresh <= REF_PHYSICAL) need_refresh = 0;
old_ix = -1; /* invalid */
}
void logical_refresh(void)
{
int kpos, nix, niy;
if (!visual)
oflush(); /* no sense refreshing if there's going to be output after */
kpos = prompt ? -(prompt->len % Wrap) : 0;
nix = ((sockecho() ? keyboard_pos : 0) - kpos) % Wrap + 1;
if (visual) {
setscroll(1, lines);
niy = istarty + (keyboard_pos - kpos) / Wrap;
if (niy <= lines) {
clear_input_line();
} else {
clear_input_window();
kpos += (niy - lines) * Wrap;
niy = lines;
}
ioutall(kpos);
ix = nix;
iy = niy;
ipos();
} else {
clear_input_line();
if (expnonvis) {
int plen = 0;
if (prompt) {
plen = (prompt->len + 1) % Wrap - 1;
hwrite(prompt, prompt->len - plen, plen, 0);
iendx = plen + 1;
}
ix = (old_ix >= iendx && old_ix <= Wrap) ? old_ix :
(sockecho()?keyboard_pos:0) % (Wrap - plen) + 1 + plen;
old_ix = -1; /* invalid */
kpos = (sockecho()?keyboard_pos:0) - (ix - 1) + plen;
if (kpos == keybuf->len && keybuf->len > 0) {
/* would print nothing; slide window so something is visible */
if (kpos > Wrap/2) {
ix += Wrap/2;
kpos -= Wrap/2;
} else {
ix += kpos;
kpos -= kpos;
}
}
ioutall(kpos);
} else {
ioutall(kpos);
kpos += Wrap;
while ((sockecho() && kpos <= keyboard_pos) || kpos < 0) {
crnl(1); cx = 1;
iendx = 1;
ioutall(kpos);
kpos += Wrap;
}
ix = nix;
}
bufputnc('\010', iendx - ix); cx -= (iendx - ix);
}
bufflush();
if (need_refresh <= REF_LOGICAL) need_refresh = 0;
}
void update_prompt(conString *newprompt, int display)
{
conString *oldprompt = prompt;
if (oldprompt == moreprompt) return;
prompt = newprompt;
old_ix = -1;
if ((oldprompt || prompt) && display)
set_refresh_pending(REF_LOGICAL);
}
/*****************************************************
* *
* OUTPUT HANDLING *
* *
*****************************************************/
/*************
* Utilities *
*************/
static void attributes_off(attr_t attrs)
{
const char *ctlseq;
if (attrs & F_HILITE) attrs |= hiliteattr;
if (have_attr & attrs & F_SIMPLE) {
if (attr_off) tp(attr_off);
else {
if (have_attr & attrs & F_UNDERLINE) tp(underline_off);
if (have_attr & attrs & F_BOLD ) tp(standout_off);
}
}
if ((attrs & F_COLORS) && (ctlseq = getvar("end_color"))) {
print_to_ascii(outbuf, ctlseq);
}
}
static void attributes_on(attr_t attrs)
{
if (attrs & F_HILITE)
attrs |= hiliteattr;
#if 0
if (attr_on) {
/* standout, underline, reverse, blink, dim, bold, blank, prot., ACS */
tp(tparm(attr_on,
(have_attr & attrs & F_BOLD && !bold),
(have_attr & attrs & F_UNDERLINE),
(have_attr & attrs & F_REVERSE),
(have_attr & attrs & F_FLASH),
(have_attr & attrs & F_DIM),
(have_attr & attrs & F_BOLD && bold),
0, 0, 0));
} else
#endif
{
/* Some emulators only show the last, so we do most important last. */
if (have_attr & attrs & F_DIM) tp(dim);
if (have_attr & attrs & F_BOLD) tp(bold ? bold : standout);
if (have_attr & attrs & F_UNDERLINE) tp(underline);
if (have_attr & attrs & F_REVERSE) tp(reverse);
if (have_attr & attrs & F_FLASH) tp(flash);
}
if (attrs & F_FGCOLOR) color_on("", attr2fgcolor(attrs));
if (attrs & F_BGCOLOR) color_on("bg", attr2bgcolor(attrs));
}
static void color_on(const char *prefix, long color)
{
const char *ctlseq;
smallstr buf;
sprintf(buf, "start_color_%s%s", prefix, enum_color[color].data);
if ((ctlseq = getvar(buf))) {
print_to_ascii(outbuf, ctlseq);
} else {
sprintf(buf, "start_color_%s%ld", prefix, color);
if ((ctlseq = getvar(buf)))
print_to_ascii(outbuf, ctlseq);
}
}
static void hwrite(conString *line, int start, int len, int indent)
{
attr_t attrs = line->attrs & F_HWRITE;
attr_t current = 0;
attr_t new;
int i, ctrl;
int col = 0;
char c;
if (line->attrs & F_BELL && start == 0) {
dobell(1);
}
if (indent) {
bufputnc(' ', indent);
cx += indent;
}
cx += len;
if (!line->charattrs && hilite && attrs)
attributes_on(current = attrs);
for (i = start; i < start + len; ++i) {
new = line->charattrs ? adj_attr(attrs, line->charattrs[i]) : attrs;
c = unmapchar(localize(line->data[i]));
ctrl = (emulation > EMUL_RAW && is_cntrl(c) && c != '\t');
if (ctrl)
new |= F_BOLD | F_REVERSE;
if (new != current) {
if (current) attributes_off(current);
current = new;
if (current) attributes_on(current);
}
if (c == '\t') {
bufputnc(' ', tabsize - col % tabsize);
col += tabsize - col % tabsize;
} else {
bufputc(ctrl ? CTRL(c) : c);
col++;
}
}
if (current) attributes_off(current);
}
void reset_outcount(Screen *screen)
{
if (!screen) screen = display_screen;
screen->outcount = visual ?
(scroll ? (out_bot - out_top + 1) : screen->outcount) :
lines - 1;
}
/* return TRUE if okay to print */
static int check_more(Screen *screen)
{
if (!screen->paused && more && interactive && screen->outcount-- <= 0)
{
/* status bar is updated in oflush() to avoid scroll region problems */
screen->paused = 1;
do_hook(H_MORE, NULL, "");
}
return !screen->paused;
}
int pause_screen(void)
{
if (display_screen->paused)
return 0;
display_screen->outcount = 0;
display_screen->paused = 1;
do_hook(H_MORE, NULL, "");
update_status_field(NULL, STAT_MORE);
return 1;
}
int clear_more(int new)
{
PhysLine *pl;
int use_insert, need_redraw = 0, scrolled = 0;
if (new < 0) {
if (!visual /* XXX || !can_scrollback */) return 0;
use_insert = insert_line && -new < winlines();
setscroll(1, out_bot);
while (scrolled > new && prevtop(display_screen)) {
pl = display_screen->top->datum;
if (display_screen->viewsize <= winlines()) {
/* visible area is not full: add to the top of visible area */
xy(1, winlines() - display_screen->viewsize + 1);
hwrite(pl->str, pl->start,
pl->len < Wrap - pl->indent ? pl->len : Wrap - pl->indent,
pl->indent);
} else {
/* visible area is full: insert at top and push bottom off */
display_screen->paused = 1;
display_screen->outcount = 0;
if (use_insert) {
xy(1, 1);
tp(insert_line);
hwrite(pl->str, pl->start,
pl->len < Wrap-pl->indent ? pl->len : Wrap-pl->indent,
pl->indent);
} else {
need_redraw++;
}
prevbot(display_screen);
}
scrolled--;
}
while (scrolled > new && display_screen->viewsize > 1) {
/* no more lines in list to scroll on to top. insert blanks. */
display_screen->paused = 1;
display_screen->outcount = 0;
if (use_insert) {
xy(1, 1);
tp(insert_line);
} else {
need_redraw++;
}
prevbot(display_screen);
scrolled--;
}
if (need_redraw) {
redraw();
return scrolled;
}
update_status_field(NULL, STAT_MORE);
} else { /* new >= 0 */
if (!display_screen->paused) return 0;
if (display_screen->nback_filtered) {
if (visual) {
setscroll(1, out_bot);
if (cy != out_bot)
xy(columns, out_bot);
} else {
if (!need_refresh)
old_ix = ix; /* physical_refresh() will restore ix */
clear_input_line();
}
while (scrolled < new && nextbot(display_screen)) {
pl = display_screen->bot->datum;
if (visual) output_scroll(pl); else output_novisual(pl);
scrolled++;
}
}
while (display_screen->viewsize > winlines())
nexttop(display_screen);
if (scrolled < new) {
display_screen->paused = 0;
display_screen->outcount = new - scrolled;
if (visual) {
update_status_field(NULL, STAT_MORE);
if (!scroll) display_screen->outcount = out_bot;
} else {
prompt = fgprompt();
old_ix = -1;
clear_input_line();
}
}
}
set_refresh_pending(REF_PHYSICAL);
return scrolled;
}
int tog_more(Var *var)
{
if (!more) clear_more(display_screen->outcount);
else reset_outcount(display_screen);
return 1;
}
int tog_keypad(Var *var)
{
if (!keypad_on) {
if (keypad)
eprintf("don't know how to enable keypad on %s terminal", TERM);
return 0;
}
tp(keypad ? keypad_on : keypad_off);
return 1;
}
int screen_end(int need_redraw)
{
Screen *screen = display_screen;
int oldmore = more;
hide_screen(screen);
if (screen->nback_filtered) {
screen->nback_filtered = screen->nback = 0;
screen->nnew_filtered = screen->nnew = 0;
special_var[VAR_more].val.u.ival = 0;
/* XXX optimize if (jump < screenful) (but what about tmp lines?) */
need_redraw = 1;
screen->maxbot = screen->bot = screen->pline.tail;
screen_refilter(screen);
special_var[VAR_more].val.u.ival = oldmore;
}
screen->paused = 0;
reset_outcount(screen);
if (need_redraw) {
redraw_window(screen, 0);
if (visual) {
update_status_field(NULL, STAT_MORE);
} else {
prompt = fgprompt();
}
}
return 1;
}
int selflush(void)
{
display_screen->selflush = 1;
screen_refilter_bottom(display_screen);
clear_more(winlines());
return 1;
}
/* next_physline
* Increment bottom of screen. Checks for More and SELFLUSH termination.
* Returns 1 if there is a displayable line, 0 if not.
*/
static int next_physline(Screen *screen)
{
if (screen->paused)
return 0;
if (!nextbot(screen)) {
if (screen->selflush) {
if (screen->selflush == 1) {
screen->selflush++;
screen->paused = 1;
update_status_field(NULL, STAT_MORE);
} else {
screen->selflush = 0;
clear_screen_filter(screen);
screen_end(1);
}
}
return 0;
}
if (!check_more(screen)) {
/* undo the nextbot() */
if (screen->maxbot == screen->bot) {
screen->maxbot = screen->maxbot->prev;
screen->nnew++;
screen->nnew_filtered++;
}
screen->bot = screen->bot->prev;
screen->nback_filtered++;
screen->nback++;
screen->viewsize--;
return 0;
}
if (display_screen->viewsize > winlines())
nexttop(screen);
return 1;
}
/* returns length of prefix of str that will fit in {wrapsize} */
int wraplen(const char *str, int len, int indent)
{
int total, max, visible;
if (emulation == EMUL_RAW) return len;
max = Wrap - indent;
for (visible = total = 0; total < len && visible < max; total++) {
if (str[total] == '\t')
visible += tabsize - visible % tabsize;
else
visible++;
}
if (total == len) return len;
len = total;
if (wrapflag) {
while (len && !is_space(str[len-1]))
--len;
if (wrappunct > 0 && len < total - wrappunct) {
len = total;
while (len && !is_space(str[len-1]) && !is_punct(str[len-1]))
--len;
}
}
return len ? len : total;
}
/****************
* Main drivers *
****************/
int moresize(Screen *screen) {
if (!screen) screen = display_screen;
return screen->nback_filtered;
}
/* write to display_screen (no history) */
void screenout(conString *line)
{
enscreen(display_screen, line);
oflush();
}
void enscreen(Screen *screen, conString *line)
{
int wrapped, visible;
if (!hilite)
line->attrs &= ~F_HWRITE;
if (line->attrs & F_GAG && gag)
return;
if (!screen->pline.head) { /* initialize wrap state */
screen->scr_wrapflag = wrapflag;
screen->scr_wrapsize = Wrap;
screen->scr_wrapspace = wrapspace;
screen->scr_wrappunct = wrappunct;
}
visible = screen_filter(screen, line);
wrapped = wraplines(line, &screen->pline, visible);
screen->nlline++;
screen->npline += wrapped;
screen->nback += wrapped;
screen->nnew += wrapped;
if (visible) {
screen->nback_filtered += wrapped;
screen->nnew_filtered += wrapped;
}
purge_old_lines(screen);
}
void oflush(void)
{
static int lastsize;
int waspaused, count = 0;
PhysLine *pl;
Screen *screen = display_screen;
if (output_disabled) return;
if (!(waspaused = screen->paused)) {
lastsize = 0;
while (next_physline(screen)) {
pl = screen->bot->datum;
if (count++ == 0) { /* first iteration? */
if (screen_mode < 1) {
if (!need_refresh)
old_ix = ix; /* physical_refresh() will restore ix */
clear_input_line();
} else if (scroll && has_scroll_region) {
setscroll(1, out_bot);
if (cy != out_bot)
xy(columns, out_bot);
}
}
if (screen_mode < 1) output_novisual(pl);
#ifdef SCREEN
else if (scroll) output_scroll(pl);
else output_noscroll(pl);
#endif
set_refresh_pending(REF_PHYSICAL);
bufflush();
}
}
if (screen->paused) {
if (!visual) {
if (!waspaused) {
prompt = moreprompt;
old_ix = -1;
set_refresh_pending(REF_LOGICAL);
}
} else if (!waspaused ||
moresize(screen) / morewait > lastsize / morewait)
{
update_status_field(NULL, STAT_MORE);
} else if (lastsize != moresize(screen)) {
need_more_refresh = 1;
}
lastsize = moresize(screen);
}
}
static void output_novisual(PhysLine *pl)
{
hwrite(pl->str, pl->start, pl->len, pl->indent);
crnl(1); cx = 1; cy++;
}
#ifdef SCREEN
static void output_noscroll(PhysLine *pl)
{
setscroll(1, lines); /* needed after scroll_input(), etc. */
xy(1, (oy + 1) % (out_bot) + 1);
clear_line();
xy(ox, oy);
hwrite(pl->str, pl->start, pl->len, pl->indent);
oy = oy % (out_bot) + 1;
}
static void output_scroll(PhysLine *pl)
{
if (has_scroll_region) {
crnl(1);
/* Some brain damaged emulators lose attributes under cursor
* when that '\n' is printed. Too bad. */
} else {
xy(1, 1);
tp(delete_line);
xy(1, out_bot);
tp(insert_line);
}
hwrite(pl->str, pl->start, pl->len, pl->indent);
}
#endif
void hide_screen(Screen *screen)
{
ListEntry *node, *next;
PhysLine *pl;
if (!screen) screen = fg_screen;
if (screen->viewsize > 0) {
/* delete any temp lines in [top,bot] */
int done;
for (done = 0, node = screen->top; !done; node = next) {
done = (node == screen->bot);
next = node->next;
pl = node->datum;
if (pl->tmp) {
if (screen->top == node)
screen->top = node->next;
if (screen->bot == node)
screen->bot = node->prev;
if (screen->maxbot == node)
screen->maxbot = node->prev;
unlist(node, &screen->pline);
conStringfree(pl->str);
pfree(pl, plpool, str);
screen->viewsize--;
screen->npline--;
screen->nlline--;
}
}
}
}
void unhide_screen(Screen *screen)
{
PhysLine *pl;
if (!virtscreen || !textdiv || !visual) {
return;
} else if (textdiv == TEXTDIV_CLEAR) {
clear_screen_view(screen);
} else if (textdiv_str && fg_screen->maxbot &&
((PhysLine*)(fg_screen->maxbot->datum))->str != textdiv_str &&
(textdiv == TEXTDIV_ALWAYS ||
fg_screen->maxbot != fg_screen->bot || fg_screen->maxbot->next))
/* If textdiv is enabled and there's no divider at maxbot already... */
{
/* insert divider at maxbot */
palloc(pl, PhysLine, plpool, str, __FILE__, __LINE__);
pl->visible = 1;
pl->tmp = 1;
(pl->str = textdiv_str)->links++;
pl->start = 0;
pl->indent = 0;
pl->len = wraplen(textdiv_str->data, textdiv_str->len, 0);
inlist(pl, &fg_screen->pline, fg_screen->maxbot);
if (fg_screen->bot == fg_screen->maxbot) {
/* insert ABOVE bot, so it doesn't look like new activity */
fg_screen->bot = fg_screen->maxbot->next;
if (fg_screen->viewsize == 0)
fg_screen->top = fg_screen->bot;
fg_screen->viewsize++;
if (fg_screen->viewsize >= winlines())
fg_screen->partialview = 0;
} else {
/* inserting BELOW bot; we must increment nback* */
fg_screen->nback++;
fg_screen->nback_filtered++;
}
fg_screen->maxbot = fg_screen->maxbot->next;
fg_screen->npline++;
fg_screen->nlline++;
}
}
/*
* Switch to new fg_screen.
*/
void switch_screen(int quiet)
{
if (fg_screen != display_screen) { /* !virtscreen */
/* move lines from fg_screen to display_screen */
/* XXX optimize when no filter */
PhysLine *pl;
List *dest = &display_screen->pline;
List *src = &fg_screen->pline;
while (src->head) {
dest->tail->next = src->head;
dest->tail = dest->tail->next;
src->head = src->head->next;
display_screen->nnew++;
display_screen->nback++;
pl = dest->tail->datum;
if (screen_filter(display_screen, pl->str)) {
display_screen->nnew_filtered++;
display_screen->nback_filtered++;
}
}
src->head = src->tail = NULL;
fg_screen->nback_filtered = fg_screen->nback = 0;
fg_screen->nnew_filtered = fg_screen->nnew = 0;
fg_screen->npline = 0;
fg_screen->nlline = 0;
fg_screen->maxbot = fg_screen->bot = fg_screen->top = NULL;
}
update_status_field(NULL, STAT_WORLD);
if (quiet) {
/* jump to end */
screen_end(1);
} else {
if (virtscreen) {
redraw_window(display_screen, 0);
}
/* display new lines */
oflush();
update_status_field(NULL, STAT_MORE);
}
}
#if USE_DMALLOC
void free_output(void)
{
int row;
tfclose(tfscreen);
tfclose(tfalert);
free_screen(default_screen);
fg_screen = default_screen = NULL;
Stringfree(outbuf);
for (row = 0; row < max_status_height; row++) {
Stringfree(status_line[row]);
while (statusfield_list[row]->head)
free_statusfield(unlist(statusfield_list[row]->head,
statusfield_list[row]));
}
pfreepool(PhysLine, plpool, str);
}
#endif
syntax highlighted by Code2HTML, v. 0.9.1