📄 terminal.c
字号:
/*
* Terminal emulator.
*/
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <time.h>
#include <assert.h>
#include "putty.h"
#include "terminal.h"
#define poslt(p1,p2) ( (p1).y < (p2).y || ( (p1).y == (p2).y && (p1).x < (p2).x ) )
#define posle(p1,p2) ( (p1).y < (p2).y || ( (p1).y == (p2).y && (p1).x <= (p2).x ) )
#define poseq(p1,p2) ( (p1).y == (p2).y && (p1).x == (p2).x )
#define posdiff(p1,p2) ( ((p1).y - (p2).y) * (term->cols+1) + (p1).x - (p2).x )
/* Product-order comparisons for rectangular block selection. */
#define posPlt(p1,p2) ( (p1).y <= (p2).y && (p1).x < (p2).x )
#define posPle(p1,p2) ( (p1).y <= (p2).y && (p1).x <= (p2).x )
#define incpos(p) ( (p).x == term->cols ? ((p).x = 0, (p).y++, 1) : ((p).x++, 0) )
#define decpos(p) ( (p).x == 0 ? ((p).x = term->cols, (p).y--, 1) : ((p).x--, 0) )
#define VT52_PLUS
#define CL_ANSIMIN 0x0001 /* Codes in all ANSI like terminals. */
#define CL_VT100 0x0002 /* VT100 */
#define CL_VT100AVO 0x0004 /* VT100 +AVO; 132x24 (not 132x14) & attrs */
#define CL_VT102 0x0008 /* VT102 */
#define CL_VT220 0x0010 /* VT220 */
#define CL_VT320 0x0020 /* VT320 */
#define CL_VT420 0x0040 /* VT420 */
#define CL_VT510 0x0080 /* VT510, NB VT510 includes ANSI */
#define CL_VT340TEXT 0x0100 /* VT340 extensions that appear in the VT420 */
#define CL_SCOANSI 0x1000 /* SCOANSI not in ANSIMIN. */
#define CL_ANSI 0x2000 /* ANSI ECMA-48 not in the VT100..VT420 */
#define CL_OTHER 0x4000 /* Others, Xterm, linux, putty, dunno, etc */
#define TM_VT100 (CL_ANSIMIN|CL_VT100)
#define TM_VT100AVO (TM_VT100|CL_VT100AVO)
#define TM_VT102 (TM_VT100AVO|CL_VT102)
#define TM_VT220 (TM_VT102|CL_VT220)
#define TM_VTXXX (TM_VT220|CL_VT340TEXT|CL_VT510|CL_VT420|CL_VT320)
#define TM_SCOANSI (CL_ANSIMIN|CL_SCOANSI)
#define TM_PUTTY (0xFFFF)
#define UPDATE_DELAY ((TICKSPERSEC+49)/50)/* ticks to defer window update */
#define TBLINK_DELAY ((TICKSPERSEC*9+19)/20)/* ticks between text blinks*/
#define CBLINK_DELAY (CURSORBLINK) /* ticks between cursor blinks */
#define VBELL_DELAY (VBELL_TIMEOUT) /* visual bell timeout in ticks */
#define compatibility(x) \
if ( ((CL_##x)&term->compatibility_level) == 0 ) { \
term->termstate=TOPLEVEL; \
break; \
}
#define compatibility2(x,y) \
if ( ((CL_##x|CL_##y)&term->compatibility_level) == 0 ) { \
term->termstate=TOPLEVEL; \
break; \
}
#define has_compat(x) ( ((CL_##x)&term->compatibility_level) != 0 )
char *EMPTY_WINDOW_TITLE = "";
const char sco2ansicolour[] = { 0, 4, 2, 6, 1, 5, 3, 7 };
#define sel_nl_sz (sizeof(sel_nl)/sizeof(wchar_t))
const wchar_t sel_nl[] = SEL_NL;
/*
* Fetch the character at a particular position in a line array,
* for purposes of `wordtype'. The reason this isn't just a simple
* array reference is that if the character we find is UCSWIDE,
* then we must look one space further to the left.
*/
#define UCSGET(a, x) \
( (x)>0 && (a)[(x)].chr == UCSWIDE ? (a)[(x)-1].chr : (a)[(x)].chr )
/*
* Detect the various aliases of U+0020 SPACE.
*/
#define IS_SPACE_CHR(chr) \
((chr) == 0x20 || (DIRECT_CHAR(chr) && ((chr) & 0xFF) == 0x20))
/*
* Spot magic CSETs.
*/
#define CSET_OF(chr) (DIRECT_CHAR(chr)||DIRECT_FONT(chr) ? (chr)&CSET_MASK : 0)
/*
* Internal prototypes.
*/
static void resizeline(Terminal *, termline *, int);
static termline *lineptr(Terminal *, int, int, int);
static void unlineptr(termline *);
static void do_paint(Terminal *, Context, int);
static void erase_lots(Terminal *, int, int, int);
static int find_last_nonempty_line(Terminal *, tree234 *);
static void swap_screen(Terminal *, int, int, int);
static void update_sbar(Terminal *);
static void deselect(Terminal *);
static void term_print_finish(Terminal *);
static void scroll(Terminal *, int, int, int, int);
#ifdef OPTIMISE_SCROLL
static void scroll_display(Terminal *, int, int, int);
#endif /* OPTIMISE_SCROLL */
static termline *newline(Terminal *term, int cols, int bce)
{
termline *line;
int j;
line = snew(termline);
line->chars = snewn(cols, termchar);
for (j = 0; j < cols; j++)
line->chars[j] = (bce ? term->erase_char : term->basic_erase_char);
line->cols = line->size = cols;
line->lattr = LATTR_NORM;
line->temporary = FALSE;
line->cc_free = 0;
return line;
}
static void freeline(termline *line)
{
if (line) {
sfree(line->chars);
sfree(line);
}
}
static void unlineptr(termline *line)
{
if (line->temporary)
freeline(line);
}
#ifdef TERM_CC_DIAGS
/*
* Diagnostic function: verify that a termline has a correct
* combining character structure.
*
* This is a performance-intensive check, so it's no longer enabled
* by default.
*/
static void cc_check(termline *line)
{
unsigned char *flags;
int i, j;
assert(line->size >= line->cols);
flags = snewn(line->size, unsigned char);
for (i = 0; i < line->size; i++)
flags[i] = (i < line->cols);
for (i = 0; i < line->cols; i++) {
j = i;
while (line->chars[j].cc_next) {
j += line->chars[j].cc_next;
assert(j >= line->cols && j < line->size);
assert(!flags[j]);
flags[j] = TRUE;
}
}
j = line->cc_free;
if (j) {
while (1) {
assert(j >= line->cols && j < line->size);
assert(!flags[j]);
flags[j] = TRUE;
if (line->chars[j].cc_next)
j += line->chars[j].cc_next;
else
break;
}
}
j = 0;
for (i = 0; i < line->size; i++)
j += (flags[i] != 0);
assert(j == line->size);
sfree(flags);
}
#endif
/*
* Add a combining character to a character cell.
*/
static void add_cc(termline *line, int col, unsigned long chr)
{
int newcc;
assert(col >= 0 && col < line->cols);
/*
* Start by extending the cols array if the free list is empty.
*/
if (!line->cc_free) {
int n = line->size;
line->size += 16 + (line->size - line->cols) / 2;
line->chars = sresize(line->chars, line->size, termchar);
line->cc_free = n;
while (n < line->size) {
if (n+1 < line->size)
line->chars[n].cc_next = 1;
else
line->chars[n].cc_next = 0;
n++;
}
}
/*
* Now walk the cc list of the cell in question.
*/
while (line->chars[col].cc_next)
col += line->chars[col].cc_next;
/*
* `col' now points at the last cc currently in this cell; so
* we simply add another one.
*/
newcc = line->cc_free;
if (line->chars[newcc].cc_next)
line->cc_free = newcc + line->chars[newcc].cc_next;
else
line->cc_free = 0;
line->chars[newcc].cc_next = 0;
line->chars[newcc].chr = chr;
line->chars[col].cc_next = newcc - col;
#ifdef TERM_CC_DIAGS
cc_check(line);
#endif
}
/*
* Clear the combining character list in a character cell.
*/
static void clear_cc(termline *line, int col)
{
int oldfree, origcol = col;
assert(col >= 0 && col < line->cols);
if (!line->chars[col].cc_next)
return; /* nothing needs doing */
oldfree = line->cc_free;
line->cc_free = col + line->chars[col].cc_next;
while (line->chars[col].cc_next)
col += line->chars[col].cc_next;
if (oldfree)
line->chars[col].cc_next = oldfree - col;
else
line->chars[col].cc_next = 0;
line->chars[origcol].cc_next = 0;
#ifdef TERM_CC_DIAGS
cc_check(line);
#endif
}
/*
* Compare two character cells for equality. Special case required
* in do_paint() where we override what we expect the chr and attr
* fields to be.
*/
static int termchars_equal_override(termchar *a, termchar *b,
unsigned long bchr, unsigned long battr)
{
/* FULL-TERMCHAR */
if (a->chr != bchr)
return FALSE;
if ((a->attr &~ DATTR_MASK) != (battr &~ DATTR_MASK))
return FALSE;
while (a->cc_next || b->cc_next) {
if (!a->cc_next || !b->cc_next)
return FALSE; /* one cc-list ends, other does not */
a += a->cc_next;
b += b->cc_next;
if (a->chr != b->chr)
return FALSE;
}
return TRUE;
}
static int termchars_equal(termchar *a, termchar *b)
{
return termchars_equal_override(a, b, b->chr, b->attr);
}
/*
* Copy a character cell. (Requires a pointer to the destination
* termline, so as to access its free list.)
*/
static void copy_termchar(termline *destline, int x, termchar *src)
{
clear_cc(destline, x);
destline->chars[x] = *src; /* copy everything except cc-list */
destline->chars[x].cc_next = 0; /* and make sure this is zero */
while (src->cc_next) {
src += src->cc_next;
add_cc(destline, x, src->chr);
}
#ifdef TERM_CC_DIAGS
cc_check(destline);
#endif
}
/*
* Move a character cell within its termline.
*/
static void move_termchar(termline *line, termchar *dest, termchar *src)
{
/* First clear the cc list from the original char, just in case. */
clear_cc(line, dest - line->chars);
/* Move the character cell and adjust its cc_next. */
*dest = *src; /* copy everything except cc-list */
if (src->cc_next)
dest->cc_next = src->cc_next - (dest-src);
/* Ensure the original cell doesn't have a cc list. */
src->cc_next = 0;
#ifdef TERM_CC_DIAGS
cc_check(line);
#endif
}
/*
* Compress and decompress a termline into an RLE-based format for
* storing in scrollback. (Since scrollback almost never needs to
* be modified and exists in huge quantities, this is a sensible
* tradeoff, particularly since it allows us to continue adding
* features to the main termchar structure without proportionally
* bloating the terminal emulator's memory footprint unless those
* features are in constant use.)
*/
struct buf {
unsigned char *data;
int len, size;
};
static void add(struct buf *b, unsigned char c)
{
if (b->len >= b->size) {
b->size = (b->len * 3 / 2) + 512;
b->data = sresize(b->data, b->size, unsigned char);
}
b->data[b->len++] = c;
}
static int get(struct buf *b)
{
return b->data[b->len++];
}
static void makerle(struct buf *b, termline *ldata,
void (*makeliteral)(struct buf *b, termchar *c,
unsigned long *state))
{
int hdrpos, hdrsize, n, prevlen, prevpos, thislen, thispos, prev2;
termchar *c = ldata->chars;
unsigned long state = 0, oldstate;
n = ldata->cols;
hdrpos = b->len;
hdrsize = 0;
add(b, 0);
prevlen = prevpos = 0;
prev2 = FALSE;
while (n-- > 0) {
thispos = b->len;
makeliteral(b, c++, &state);
thislen = b->len - thispos;
if (thislen == prevlen &&
!memcmp(b->data + prevpos, b->data + thispos, thislen)) {
/*
* This literal precisely matches the previous one.
* Turn it into a run if it's worthwhile.
*
* With one-byte literals, it costs us two bytes to
* encode a run, plus another byte to write the header
* to resume normal output; so a three-element run is
* neutral, and anything beyond that is unconditionally
* worthwhile. With two-byte literals or more, even a
* 2-run is a win.
*/
if (thislen > 1 || prev2) {
int runpos, runlen;
/*
* It's worth encoding a run. Start at prevpos,
* unless hdrsize==0 in which case we can back up
* another one and start by overwriting hdrpos.
*/
hdrsize--; /* remove the literal at prevpos */
if (prev2) {
assert(hdrsize > 0);
hdrsize--;
prevpos -= prevlen;/* and possibly another one */
}
if (hdrsize == 0) {
assert(prevpos == hdrpos + 1);
runpos = hdrpos;
b->len = prevpos+prevlen;
} else {
memmove(b->data + prevpos+1, b->data + prevpos, prevlen);
runpos = prevpos;
b->len = prevpos+prevlen+1;
/*
* Terminate the previous run of ordinary
* literals.
*/
assert(hdrsize >= 1 && hdrsize <= 128);
b->data[hdrpos] = hdrsize - 1;
}
runlen = prev2 ? 3 : 2;
while (n > 0 && runlen < 129) {
int tmppos, tmplen;
tmppos = b->len;
oldstate = state;
makeliteral(b, c, &state);
tmplen = b->len - tmppos;
b->len = tmppos;
if (tmplen != thislen ||
memcmp(b->data + runpos+1, b->data + tmppos, tmplen)) {
state = oldstate;
break; /* run over */
}
n--, c++, runlen++;
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -