611 lines
11 KiB
C
611 lines
11 KiB
C
/*
|
|
* Copyright (C) 1984-2005 Mark Nudelman
|
|
*
|
|
* You may distribute under the terms of either the GNU General Public
|
|
* License or the Less License, as specified in the README file.
|
|
*
|
|
* For more information about less, or for information on how to
|
|
* contact the author, see the README file.
|
|
*/
|
|
|
|
|
|
/*
|
|
* High level routines dealing with the output to the screen.
|
|
*/
|
|
|
|
#include "less.h"
|
|
#if MSDOS_COMPILER==WIN32C
|
|
#include "windows.h"
|
|
#endif
|
|
|
|
public int errmsgs; /* Count of messages displayed by error() */
|
|
public int need_clr;
|
|
public int final_attr;
|
|
|
|
extern int sigs;
|
|
extern int sc_width;
|
|
extern int so_s_width, so_e_width;
|
|
extern int screen_trashed;
|
|
extern int any_display;
|
|
extern int is_tty;
|
|
|
|
#if MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC
|
|
extern int ctldisp;
|
|
extern int nm_fg_color, nm_bg_color;
|
|
extern int bo_fg_color, bo_bg_color;
|
|
extern int ul_fg_color, ul_bg_color;
|
|
extern int so_fg_color, so_bg_color;
|
|
extern int bl_fg_color, bl_bg_color;
|
|
#endif
|
|
|
|
/*
|
|
* Display the line which is in the line buffer.
|
|
*/
|
|
public void
|
|
put_line()
|
|
{
|
|
register int c;
|
|
register int i;
|
|
int a;
|
|
|
|
if (ABORT_SIGS())
|
|
{
|
|
/*
|
|
* Don't output if a signal is pending.
|
|
*/
|
|
screen_trashed = 1;
|
|
return;
|
|
}
|
|
|
|
final_attr = AT_NORMAL;
|
|
|
|
for (i = 0; (c = gline(i, &a)) != '\0'; i++)
|
|
{
|
|
at_switch(a);
|
|
final_attr = a;
|
|
if (c == '\b')
|
|
putbs();
|
|
else
|
|
putchr(c);
|
|
}
|
|
|
|
at_exit();
|
|
}
|
|
|
|
static char obuf[OUTBUF_SIZE];
|
|
static char *ob = obuf;
|
|
|
|
/*
|
|
* Flush buffered output.
|
|
*
|
|
* If we haven't displayed any file data yet,
|
|
* output messages on error output (file descriptor 2),
|
|
* otherwise output on standard output (file descriptor 1).
|
|
*
|
|
* This has the desirable effect of producing all
|
|
* error messages on error output if standard output
|
|
* is directed to a file. It also does the same if
|
|
* we never produce any real output; for example, if
|
|
* the input file(s) cannot be opened. If we do
|
|
* eventually produce output, code in edit() makes
|
|
* sure these messages can be seen before they are
|
|
* overwritten or scrolled away.
|
|
*/
|
|
public void
|
|
flush()
|
|
{
|
|
register int n;
|
|
register int fd;
|
|
|
|
n = ob - obuf;
|
|
if (n == 0)
|
|
return;
|
|
#if MSDOS_COMPILER==WIN32C
|
|
if (is_tty && any_display)
|
|
{
|
|
char *op;
|
|
DWORD nwritten = 0;
|
|
CONSOLE_SCREEN_BUFFER_INFO scr;
|
|
int row;
|
|
int col;
|
|
int olen;
|
|
extern HANDLE con_out;
|
|
|
|
olen = ob - obuf;
|
|
/*
|
|
* There is a bug in Win32 WriteConsole() if we're
|
|
* writing in the last cell with a different color.
|
|
* To avoid color problems in the bottom line,
|
|
* we scroll the screen manually, before writing.
|
|
*/
|
|
GetConsoleScreenBufferInfo(con_out, &scr);
|
|
col = scr.dwCursorPosition.X;
|
|
row = scr.dwCursorPosition.Y;
|
|
for (op = obuf; op < obuf + olen; op++)
|
|
{
|
|
if (*op == '\n')
|
|
{
|
|
col = 0;
|
|
row++;
|
|
} else if (*op == '\r')
|
|
{
|
|
col = 0;
|
|
} else
|
|
{
|
|
col++;
|
|
if (col >= sc_width)
|
|
{
|
|
col = 0;
|
|
row++;
|
|
}
|
|
}
|
|
}
|
|
if (row > scr.srWindow.Bottom)
|
|
win32_scroll_up(row - scr.srWindow.Bottom);
|
|
WriteConsole(con_out, obuf, olen, &nwritten, NULL);
|
|
ob = obuf;
|
|
return;
|
|
}
|
|
#else
|
|
#if MSDOS_COMPILER==MSOFTC
|
|
if (is_tty && any_display)
|
|
{
|
|
*ob = '\0';
|
|
_outtext(obuf);
|
|
ob = obuf;
|
|
return;
|
|
}
|
|
#else
|
|
#if MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC
|
|
if (is_tty && any_display)
|
|
{
|
|
*ob = '\0';
|
|
if (ctldisp != OPT_ONPLUS)
|
|
cputs(obuf);
|
|
else
|
|
{
|
|
/*
|
|
* Look for SGR escape sequences, and convert them
|
|
* to color commands. Replace bold, underline,
|
|
* and italic escapes into colors specified via
|
|
* the -D command-line option.
|
|
*/
|
|
char *anchor, *p, *p_next;
|
|
int buflen = ob - obuf;
|
|
unsigned char fg, bg, norm_attr;
|
|
/*
|
|
* Only dark colors mentioned here, so that
|
|
* bold has visible effect.
|
|
*/
|
|
static enum COLORS screen_color[] = {
|
|
BLACK, RED, GREEN, BROWN,
|
|
BLUE, MAGENTA, CYAN, LIGHTGRAY
|
|
};
|
|
|
|
/* Normal text colors are used as baseline. */
|
|
bg = nm_bg_color & 0xf;
|
|
fg = nm_fg_color & 0xf;
|
|
norm_attr = (bg << 4) | fg;
|
|
for (anchor = p_next = obuf;
|
|
(p_next = memchr (p_next, ESC,
|
|
buflen - (p_next - obuf)))
|
|
!= NULL; )
|
|
{
|
|
p = p_next;
|
|
|
|
/*
|
|
* Handle the null escape sequence
|
|
* (ESC-[m), which is used to restore
|
|
* the original color.
|
|
*/
|
|
if (p[1] == '[' && is_ansi_end(p[2]))
|
|
{
|
|
textattr(norm_attr);
|
|
p += 3;
|
|
anchor = p_next = p;
|
|
continue;
|
|
}
|
|
|
|
if (p[1] == '[') /* "Esc-[" sequence */
|
|
{
|
|
/*
|
|
* If some chars seen since
|
|
* the last escape sequence,
|
|
* write it out to the screen
|
|
* using current text attributes.
|
|
*/
|
|
if (p > anchor)
|
|
{
|
|
*p = '\0';
|
|
cputs (anchor);
|
|
*p = ESC;
|
|
anchor = p;
|
|
}
|
|
p += 2;
|
|
p_next = p;
|
|
while (!is_ansi_end(*p))
|
|
{
|
|
char *q;
|
|
long code = strtol(p, &q, 10);
|
|
|
|
if (!*q)
|
|
{
|
|
/*
|
|
* Incomplete sequence.
|
|
* Leave it unprocessed
|
|
* in the buffer.
|
|
*/
|
|
int slop = q - anchor;
|
|
strcpy(obuf, anchor);
|
|
ob = &obuf[slop];
|
|
return;
|
|
}
|
|
|
|
if (q == p
|
|
|| code > 49 || code < 0
|
|
|| (!is_ansi_end(*q)
|
|
&& *q != ';'))
|
|
{
|
|
p_next = q;
|
|
break;
|
|
}
|
|
if (*q == ';')
|
|
q++;
|
|
|
|
switch (code)
|
|
{
|
|
case 1: /* bold on */
|
|
fg = bo_fg_color;
|
|
bg = bo_bg_color;
|
|
break;
|
|
case 3: /* italic on */
|
|
fg = so_fg_color;
|
|
bg = so_bg_color;
|
|
break;
|
|
case 4: /* underline on */
|
|
fg = ul_fg_color;
|
|
bg = ul_bg_color;
|
|
break;
|
|
case 8: /* concealed on */
|
|
fg = (bg & 7) | 8;
|
|
break;
|
|
case 0: /* all attrs off */
|
|
case 22:/* bold off */
|
|
case 23:/* italic off */
|
|
case 24:/* underline off */
|
|
fg = nm_fg_color;
|
|
bg = nm_bg_color;
|
|
break;
|
|
case 30: case 31: case 32:
|
|
case 33: case 34: case 35:
|
|
case 36: case 37:
|
|
fg = (fg & 8) | (screen_color[code - 30]);
|
|
break;
|
|
case 39: /* default fg */
|
|
fg = nm_fg_color;
|
|
break;
|
|
case 40: case 41: case 42:
|
|
case 43: case 44: case 45:
|
|
case 46: case 47:
|
|
bg = (bg & 8) | (screen_color[code - 40]);
|
|
break;
|
|
case 49: /* default fg */
|
|
bg = nm_bg_color;
|
|
break;
|
|
}
|
|
p = q;
|
|
}
|
|
if (is_ansi_end(*p) && p > p_next)
|
|
{
|
|
bg &= 15;
|
|
fg &= 15;
|
|
textattr ((bg << 4)| fg);
|
|
p_next = anchor = p + 1;
|
|
} else
|
|
break;
|
|
} else
|
|
p_next++;
|
|
}
|
|
|
|
/* Output what's left in the buffer. */
|
|
cputs (anchor);
|
|
}
|
|
ob = obuf;
|
|
return;
|
|
}
|
|
#endif
|
|
#endif
|
|
#endif
|
|
fd = (any_display) ? 1 : 2;
|
|
if (write(fd, obuf, n) != n)
|
|
screen_trashed = 1;
|
|
ob = obuf;
|
|
}
|
|
|
|
/*
|
|
* Output a character.
|
|
*/
|
|
public int
|
|
putchr(c)
|
|
int c;
|
|
{
|
|
#if 0 /* fake UTF-8 output for testing */
|
|
extern int utf_mode;
|
|
if (utf_mode)
|
|
{
|
|
static char ubuf[MAX_UTF_CHAR_LEN];
|
|
static int ubuf_len = 0;
|
|
static int ubuf_index = 0;
|
|
if (ubuf_len == 0)
|
|
{
|
|
ubuf_len = utf_len(c);
|
|
ubuf_index = 0;
|
|
}
|
|
ubuf[ubuf_index++] = c;
|
|
if (ubuf_index < ubuf_len)
|
|
return c;
|
|
c = get_wchar(ubuf) & 0xFF;
|
|
ubuf_len = 0;
|
|
}
|
|
#endif
|
|
if (need_clr)
|
|
{
|
|
need_clr = 0;
|
|
clear_bot();
|
|
}
|
|
#if MSDOS_COMPILER
|
|
if (c == '\n' && is_tty)
|
|
{
|
|
/* remove_top(1); */
|
|
putchr('\r');
|
|
}
|
|
#else
|
|
#ifdef _OSK
|
|
if (c == '\n' && is_tty) /* In OS-9, '\n' == 0x0D */
|
|
putchr(0x0A);
|
|
#endif
|
|
#endif
|
|
/*
|
|
* Some versions of flush() write to *ob, so we must flush
|
|
* when we are still one char from the end of obuf.
|
|
*/
|
|
if (ob >= &obuf[sizeof(obuf)-1])
|
|
flush();
|
|
*ob++ = c;
|
|
return (c);
|
|
}
|
|
|
|
/*
|
|
* Output a string.
|
|
*/
|
|
public void
|
|
putstr(s)
|
|
register char *s;
|
|
{
|
|
while (*s != '\0')
|
|
putchr(*s++);
|
|
}
|
|
|
|
|
|
/*
|
|
* Convert an integral type to a string.
|
|
*/
|
|
#define TYPE_TO_A_FUNC(funcname, type) \
|
|
void funcname(num, buf) \
|
|
type num; \
|
|
char *buf; \
|
|
{ \
|
|
int neg = (num < 0); \
|
|
char tbuf[INT_STRLEN_BOUND(num)+2]; \
|
|
register char *s = tbuf + sizeof(tbuf); \
|
|
if (neg) num = -num; \
|
|
*--s = '\0'; \
|
|
do { \
|
|
*--s = (num % 10) + '0'; \
|
|
} while ((num /= 10) != 0); \
|
|
if (neg) *--s = '-'; \
|
|
strcpy(buf, s); \
|
|
}
|
|
|
|
TYPE_TO_A_FUNC(postoa, POSITION)
|
|
TYPE_TO_A_FUNC(linenumtoa, LINENUM)
|
|
TYPE_TO_A_FUNC(inttoa, int)
|
|
|
|
/*
|
|
* Output an integer in a given radix.
|
|
*/
|
|
static int
|
|
iprint_int(num)
|
|
int num;
|
|
{
|
|
char buf[INT_STRLEN_BOUND(num)];
|
|
|
|
inttoa(num, buf);
|
|
putstr(buf);
|
|
return (strlen(buf));
|
|
}
|
|
|
|
/*
|
|
* Output a line number in a given radix.
|
|
*/
|
|
static int
|
|
iprint_linenum(num)
|
|
LINENUM num;
|
|
{
|
|
char buf[INT_STRLEN_BOUND(num)];
|
|
|
|
linenumtoa(num, buf);
|
|
putstr(buf);
|
|
return (strlen(buf));
|
|
}
|
|
|
|
/*
|
|
* This function implements printf-like functionality
|
|
* using a more portable argument list mechanism than printf's.
|
|
*/
|
|
static int
|
|
less_printf(fmt, parg)
|
|
register char *fmt;
|
|
PARG *parg;
|
|
{
|
|
register char *s;
|
|
register int col;
|
|
|
|
col = 0;
|
|
while (*fmt != '\0')
|
|
{
|
|
if (*fmt != '%')
|
|
{
|
|
putchr(*fmt++);
|
|
col++;
|
|
} else
|
|
{
|
|
++fmt;
|
|
switch (*fmt++)
|
|
{
|
|
case 's':
|
|
s = parg->p_string;
|
|
parg++;
|
|
while (*s != '\0')
|
|
{
|
|
putchr(*s++);
|
|
col++;
|
|
}
|
|
break;
|
|
case 'd':
|
|
col += iprint_int(parg->p_int);
|
|
parg++;
|
|
break;
|
|
case 'n':
|
|
col += iprint_linenum(parg->p_linenum);
|
|
parg++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return (col);
|
|
}
|
|
|
|
/*
|
|
* Get a RETURN.
|
|
* If some other non-trivial char is pressed, unget it, so it will
|
|
* become the next command.
|
|
*/
|
|
public void
|
|
get_return()
|
|
{
|
|
int c;
|
|
|
|
#if ONLY_RETURN
|
|
while ((c = getchr()) != '\n' && c != '\r')
|
|
bell();
|
|
#else
|
|
c = getchr();
|
|
if (c != '\n' && c != '\r' && c != ' ' && c != READ_INTR)
|
|
ungetcc(c);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Output a message in the lower left corner of the screen
|
|
* and wait for carriage return.
|
|
*/
|
|
public void
|
|
error(fmt, parg)
|
|
char *fmt;
|
|
PARG *parg;
|
|
{
|
|
int col = 0;
|
|
static char return_to_continue[] = " (press RETURN)";
|
|
|
|
errmsgs++;
|
|
|
|
if (any_display && is_tty)
|
|
{
|
|
at_exit();
|
|
clear_bot();
|
|
at_enter(AT_STANDOUT);
|
|
col += so_s_width;
|
|
}
|
|
|
|
col += less_printf(fmt, parg);
|
|
|
|
if (!(any_display && is_tty))
|
|
{
|
|
putchr('\n');
|
|
return;
|
|
}
|
|
|
|
putstr(return_to_continue);
|
|
at_exit();
|
|
col += sizeof(return_to_continue) + so_e_width;
|
|
|
|
get_return();
|
|
lower_left();
|
|
|
|
if (col >= sc_width)
|
|
/*
|
|
* Printing the message has probably scrolled the screen.
|
|
* {{ Unless the terminal doesn't have auto margins,
|
|
* in which case we just hammered on the right margin. }}
|
|
*/
|
|
screen_trashed = 1;
|
|
|
|
flush();
|
|
}
|
|
|
|
static char intr_to_abort[] = "... (interrupt to abort)";
|
|
|
|
/*
|
|
* Output a message in the lower left corner of the screen
|
|
* and don't wait for carriage return.
|
|
* Usually used to warn that we are beginning a potentially
|
|
* time-consuming operation.
|
|
*/
|
|
public void
|
|
ierror(fmt, parg)
|
|
char *fmt;
|
|
PARG *parg;
|
|
{
|
|
at_exit();
|
|
clear_bot();
|
|
at_enter(AT_STANDOUT);
|
|
(void) less_printf(fmt, parg);
|
|
putstr(intr_to_abort);
|
|
at_exit();
|
|
flush();
|
|
need_clr = 1;
|
|
}
|
|
|
|
/*
|
|
* Output a message in the lower left corner of the screen
|
|
* and return a single-character response.
|
|
*/
|
|
public int
|
|
query(fmt, parg)
|
|
char *fmt;
|
|
PARG *parg;
|
|
{
|
|
register int c;
|
|
int col = 0;
|
|
|
|
if (any_display && is_tty)
|
|
clear_bot();
|
|
|
|
(void) less_printf(fmt, parg);
|
|
c = getchr();
|
|
|
|
if (!(any_display && is_tty))
|
|
{
|
|
putchr('\n');
|
|
return (c);
|
|
}
|
|
|
|
lower_left();
|
|
if (col >= sc_width)
|
|
screen_trashed = 1;
|
|
flush();
|
|
|
|
return (c);
|
|
}
|