/*  Copyright 1992 John Bovey, University of Kent at Canterbury.
 *
 *  You can do what you like with this source code as long as
 *  you don't try to make money out of it and you include an
 *  unaltered copy of this message (including the copyright).
 */

char xvt_screen_c_sccsid[] = "@(#)screen.c	1.3 18/9/92 (UKC)";

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include "rvt.h"
#include "screen.h"
#include "command.h"
#include "xsetup.h"
#include "sbar.h"

#define MAX_WIDTH 250		/* max width of selected lines */
#define PROP_SIZE 1024		/* chunk size for retrieving the selection
				 * property */

/*  Character classes used when selecting words with a double click.
 */
static int char_class[128] = {
	32, 1, 1, 1, 1, 1, 1, 1,
	1, 32, 1, 1, 1, 1, 1, 1,
	1, 1, 1, 1, 1, 1, 1, 1,
	1, 1, 1, 1, 1, 1, 1, 1,
	32, 33, 34, 35, 36, 37, 38, 39,
	40, 41, 42, 43, 44, 45, 46, 47,
	48, 48, 48, 48, 48, 48, 48, 48,
	48, 48, 58, 59, 60, 61, 62, 63,
	64, 48, 48, 48, 48, 48, 48, 48,
	48, 48, 48, 48, 48, 48, 48, 48,
	48, 48, 48, 48, 48, 48, 48, 48,
	48, 48, 48, 91, 92, 93, 94, 48,
	96, 48, 48, 48, 48, 48, 48, 48,
	48, 48, 48, 48, 48, 48, 48, 48,
	48, 48, 48, 48, 48, 48, 48, 48,
	48, 48, 48, 123, 124, 125, 126, 1
};
/*  External global variables that are initialised at startup.
 */
extern Display *display;
extern Window vt_win;
extern Window main_win;
extern Colormap colormap;
extern XFontStruct *mainfont;	/* main font structure */
extern XFontStruct *boldfont;	/* bold font structure */
extern GC gc;			/* GC for drawing text */
extern GC negc;			/* GC without graphics exposures */
extern GC hlgc;			/* GC used for highlighting text cursor */
extern unsigned long foreground;/* foreground pixel value */
extern unsigned long background;/* background pixel value */
extern int reverse_wrap;	/* reverse wrap allowed */

/*  Structure describing the current state of the screen.
 */
struct screenst {
	char **text;		/* backup copy of screen->text */
	char **rend;		/* character rendition styles etc. */
	int row;		/* cursor position */
	int col;		/* ditto */
	int tmargin;		/* top scroll margin */
	int bmargin;		/* bottom scroll margin */
	int decom;		/* origin mode flag */
	int wrap;		/* auto-wrap flag */
	int wrap_next;		/* wrap before the next printed character */
	int insert;		/* insert mode flag */
};
/*  structure describing a saved line
 */
struct slinest {
	char *sl_text;		/* the text of the line */
	char *sl_rend;		/* the rendition style */
	int sl_length;		/* length of the line */
};
/*  selection endpoint types.
 */
enum seltype {
	SCREEN,
	SAVED,
	NOSEL
};
/*  structure describing a selection endpoint.
 */
struct selst {
	enum seltype se_type;
	int se_index;		/* index into the sline or screen array */
	int se_col;		/* column of the character */
};

static struct selst selend1, selend2;	/* the selection endpoints */
static struct selst selanchor;	/* the selection anchor */
static char *selection_text = NULL;	/* text version of the current
					 * selection */
static int selection_length;	/* length of selection text */
static enum selunit selection_unit;	/* current unit of selection */

/*  Screen state variables that are the same for both screens.
 */
static int pwidth;		/* width in pixels */
static int pheight;		/* height in pixels */
static int cwidth = 0;		/* width in characters */
static int cheight = 0;		/* height in characters */
static int focus = 0;		/* window has the keyboard focus */
static int rstyle;		/* rendition style */
static int save_rstyle;		/* when it needs to be saved */

/*  screen state variables
 */
static struct screenst screen1;	/* main screen */
static struct screenst screen2;	/* second screen */
static struct screenst *screen = NULL;	/* current of screen1 and screen2 */
static struct screenst save_screen;

static int sline_top;		/* high water mark of saved scrolled lines */
static int sline_max;		/* Max number of saved lines */
static struct slinest **sline;	/* main array of saved lines */
static int offset = 0;		/* current vertical offset for displaying
				 * saved lines */

static int fheight;		/* height of a character in the font */
static int fwidth;		/* width of a font character */

static void repair_damage(void);
static Bool grexornoex(Display *, XEvent *, char *);
static void change_offset(int);
static void paint_rval_text(char *, int, int, int, int);
static void paint_rvec_text(char *, char *, int, int, int);
static void refresh(int, int, int, int);
static void show_selection(int, int, int, int);
static void scroll(int, int, int);
static void scroll1(int);
static void home_screen(void);
static void cursor(void);
static void rc_to_selend(int, int, struct selst *);
static void fix_rc(int *, int *);
static void selend_to_rc(int *, int *, struct selst *);
static void change_selection(struct selst *, struct selst *);
static char *convert_line(char *, int *, int, int);
static int selcmp(struct selst *, struct selst *);
static void adjust_selection(struct selst *);
static int save_selection(void);
static void check_selection(int, int);
static int cclass(int);

/*  Perform any initialisation on the screen data structures.  Called just once
 *  at startup.  saved_lines is the number of saved lines.
 */
void
scr_init(saved_lines)
	int saved_lines;
{
	int i;

	/* Initialise the array of lines that have scrolled of the top. */
	sline_max = saved_lines;
	if (sline_max < MAX_SCROLL)
		sline_max = MAX_SCROLL;
	sline = (struct slinest **) cmalloc(sline_max * sizeof(struct slinest *));
	for (i = 0; i < sline_max; i++)
		sline[i] = NULL;
	sline_top = 0;

	screen1.text = NULL;
	screen1.rend = NULL;
	screen1.row = 0;
	screen1.col = 0;
	screen1.wrap = 1;
	screen1.decom = 0;
	screen1.insert = 0;
	screen2.text = NULL;
	screen2.rend = NULL;
	screen2.row = 0;
	screen2.col = 0;
	screen2.wrap = 1;
	screen2.decom = 0;
	screen2.insert = 0;
	save_screen.row = 0;
	save_screen.col = 0;
	screen = &screen1;
	rstyle = 0;

	fwidth = XTextWidth(mainfont, "M", 1);
	fheight = mainfont->ascent + mainfont->descent;
	scr_reset();
}
/*  Reset the screen - called whenever the screen needs to be repaired.
 */
void
scr_reset()
{
	Window root;
	int x, y, n, i, j, onscreen;
	unsigned int width, height, border_width, depth;
	int cw, ch;
	char **r1, **r2, **s1, **s2;
	struct slinest *sl;

	XGetGeometry(display, vt_win, &root, &x, &y, &width, &height, &border_width, &depth);
	cw = (width - 2 * MARGIN) / fwidth;
	ch = (height - 2 * MARGIN) / fheight;

	if (screen->text == NULL || cw != cwidth || ch != cheight) {

		offset = 0;
		/* Recreate the screen backup arrays. The screen arrays are
		 * one byte wider than the screen and the last byte is used as
		 * a flag which is non-zero of the line wrapped automatically. */
		s1 = (char **) cmalloc(ch * sizeof(char *));
		s2 = (char **) cmalloc(ch * sizeof(char *));
		r1 = (char **) cmalloc(ch * sizeof(char *));
		r2 = (char **) cmalloc(ch * sizeof(char *));
		for (y = 0; y < ch; y++) {
			s1[y] = (char *) cmalloc(cw + 1);
			s2[y] = (char *) cmalloc(cw + 1);
			r1[y] = (char *) cmalloc(cw + 1);
			r2[y] = (char *) cmalloc(cw + 1);
			memset(s1[y], 0, cw + 1);
			memset(s2[y], 0, cw + 1);
			memset(r1[y], 0, cw + 1);
			memset(r2[y], 0, cw + 1);
		}

		if (screen1.text != NULL) {

			/* Now fill up the screen from the old screen and
			 * saved lines. */
			if (screen1.row >= ch) {
				/* scroll up to save any lines that will be
				 * lost. */
				scroll1(screen1.row - ch + 1);
				screen1.row = ch - 1;
			}
			/* calculate working no. of lines. */
			i = sline_top + screen1.row + 1;
			j = i > ch ? ch - 1 : i - 1;
			i = screen1.row;
			screen1.row = j;
			onscreen = 1;
			for (; j >= 0; j--) {
				if (onscreen) {
					n = cw < cwidth ? cw : cwidth;
					memcpy(s1[j], screen1.text[i], n);
					memcpy(s2[j], screen2.text[i], n);
					memcpy(r1[j], screen1.rend[i], n);
					memcpy(r2[j], screen2.rend[i], n);
					s1[j][cw] = screen1.text[i][cwidth];
					s2[j][cw] = screen2.text[i][cwidth];
					r1[j][cw] = screen1.rend[i][cwidth];
					r2[j][cw] = screen2.rend[i][cwidth];
					i--;
					if (i < 0) {
						onscreen = 0;
						i = 0;
					}
				} else {
					if (i >= sline_top)
						break;
					sl = sline[i];
					n = cw < sl->sl_length ? cw : sl->sl_length;
					memcpy(s1[j], sl->sl_text, n);
					free(sl->sl_text);
					if (sl->sl_rend != NULL) {
						memcpy(r1[j], sl->sl_rend, n);
						r1[j][cw] = sl->sl_rend[sl->sl_length];
						free(sl->sl_rend);
					}
					i++;
				}
			}
			if (onscreen)
				abort();
			for (j = i; j < sline_top; j++)
				sline[j - i] = sline[j];
			for (j = sline_top - i; j < sline_top; j++)
				sline[j] = NULL;
			sline_top -= i;

			for (y = 0; y < cheight; y++) {
				free(screen1.text[y]);
				free(screen2.text[y]);
				free(screen1.rend[y]);
				free(screen2.rend[y]);
			}
			free((char *) screen1.text);
			free((char *) screen2.text);
			free((char *) screen1.rend);
			free((char *) screen2.rend);
		}
		screen1.text = s1;
		screen2.text = s2;
		screen1.rend = r1;
		screen2.rend = r2;

		cwidth = cw;
		cheight = ch;
		pwidth = width;
		pheight = height;
		screen1.tmargin = 0;
		screen1.bmargin = cheight - 1;
		screen1.decom = 0;
		screen1.wrap_next = 0;
		screen2.tmargin = 0;
		screen2.bmargin = cheight - 1;
		screen2.decom = 0;
		screen2.wrap_next = 0;
		tty_set_size(cwidth, cheight);
		scr_start_selection(0, 0, CHAR);
	}
	if (screen->col >= cwidth)
		screen->col = cwidth - 1;
	if (screen->row >= cheight)
		screen->row = cheight - 1;
	sbar_show(cheight + sline_top - 1, offset, offset + cheight - 1);
	refresh(0, cheight - 1, 0, cwidth - 1);
	cursor();
}
/*  Parse the string as a sequence of character classes and use it to
 *  modify the char_class table.
 */
void
scr_char_class(s)
	char *s;
{
	int first, last, value, i;

	while (isdigit(*s)) {
		first = last = value = 0;
		while (isdigit(*s)) {
			first = first * 10 + *s - '0';
			s++;
		}
		if (*s == '-') {
			s++;
			while (isdigit(*s)) {
				last = last * 10 + *s - '0';
				s++;
			}
		} else
			last = first;
		if (*s == ':')
			s++;
		while (isdigit(*s)) {
			value = value * 10 + *s - '0';
			s++;
		}
		if (*s == ',')
			s++;
		if (last > 127)
			last = 127;
		for (i = first; i <= last; i++)
			char_class[i] = value;
	}
}
/*  Handle a backspace
 */
void
scr_backspace()
{
	if (screen->col == 0 && reverse_wrap && screen->row > 0) {
		cursor();
		screen->row--;
		screen->col = cwidth - 1;
		cursor();
	} else if (screen->wrap_next && reverse_wrap)
		screen->wrap_next = 0;
	else
		scr_move(-1, 0, COL_RELATIVE | ROW_RELATIVE);
}
/*  Ring the bell
 */
void
scr_bell()
{
	XBell(display, 0);
}
/*  Change between the alternate and the main screens
 */
void
scr_change_screen(direction)
	int direction;
{
	home_screen();
	screen = (direction == HIGH) ? &screen2 : &screen1;
	selend2.se_type = NOSEL;
	refresh(0, cheight - 1, 0, cwidth - 1);
	cursor();
}
/*  Change the rendition style.
 */
void
scr_change_rendition(style)
	int style;
{
	if (style == 0)
		rstyle = 0;
	else
		rstyle |= style;
}
/*  Return the width and height of the screen.
 */
void
scr_get_size(width_p, height_p)
	int *width_p, *height_p;
{
	*width_p = cwidth;
	*height_p = cheight;
}
/*  Indicate a change of keyboard focus.  type is 1 for entry events and 2 for
 *  focus events.
 */
void
scr_focus(type, is_in)
	int type, is_in;
{
	cursor();
	if (is_in)
		focus |= type;
	else
		focus &= ~type;
	cursor();
}
/*  Display the string at the current position.  nlcount is the number of new lines
 *  in the string.
 */
void
scr_string(str, len, nlcount)
	char *str;
	int len, nlcount;
{
	int x, x2, y, n, i, width;
	char *s, *r;

	home_screen();
	cursor();
	if (nlcount > 0) {
		if (screen->row > screen->bmargin)
			nlcount = 0;
		else
			nlcount -= screen->bmargin - screen->row;
		if (nlcount < 0)
			nlcount = 0;
		else if (nlcount > screen->row - screen->tmargin)
			nlcount = screen->row - screen->tmargin;
		if (nlcount > MAX_SCROLL)
			nlcount = MAX_SCROLL;
		scroll(screen->tmargin, screen->bmargin, nlcount);
		screen->row -= nlcount;
	}
	while (len > 0) {
		if (*str == '\n') {
			if (screen->row == screen->bmargin)
				scroll(screen->tmargin, screen->bmargin, 1);
			else if (screen->row < cheight - 1)
				screen->row++;
			check_selection(screen->row, screen->row);
			screen->wrap_next = 0;
			len--;
			str++;
			continue;
		}
		if (*str == '\r') {
			screen->col = 0;
			screen->wrap_next = 0;
			len--;
			str++;
			continue;
		}
		if (*str == '\t') {
			if (screen->col < cwidth - 1) {
				s = screen->text[screen->row];
				if (s[screen->col] == 0)
					s[screen->col] = '\t';
				screen->col++;
				while (screen->col % 8 != 0 && screen->col < cwidth - 1)
					screen->col++;
			}
			len--;
			str++;
			continue;
		}
		if (screen->wrap_next) {
			screen->text[screen->row][cwidth] = 1;
			if (screen->row == screen->bmargin)
				scroll(screen->tmargin, screen->bmargin, 1);
			else if (screen->row < cheight - 1)
				screen->row++;
			screen->col = 0;
			screen->wrap_next = 0;
		}
		check_selection(screen->row, screen->row);
		x = MARGIN + fwidth * screen->col;
		y = MARGIN + fheight * screen->row;
		for (n = 0; str[n] >= ' '; n++);
		n = n + screen->col > cwidth ? cwidth - screen->col : n;
		if (screen->insert) {
			s = screen->text[screen->row];
			r = screen->rend[screen->row];
			for (i = cwidth - 1; i >= screen->col + n; i--) {
				s[i] = s[i - n];
				r[i] = r[i - n];
			}
			width = (cwidth - screen->col - n) * fwidth;
			x2 = x + n * fwidth;
			if (width > 0) {
				XCopyArea(display, vt_win, vt_win, gc, x, y, width, fheight, x2, y);
				repair_damage();
			}
		}
		paint_rval_text(str, rstyle, n, x, y);
		memcpy(screen->text[screen->row] + screen->col, str, n);
		if (rstyle == 0)
			memset(screen->rend[screen->row] + screen->col, 0, n);
		else {
			for (i = 0; i < n; i++)
				screen->rend[screen->row][screen->col + i] = rstyle;
			screen->rend[screen->row][cwidth] = 1;
		}
		len -= n;
		str += n;
		screen->col += n;
		if (len > 0 && screen->col == cwidth && *str >= ' ') {
			if (screen->wrap) {
				screen->text[screen->row][cwidth] = 1;
				if (screen->row == screen->bmargin)
					scroll(screen->tmargin, screen->bmargin, 1);
				else
					screen->row++;
				screen->col = 0;
			} else {
				screen->col = cwidth - 1;
				cursor();
				return;
			}
		}
	}
	if (screen->col == cwidth) {
		screen->col = cwidth - 1;
		screen->wrap_next = screen->wrap;
	}
	cursor();
}
/*  Move the cursor to a new position.  The relative argument is a pair of
 *  flags that specify relative rather than absolute motion.
 */
void
scr_move(x, y, relative)
	int x, y, relative;
{
	home_screen();
	cursor();
	screen->col = (relative & COL_RELATIVE) ? screen->col + x : x;
	if (screen->col < 0)
		screen->col = 0;
	if (screen->col >= cwidth)
		screen->col = cwidth - 1;

	if (relative & ROW_RELATIVE) {
		if (y > 0) {
			if (screen->row <= screen->bmargin && screen->row + y > screen->bmargin)
				screen->row = screen->bmargin;
			else
				screen->row += y;
		} else if (y < 0) {
			if (screen->row >= screen->tmargin && screen->row + y < screen->tmargin)
				screen->row = screen->tmargin;
			else
				screen->row += y;
		}
	} else {
		if (screen->decom) {
			screen->row = y + screen->tmargin;
			if (screen->row > screen->bmargin)
				screen->row = screen->bmargin;
		} else
			screen->row = y;
	}
	if (screen->row < 0)
		screen->row = 0;
	if (screen->row >= cheight)
		screen->row = cheight - 1;

	screen->wrap_next = 0;
	check_selection(screen->row, screen->row);
	cursor();
}
/*  Move the cursor down one line and scroll if necessary.
 */
void
scr_index()
{
	home_screen();
	cursor();
	if (screen->row == screen->bmargin)
		scroll(screen->tmargin, screen->bmargin, 1);
	else
		screen->row++;
	screen->wrap_next = 0;
	check_selection(screen->row, screen->row);
	cursor();
}
/*  Move the cursor up one line and scroll if necessary.
 */
void
scr_rindex()
{
	home_screen();
	cursor();
	if (screen->row == screen->tmargin)
		scroll(screen->tmargin, screen->bmargin, -1);
	else
		screen->row--;
	screen->wrap_next = 0;
	check_selection(screen->row, screen->row);
	cursor();
}
/*  Save the cursor position and rendition style.
 */
void
scr_save_cursor()
{
	save_screen.row = screen->row;
	save_screen.col = screen->col;
	save_rstyle = rstyle;
}
/*  Restore the cursor position and rendition style.
 */
void
scr_restore_cursor()
{
	cursor();
	screen->row = save_screen.row;
	if (screen->row >= cheight)
		screen->row = cheight - 1;
	screen->col = save_screen.col;
	if (screen->col >= cwidth)
		screen->col = cwidth - 1;
	scr_change_rendition(save_rstyle);
	cursor();
}
/*  erase part or the whole of a line
 */
void
scr_erase_line(mode)
	int mode;
{
	int i, x, y, width, height;
	char *r, *s;

	home_screen();
	y = MARGIN + screen->row * fheight;
	height = fheight;
	s = screen->text[screen->row];
	r = screen->rend[screen->row];
	switch (mode) {
	case START:
		x = MARGIN;
		width = (screen->col + 1) * fwidth;
		memset(s, 0, screen->col + 1);
		memset(r, 0, screen->col + 1);
		break;
	case END:
		x = MARGIN + screen->col * fwidth;
		width = (cwidth - screen->col) * fwidth;
		memset(s + screen->col, 0, cwidth - screen->col + 1);
		memset(r + screen->col, 0, cwidth - screen->col);
		break;
	case ENTIRE:
		x = MARGIN;
		width = cwidth * fwidth;
		memset(s, 0, cwidth + 1);
		memset(r, 0, cwidth);
		break;
	default:
		return;
	}
	/* patch in the final rendition flag if there is any non-zero
	 * rendition. */
	r[cwidth] = 0;
	for (i = 0; i < cwidth; i++)
		if (r[i] != 0) {
			r[cwidth] = 1;
			break;
		}
	cursor();
	check_selection(screen->row, screen->row);
	XClearArea(display, vt_win, x, y, width, height, False);
	screen->wrap_next = 0;
	cursor();
}
/*  erase part or the whole of the screen
 */
void
scr_erase_screen(mode)
	int mode;
{
	int x, y, width, height;
	int i;

	home_screen();
	screen->wrap_next = 0;
	x = MARGIN;
	width = fwidth * cwidth;
	switch (mode) {
	case START:
		y = MARGIN;
		height = screen->row * fheight;
		for (i = 0; i < screen->row; i++) {
			memset(screen->text[i], 0, cwidth + 1);
			memset(screen->rend[i], 0, cwidth + 1);
		}
		check_selection(0, screen->row - 1);
		if (height > 0)
			XClearArea(display, vt_win, x, y, width, height, False);
		scr_erase_line(mode);
		break;
	case END:
		y = MARGIN + (screen->row + 1) * fheight;
		height = (cheight - screen->row - 1) * fheight;
		for (i = screen->row + 1; i < cheight; i++) {
			memset(screen->text[i], 0, cwidth + 1);
			memset(screen->rend[i], 0, cwidth + 1);
		}
		check_selection(screen->row + 1, cheight - 1);
		if (height > 0)
			XClearArea(display, vt_win, x, y, width, height, False);
		scr_erase_line(mode);
		break;
	case ENTIRE:
		y = MARGIN;
		height = cheight * fheight;
		for (i = 0; i < cheight; i++) {
			memset(screen->text[i], 0, cwidth + 1);
			memset(screen->rend[i], 0, cwidth + 1);
		}
		cursor();
		check_selection(0, cheight - 1);
		XClearArea(display, vt_win, x, y, width, height, False);
		cursor();
		break;
	default:
		return;
	}
}
/*  Delete count lines and scroll up the bottom of the screen to fill the gap
 */
void
scr_delete_lines(count)
	int count;
{
	if (count > screen->bmargin - screen->row + 1)
		return;

	home_screen();
	cursor();
	while (count > MAX_SCROLL) {
		scroll(screen->row, screen->bmargin, MAX_SCROLL);
		count -= MAX_SCROLL;
	}
	scroll(screen->row, screen->bmargin, count);
	screen->wrap_next = 0;
	cursor();
}
/*  Insert count blank lines at the current position and scroll the lower lines
 *  down.
 */
void
scr_insert_lines(count)
	int count;
{
	if (screen->row > screen->bmargin)
		return;
	if (count > screen->bmargin - screen->row + 1)
		count = screen->bmargin - screen->row + 1;

	home_screen();
	cursor();
	while (count > MAX_SCROLL) {
		scroll(screen->row, screen->bmargin, -MAX_SCROLL);
		count -= MAX_SCROLL;
	}
	scroll(screen->row, screen->bmargin, -count);
	screen->wrap_next = 0;
	cursor();
}
/*  Delete count characters from the current position.
 */
void
scr_delete_characters(count)
	int count;
{
	int x1, x2, y, width, i;
	char *r, *s;

	if (count > cwidth - screen->col)
		count = cwidth - screen->col;
	if (count <= 0)
		return;

	home_screen();
	cursor();
	check_selection(screen->row, screen->row);
	s = screen->text[screen->row];
	r = screen->rend[screen->row];
	for (i = screen->col + count; i < cwidth; i++) {
		s[i - count] = s[i];
		r[i - count] = r[i];
	}
	memset(s + cwidth - count, 0, count);
	memset(r + cwidth - count, 0, count);
	y = MARGIN + screen->row * fheight;
	x2 = MARGIN + screen->col * fwidth;
	x1 = x2 + count * fwidth;
	width = (cwidth - count - screen->col) * fwidth;
	if (width > 0) {
		XCopyArea(display, vt_win, vt_win, gc, x1, y, width, fheight, x2, y);
		repair_damage();
	}
	x1 = x2 + width;
	width = count * fwidth;
	XClearArea(display, vt_win, x1, y, width, fheight, False);
	screen->wrap_next = 0;
	cursor();
}
/*  Insert count spaces from the current position.
 */
void
scr_insert_characters(count)
	int count;
{
	int x1, x2, y, width, i;
	char *r, *s;

	if (count > cwidth - screen->col)
		count = cwidth - screen->col;
	if (count <= 0)
		return;

	home_screen();
	cursor();
	check_selection(screen->row, screen->row);
	s = screen->text[screen->row];
	r = screen->rend[screen->row];
	for (i = cwidth - 1; i >= screen->col + count; i--) {
		s[i] = s[i - count];
		r[i] = r[i - count];
	}
	memset(s + screen->col, 0, count);
	memset(r + screen->col, 0, count);
	y = MARGIN + screen->row * fheight;
	x1 = MARGIN + screen->col * fwidth;
	x2 = x1 + count * fwidth;
	width = (cwidth - count - screen->col) * fwidth;
	if (width > 0)
		XCopyArea(display, vt_win, vt_win, negc, x1, y, width, fheight, x2, y);
	x1 = MARGIN + screen->col * fwidth;
	width = count * fwidth;
	XClearArea(display, vt_win, x1, y, width, fheight, False);
	screen->wrap_next = 0;
	cursor();
}
/*  Tab to the next tab_stop.
 */
void
scr_tab()
{
	home_screen();
	if (screen->col == cwidth - 1)
		return;
	cursor();
	check_selection(screen->row, screen->row);
	if (screen->text[screen->row][screen->col] == 0)
		screen->text[screen->row][screen->col] = '\t';
	screen->col++;
	while (screen->col % 8 != 0 && screen->col < cwidth - 1)
		screen->col++;
	cursor();
}
/*  Attempt to set the top ans bottom scroll margins.
 */
void
scr_set_margins(top, bottom)
	int top, bottom;
{
	if (top < 0)
		top = 0;
	if (bottom >= cheight)
		bottom = cheight - 1;
	if (top > bottom)
		return;

	screen->tmargin = top;
	screen->bmargin = bottom;
	scr_move(0, 0, 0);
}
/*  Set or unset automatic wrapping.
 */
void
scr_set_wrap(mode)
	int mode;
{
	screen->wrap = mode == HIGH;
}
/*  Set or unset margin origin mode.
 */
void
scr_set_decom(mode)
	int mode;
{
	screen->decom = mode == HIGH;
	scr_move(0, 0, 0);
}
/*  Set or unset automatic insert mode.
 */
void
scr_set_insert(mode)
	int mode;
{
	screen->insert = mode == HIGH;
}
/*  Fill the screen with 'E's - useful for testing.
 */
void
scr_efill()
{
	int x, y;

	for (y = 0; y < cheight; y++)
		for (x = 0; x < cwidth; x++) {
			screen->text[y][x] = 'E';
			screen->rend[y][x] = 0;
		}
	home_screen();
	check_selection(0, cheight - 1);
	refresh(0, cheight - 1, 0, cwidth - 1);
	cursor();
}
/*  Move the display so that line represented by scrollbar value y is at the top
 *  of the screen.
 */
void
scr_move_to(y)
	int y;
{
	int n, lnum;

	y = pheight - 1 - y;
	lnum = y * (cheight + sline_top - 1) / (pheight - 1);
	n = lnum - cheight + 1;
	change_offset(n);
}
/*  Move the display by a distance represented by the value.
 */
void
scr_move_by(y)
	int y;
{
	int n;

	if (y >= 0) {
		y = (y - MARGIN) / fheight;
		n = offset - y;
	} else {
		y = (-y - MARGIN) / fheight;
		n = offset + y;
	}
	change_offset(n);
}
/*  Make the selection currently delimited by the selection end markers.
 */
void
scr_make_selection(time)
	int time;
{
	if (save_selection() < 0)
		return;

	XSetSelectionOwner(display, XA_PRIMARY, vt_win, (Time) time);
	if (XGetSelectionOwner(display, XA_PRIMARY) != vt_win)
		error("Could not get primary selection");

	/* Place in CUT_BUFFER0 for backup. */
	XChangeProperty(display, DefaultRootWindow(display), XA_CUT_BUFFER0,
	    XA_STRING, 8, PropModeReplace, selection_text, selection_length);
}
/*  respond to a request for our current selection.
 */
void
scr_send_selection(time, requestor, target, property)
	int time, requestor, target, property;
{
	XEvent event;
	int status;

	event.xselection.type = SelectionNotify;
	event.xselection.selection = XA_PRIMARY;
	event.xselection.target = XA_STRING;
	event.xselection.requestor = requestor;
	event.xselection.time = time;
	if (target == XA_STRING) {
		XChangeProperty(display, requestor, property, XA_STRING, 8, PropModeReplace,
		    selection_text, selection_length);
		event.xselection.property = property;
	} else
		event.xselection.property = None;
	status = XSendEvent(display, requestor, False, 0, &event);
}
/*  Request the current primary selection
 */
void
scr_request_selection(time, x, y)
	int time;
	int x, y;
{
	Atom sel_property;

	/* First check that the release is within the window. */
	if (x < 0 || x >= pwidth || y < 0 || y >= pheight)
		return;

	if (selection_text != NULL) {

		/* The selection is internal */
		send_string(selection_text, selection_length);
		return;
	}
	if (XGetSelectionOwner(display, XA_PRIMARY) == None) {

		/* No primary selection so use the cut buffer. */
		Atom actual_type;
		int actual_format;
		unsigned long nitems, bytes_after, nread;
		unsigned char *data;

		nread = 0;
		do {
			if (XGetWindowProperty(display, DefaultRootWindow(display),
				XA_CUT_BUFFER0, nread / 4, PROP_SIZE, False,
				XA_STRING, &actual_type, &actual_format,
				&nitems, &bytes_after, &data) != Success)
				return;
			send_string(data, nitems);
			nread += nitems;
			XFree(data);
		} while (bytes_after > 0);
		return;
	}
	sel_property = XInternAtom(display, "VT_SELECTION", False);
	XConvertSelection(display, XA_PRIMARY, XA_STRING, sel_property, vt_win, time);
}
/*  Respond to a notification that a primary selection has been sent
 */
void
scr_paste_primary(time, window, property)
	int time, window, property;
{
	Atom actual_type;
	int actual_format;
	unsigned long nitems, bytes_after, nread;
	unsigned char *data;

	nread = 0;
	do {
		if (XGetWindowProperty(display, window, property, nread / 4, PROP_SIZE, True,
			AnyPropertyType, &actual_type, &actual_format,
			&nitems, &bytes_after, &data) != Success)
			return;
		if (actual_type != XA_STRING)
			return;
		send_string(data, nitems);
		nread += nitems;
		XFree(data);
	} while (bytes_after > 0);
}
/*  Clear the current selection.
 */
void
scr_clear_selection()
{
	if (selection_text != NULL) {
		free(selection_text);
		selection_text = NULL;
		selection_length = 0;
	}
	show_selection(0, cheight - 1, 0, cwidth - 1);
	selend1.se_type = selend2.se_type = NOSEL;
}
/*  Extend the selection.
 */
void
scr_extend_selection(x, y, drag)
	int x, y;
	int drag;
{
	int row, col, r1, r2, c1, c2;
	struct selst sesave1, sesave2;
	struct selst *se;

	if (selend1.se_type == NOSEL)
		return;

	col = (x - MARGIN) / fwidth;
	row = (y - MARGIN) / fheight;
	fix_rc(&row, &col);

	if (selend2.se_type == NOSEL) {
		rc_to_selend(row, col, &selend2);
		show_selection(0, cheight - 1, 0, cwidth - 1);
		return;
	}
	sesave1 = selend1;
	sesave2 = selend2;
	if (drag) {

		/* Anchor the start end. */
		selend1 = selanchor;
		rc_to_selend(row, col, &selend2);
		adjust_selection(&selend2);
	} else {
		selend_to_rc(&r1, &c1, &selend1);
		selend_to_rc(&r2, &c2, &selend2);

		/* Determine which is the nearest endpoint. */
		if (abs(r1 - row) < abs(r2 - row))
			se = &selend1;
		else if (abs(r2 - row) < abs(r1 - row))
			se = &selend2;
		else if (r1 == r2) {
			if (row < r1)
				se = (c1 < c2) ? &selend1 : &selend2;
			else if (row > r1)
				se = (c1 > c2) ? &selend1 : &selend2;
			else
				se = abs(c1 - col) < abs(c2 - col) ? &selend1 : &selend2;
		} else
			se = &selend2;
		rc_to_selend(row, col, se);
		adjust_selection(se);
	}

	change_selection(&sesave1, &sesave2);
}
/*  start a selection using the specified unit.
 */
void
scr_start_selection(x, y, unit)
	int x, y;
	enum selunit unit;
{
	int row, col;

	show_selection(0, cheight - 1, 0, cwidth - 1);
	col = (x - MARGIN) / fwidth;
	row = (y - MARGIN) / fheight;
	selection_unit = unit;
	fix_rc(&row, &col);
	rc_to_selend(row, col, &selanchor);
	selend2 = selend1 = selanchor;
	adjust_selection(&selend2);
	show_selection(0, cheight - 1, 0, cwidth - 1);
}
/*  Send the name of the current display to the command.
 */
void
scr_report_display()
{
	char *dname;
	static char hostname[32 + 6];

	dname = DisplayString(display);

	if (strncmp(dname, "unix:", 5) == 0) {
		gethostname(hostname, sizeof(hostname));
		cprintf("%s%s\n", hostname, dname + 4);
	} else
		cprintf("%s\n", dname);
}
/*  Report the current cursor position.
 */
void
scr_report_position()
{
	cprintf("\033[%d;%dR", screen->row + 1, screen->col + 1);
}
/*  Check for and repair any damage after copying an area of the window.
 */
static void
repair_damage(void)
{
	XEvent event;
	int row1, row2, col1, col2;

	do {
		/* Get the next graphics exposure or noexposure event. */
		XIfEvent(display, &event, grexornoex, NULL);
		if (event.type == NoExpose)
			return;

		row1 = (event.xgraphicsexpose.y - MARGIN) / fheight;
		if (row1 < 0)
			row1 = 0;
		row2 = (event.xgraphicsexpose.y + event.xgraphicsexpose.height - MARGIN) / fheight;
		if (row2 >= cheight)
			row2 = cheight - 1;
		col1 = (event.xgraphicsexpose.x - MARGIN) / fwidth;
		if (col1 < 0)
			col1 = 0;
		col2 = (event.xgraphicsexpose.x + event.xgraphicsexpose.width - MARGIN) / fwidth;
		if (col2 >= cwidth)
			col2 = cwidth - 1;
		refresh(row1, row2, col1, col2);
	} while (event.xgraphicsexpose.count > 0);
}
/*  Return true if the event is a graphics exposure or noexposure.
 */
static Bool
grexornoex(dpy, ev, arg)
	Display *dpy;
	XEvent *ev;
	char *arg;
{
	return (ev->type == GraphicsExpose || ev->type == NoExpose);
}
/*  Change the value of the scrolled screen offset and repaint the screen
 */
static void
change_offset(n)
	int n;
{
	int y1, y2, height, d;

	if (n > sline_top)
		n = sline_top;
	if (n < 0)
		n = 0;
	if (n == offset)
		return;
	cursor();
	d = n - offset;
	offset = n;
	if (d > 0 && d < cheight) {
		/* Text has moved down by less than a screen so raster the
		 * lines that did not move off. */
		y1 = MARGIN;
		y2 = y1 + d * fheight;
		height = (cheight - d) * fheight;
		XCopyArea(display, vt_win, vt_win, gc, 0, y1, pwidth, height, 0, y2);
		refresh(0, d - 1, 0, cwidth - 1);
		repair_damage();
	} else if (d < 0 && -d < cheight) {
		/* Text has moved down by less than a screen. */
		d = -d;
		y2 = MARGIN;
		y1 = y2 + d * fheight;
		height = (cheight - d) * fheight;
		XCopyArea(display, vt_win, vt_win, gc, 0, y1, pwidth, height, 0, y2);
		refresh(cheight - d, cheight - 1, 0, cwidth - 1);
		repair_damage();
	} else
		refresh(0, cheight - 1, 0, cwidth - 1);
	cursor();
	sbar_show(cheight + sline_top - 1, offset, offset + cheight - 1);
}
/*  Paint the text using the rendition value at the screen position.
 */
static void
paint_rval_text(str, rval, len, x, y)
	char *str;
	int rval;
	int len, x, y;
{
	if (rval & RS_RVID) {
		XSetForeground(display, gc, background);
		XSetBackground(display, gc, foreground);
	}
	if (rval & RS_BOLD && boldfont != NULL) {
		XSetFont(display, gc, boldfont->fid);
		y += boldfont->ascent;
	} else
		y += mainfont->ascent;

	XDrawImageString(display, vt_win, gc, x, y, str, len);

	y++;
	if (rval & RS_ULINE)
		XDrawLine(display, vt_win, gc, x, y, x + len * fwidth, y);

	if (rval & RS_RVID) {
		XSetForeground(display, gc, foreground);
		XSetBackground(display, gc, background);
	}
	if (rval & RS_BOLD)
		XSetFont(display, gc, mainfont->fid);
}
/* Display the string using the rendition vector at the screen coordinates
 */
static void
paint_rvec_text(str, rvec, len, x, y)
	char *str, *rvec;
	int len, x, y;
{
	int i;

	if (rvec == NULL) {
		paint_rval_text(str, 0, len, x, y);
		return;
	}
	while (len > 0) {
		for (i = 0; i < len; i++)
			if (rvec[i] != rvec[0])
				break;
		paint_rval_text(str, rvec[0], i, x, y);
		str += i;
		rvec += i;
		len -= i;
		x += i * fwidth;
	}
}
/* Repaint the box delimited by row1 to row2 and col1 to col2 of the displayed
 * screen from the backup screen.
 */
static void
refresh(row1, row2, col1, col2)
	int row1, row2, col1, col2;
{
	char *s, *str, *r;
	int x, y, x1, y1, x2, width, i, m;
	struct slinest *sl;

	str = (char *) cmalloc(cwidth + 1);
	y = row1;
	x1 = MARGIN + col1 * fwidth;
	y1 = MARGIN + row1 * fheight;

	/* First do any 'scrolled off' lines that are visible. */
	for (i = offset - 1 - row1; y <= row2 && i >= 0; y++, i--) {
		sl = sline[i];
		m = (col2 + 1) < sl->sl_length ? (col2 + 1) : sl->sl_length;
		s = sl->sl_text;
		m -= col1;
		for (x = 0; x < m; x++)
			str[x] = s[x + col1] < ' ' ? ' ' : s[x + col1];
		r = sl->sl_rend == NULL ? NULL : sl->sl_rend + col1;
		paint_rvec_text(str, r, m, x1, y1);
		x2 = x1 + m * fwidth;
		width = (col2 - col1 + 1 - m) * fwidth;
		if (width > 0)
			XClearArea(display, vt_win, x2, y1, width, fheight, False);
		y1 += fheight;
	}


	/* Now do the remainder from the current screen */
	i = offset > row1 ? 0 : row1 - offset;
	for (; y <= row2; y++, i++) {
		s = screen->text[i];
		m = col1 - 1;
		for (x = col1; x <= col2; x++)
			if (s[x] < ' ')
				str[x - col1] = ' ';
			else {
				str[x - col1] = s[x];
				m = x;
			}
		m++;
		m -= col1;
		r = screen->rend[i][cwidth] == 0 ? NULL : screen->rend[i] + col1;
		paint_rvec_text(str, r, m, x1, y1);
		x2 = x1 + m * fwidth;
		width = (col2 - col1 + 1 - m) * fwidth;
		if (width > 0)
			XClearArea(display, vt_win, x2, y1, width, fheight, False);
		y1 += fheight;
	}
	free(str);
	show_selection(row1, row2, col1, col2);
}
/*  Paint any part of the selection that is between rows row1 and row2 inclusive
 *  and between cols col1 and col2 inclusive.
 */
static void
show_selection(row1, row2, col1, col2)
	int row1, row2, col1, col2;
{
	int r1, c1, r2, c2, sr, sc, er, ec;
	int x1, x2, y, row;

	if (selend1.se_type == NOSEL || selend2.se_type == NOSEL)
		return;
	if (selcmp(&selend1, &selend2) == 0)
		return;
	selend_to_rc(&r1, &c1, &selend1);
	selend_to_rc(&r2, &c2, &selend2);

	col2++;

	/* Obtain initial and final endpoints for the selection. */
	if (r1 < r2 || (r1 == r2 && c1 <= c2)) {
		sr = r1;
		sc = c1;
		er = r2;
		ec = c2;
	} else {
		sr = r2;
		sc = c2;
		er = r1;
		ec = c1;
	}
	if (sr < row1) {
		sr = row1;
		sc = col1;
	}
	if (sc < col1)
		sc = col1;
	if (er > row2) {
		er = row2;
		ec = col2;
	}
	if (ec > col2)
		ec = col2;

	if (sr > er)
		return;

	/* Paint in the reverse video. */
	for (row = sr; row <= er; row++) {
		y = MARGIN + row * fheight;
		x1 = MARGIN + (row == sr ? sc : col1) * fwidth;
		x2 = MARGIN + ((row == er) ? ec : col2) * fwidth;
		if (x2 > x1)
			XFillRectangle(display, vt_win, hlgc, x1, y, x2 - x1, fheight);
	}
}
/*  Scroll count lines from row1 to row2 inclusive.  row1 should be <= row1.
 *  scrolling is up for a +ve count and down for a -ve count.
 *  count is limited to a maximum of MAX_SCROLL lines.
 */
static void
scroll(row1, row2, count)
	int row1, row2, count;
{
	int y1, y2, height, i, j;
	char *save[MAX_SCROLL], *rend[MAX_SCROLL];
	struct slinest *sl;
	char *r, *s;

	row2++;

	if (row1 == 0 && screen == &screen1 && count > 0) {

		/* Save lines that scroll of the top of the screen. */
		for (i = 1; i <= count; i++) {
			if ((sl = sline[sline_max - i]) != NULL) {
				free(sl->sl_text);
				if (sl->sl_rend != NULL)
					free(sl->sl_rend);
				free((char *) sl);
			}
			if (selend1.se_type == SAVED && selend1.se_index == sline_max - i) {
				show_selection(0, cheight - 1, 0, cwidth - 1);
				selend1.se_type = NOSEL;
			}
			if (selend2.se_type == SAVED && selend2.se_index == sline_max - i) {
				show_selection(0, cheight - 1, 0, cwidth - 1);
				selend2.se_type = NOSEL;
			}
		}
		for (i = sline_max - count - 1; i >= 0; i--) {
			sline[i + count] = sline[i];
			if (selend1.se_type == SAVED && selend1.se_index == i)
				selend1.se_index = i + count;
			if (selend2.se_type == SAVED && selend2.se_index == i)
				selend2.se_index = i + count;
		}
		for (i = 0; i < count; i++) {
			s = screen->text[i];
			r = screen->rend[i];
			for (j = cwidth - 1; j >= 0 && s[j] == 0; j--);
			j++;
			sl = (struct slinest *) cmalloc(sizeof(struct slinest));
			sl->sl_text = (char *) cmalloc(j + 1);
			memcpy(sl->sl_text, s, j);
			sl->sl_text[j] = s[cwidth];
			if (r[cwidth] == 0)
				sl->sl_rend = NULL;
			else {
				sl->sl_rend = (char *) cmalloc(j + 1);
				memcpy(sl->sl_rend, r, j);
			}
			sl->sl_length = j;
			sline[count - i - 1] = sl;
			if (selend1.se_type == SCREEN && selend1.se_index == i) {
				selend1.se_type = SAVED;
				selend1.se_index = count - i - 1;
			}
			if (selend2.se_type == SCREEN && selend2.se_index == i) {
				selend2.se_type = SAVED;
				selend2.se_index = count - i - 1;
			}
		}
		sline_top += count;
		if (sline_top > sline_max)
			sline_top = sline_max;
		sbar_show(cheight + sline_top - 1, offset, offset + cheight - 1);
	}
	if (count > 0) {
		j = row1;
		for (i = 0; i < count; i++, j++) {
			save[i] = screen->text[j];
			rend[i] = screen->rend[j];
			if (selend1.se_type == SCREEN && selend1.se_index == j) {
				show_selection(0, cheight - 1, 0, cwidth - 1);
				selend1.se_type = NOSEL;
			}
			if (selend2.se_type == SCREEN && selend2.se_index == j) {
				show_selection(0, cheight - 1, 0, cwidth - 1);
				selend2.se_type = NOSEL;
			}
		}
		for (; j < row2; j++) {
			screen->text[j - count] = screen->text[j];
			screen->rend[j - count] = screen->rend[j];
			if (selend1.se_type == SCREEN && selend1.se_index == j)
				selend1.se_index = j - count;
			if (selend2.se_type == SCREEN && selend2.se_index == j)
				selend2.se_index = j - count;
		}
		for (i = 0; i < count; i++) {
			memset(save[i], 0, cwidth + 1);
			screen->text[row2 - i - 1] = save[i];
			memset(rend[i], 0, cwidth + 1);
			screen->rend[row2 - i - 1] = rend[i];
		}
		if (count < row2 - row1) {
			y2 = MARGIN + row1 * fheight;
			y1 = y2 + count * fheight;
			height = (row2 - row1 - count) * fheight;
			XCopyArea(display, vt_win, vt_win, gc, 0, y1, pwidth, height, 0, y2);
			repair_damage();
		}
		height = count * fheight;
		y1 = MARGIN + (row2 - count) * fheight;
		XClearArea(display, vt_win, 0, y1, pwidth, height, False);
	}
	if (count < 0) {
		count = -count;
		j = row2 - 1;
		for (i = 0; i < count; i++, j--) {
			save[i] = screen->text[j];
			rend[i] = screen->rend[j];
			if (selend1.se_type == SCREEN && selend1.se_index == j) {
				show_selection(0, cheight - 1, 0, cwidth - 1);
				selend1.se_type = NOSEL;
			}
			if (selend2.se_type == SCREEN && selend2.se_index == j) {
				show_selection(0, cheight - 1, 0, cwidth - 1);
				selend2.se_type = NOSEL;
			}
		}
		for (; j >= row1; j--) {
			screen->text[j + count] = screen->text[j];
			screen->rend[j + count] = screen->rend[j];
			if (selend1.se_type == SCREEN && selend1.se_index == j)
				selend1.se_index = j + count;
			if (selend2.se_type == SCREEN && selend2.se_index == j)
				selend2.se_index = j + count;
		}
		for (i = 0; i < count; i++) {
			memset(save[i], 0, cwidth + 1);
			screen->text[row1 + i] = save[i];
			memset(rend[i], 0, cwidth + 1);
			screen->rend[row1 + i] = rend[i];
		}
		if (count < row2 - row1) {
			y1 = MARGIN + row1 * fheight;
			y2 = y1 + count * fheight;
			height = (row2 - row1 - count) * fheight;
			XCopyArea(display, vt_win, vt_win, gc, 0, y1, pwidth, height, 0, y2);
			repair_damage();
		}
		height = count * fheight;
		y1 = MARGIN + row1 * fheight;
		XClearArea(display, vt_win, 0, y1, pwidth, height, False);
	}
}
/*  Scroll screen1 up by count lines saving lines as needed.  This is used
 *  after the screen size is reduced.
 */
static void
scroll1(count)
	int count;
{
	int i, j, n;
	char *save[MAX_SCROLL], *rend[MAX_SCROLL];
	struct slinest *sl;
	char *r, *s;

	while (count > 0) {

		/* If count is greater than MAX_SCROLL then scroll in
		 * installements. */
		n = count > MAX_SCROLL ? MAX_SCROLL : count;
		count -= n;

		/* Save lines that scroll of the top of the screen. */
		for (i = 1; i <= n; i++) {
			if ((sl = sline[sline_max - i]) != NULL) {
				free(sl->sl_text);
				if (sl->sl_rend != NULL)
					free(sl->sl_rend);
				free((char *) sl);
			}
		}
		for (i = sline_max - n - 1; i >= 0; i--) {
			sline[i + n] = sline[i];
		}
		for (i = 0; i < n; i++) {
			s = screen1.text[i];
			r = screen1.rend[i];
			for (j = cwidth - 1; j >= 0 && s[j] == 0; j--);
			j++;
			sl = (struct slinest *) cmalloc(sizeof(struct slinest));
			sl->sl_text = (char *) cmalloc(j + 1);
			memcpy(sl->sl_text, s, j);
			sl->sl_text[j] = s[cwidth];
			if (r[cwidth] == 0)
				sl->sl_rend = NULL;
			else {
				sl->sl_rend = (char *) cmalloc(j + 1);
				memcpy(sl->sl_rend, r, j);
			}
			sl->sl_length = j;
			sline[n - i - 1] = sl;
		}
		sline_top += n;
		if (sline_top > sline_max)
			sline_top = sline_max;

		j = 0;
		for (i = 0; i < n; i++, j++) {
			save[i] = screen1.text[j];
			rend[i] = screen1.rend[j];
		}
		for (; j < cheight; j++) {
			screen1.text[j - n] = screen1.text[j];
			screen1.rend[j - n] = screen1.rend[j];
		}
		for (i = 0; i < n; i++) {
			memset(save[i], 0, cwidth + 1);
			screen1.text[cheight - i - 1] = save[i];
			memset(rend[i], 0, cwidth + 1);
			screen1.rend[cheight - i - 1] = rend[i];
		}
	}
}
/*  Reposition the scrolled text so that the scrollbar is at the bottom.
 */
static void
home_screen(void)
{
	if (offset > 0) {
		offset = 0;
		refresh(0, cheight - 1, 0, cwidth - 1);
		cursor();
		sbar_show(cheight + sline_top - 1, 0, cheight - 1);
	}
}
/*  Draw the cursor at the current position.
 */
static void
cursor(void)
{
	int x, y;

	if (offset > 0)
		return;

	x = MARGIN + fwidth * screen->col;
	y = MARGIN + fheight * screen->row;
	XFillRectangle(display, vt_win, hlgc, x, y, fwidth, fheight);
	if (focus == 0)
		XFillRectangle(display, vt_win, hlgc, x + 1, y + 1, fwidth - 2, fheight - 2);
}
/*  Convert a row and column coordinates into a selection endpoint.
 */
static void
rc_to_selend(row, col, se)
	int row, col;
	struct selst *se;
{
	int i;

	i = (row - offset);
	if (i >= 0)
		se->se_type = SCREEN;
	else {
		se->se_type = SAVED;
		i = -1 - i;
	}
	se->se_index = i;
	se->se_col = col;
}
/*  Fix the coordinates so that they are within the screen and do not lie within
 *  empty space.
 */
static void
fix_rc(rowp, colp)
	int *rowp, *colp;
{
	int i, len, row, col;
	char *s;

	col = *colp;
	if (col < 0)
		col = 0;
	if (col > cwidth)
		col = cwidth;
	row = *rowp;
	if (row < 0)
		row = 0;
	if (row >= cheight)
		row = cheight - 1;

	if (selection_unit == CHAR) {
		i = (row - offset);
		if (i >= 0) {
			s = screen->text[i];
			if (col > 0 && s[col - 1] < ' ')
				while (col < cwidth && s[col] < ' ')
					col++;
		} else {
			i = -1 - i;
			len = sline[i]->sl_length;
			s = sline[i]->sl_text;
			if (col > 0 && s[col - 1] < ' ')
				while (col <= len && s[col] < ' ')
					col++;
			if (col > len)
				col = cwidth;
		}
	}
	*colp = col;
	*rowp = row;
}
/*  Convert the selection into a row and column.
 */
static void
selend_to_rc(rowp, colp, se)
	int *rowp, *colp;
	struct selst *se;
{
	if (se->se_type == NOSEL)
		return;

	*colp = se->se_col;
	if (se->se_type == SCREEN)
		*rowp = se->se_index + offset;
	else
		*rowp = offset - se->se_index - 1;
}
/*  Repaint the displayed selection to reflect the new value.  ose1 and ose2
 *  are assumed to represent the currently displayed selection endpoints.
 */
static void
change_selection(ose1, ose2)
	struct selst *ose1, *ose2;
{
	int rs, cs, re, ce, n;
	int row;
	int row1, row2;
	int x1, x2, y;
	struct selst *se, *se1, *se2;

	if (selcmp(ose1, ose2) > 0) {
		se = ose1;
		ose1 = ose2;
		ose2 = se;
	}
	if (selcmp(&selend1, &selend2) <= 0) {
		se1 = &selend1;
		se2 = &selend2;
	} else {
		se1 = &selend2;
		se2 = &selend1;
	}

	if ((n = selcmp(se1, ose1)) != 0) {

		/* repaint the start. */
		if (n < 0) {
			selend_to_rc(&rs, &cs, se1);
			selend_to_rc(&re, &ce, ose1);
		} else {
			selend_to_rc(&rs, &cs, ose1);
			selend_to_rc(&re, &ce, se1);
		}
		row1 = rs < 0 ? 0 : rs;
		row2 = re >= cheight ? cheight - 1 : re;

		/* Invert the changed area */
		for (row = row1; row <= row2; row++) {
			y = MARGIN + row * fheight;
			x1 = MARGIN + (row == rs ? cs * fwidth : 0);
			x2 = MARGIN + ((row == re) ? ce : cwidth) * fwidth;
			XFillRectangle(display, vt_win, hlgc, x1, y, x2 - x1, fheight);
		}
	}
	if ((n = selcmp(se2, ose2)) != 0) {

		/* repaint the end. */
		if (n < 0) {
			selend_to_rc(&rs, &cs, se2);
			selend_to_rc(&re, &ce, ose2);
		} else {
			selend_to_rc(&rs, &cs, ose2);
			selend_to_rc(&re, &ce, se2);
		}
		row1 = rs < 0 ? 0 : rs;
		row2 = re >= cheight ? cheight - 1 : re;

		/* Invert the changed area */
		for (row = row1; row <= row2; row++) {
			y = MARGIN + row * fheight;
			x1 = MARGIN + (row == rs ? cs * fwidth : 0);
			x2 = MARGIN + ((row == re) ? ce : cwidth) * fwidth;
			XFillRectangle(display, vt_win, hlgc, x1, y, x2 - x1, fheight);
		}
	}
}
/*  Convert a section of displayed text line into a text string suitable for pasting.
 *  *lenp is the length of the input string, i1 is index of the first character to
 *  convert and i2 is the last.  The length of the returned string is returned
 *  in *lenp;
 */
static char *
convert_line(str, lenp, i1, i2)
	char *str;
	int *lenp;
	int i1, i2;
{
	static char buf[MAX_WIDTH + 3];
	char *s;
	int i;
	int newline;

	newline = (i2 + 1 == cwidth) && (str[*lenp] == 0);
	if (i2 >= *lenp)
		i2 = *lenp - 1;
	if (i2 - i1 >= MAX_WIDTH)
		i2 = i1 + MAX_WIDTH;
	while (i2 >= i1 && str[i2] == 0)
		i2--;
	s = buf;
	for (i = i1; i <= i2; i++) {
		if (str[i] >= ' ')
			*s++ = str[i];
		else if (str[i] == '\t') {
			*s++ = '\t';
			while (i < i2 && str[i + 1] == 0)
				i++;
		} else
			*s++ = ' ';
	}
	if (newline)
		*s++ = '\n';
	*s = 0;
	*lenp = s - buf;
	return (buf);
}
/*  Compare the two selections and return -1, 0 or 1 depending on
 *  whether se2 is after, equal to or before se1.
 */
static int
selcmp(se1, se2)
	struct selst *se1, *se2;
{
	if (se1->se_type == SAVED && se2->se_type == SAVED) {
		if (se1->se_index > se2->se_index)
			return (-1);
		if (se1->se_index < se2->se_index)
			return (1);
		if (se1->se_col < se2->se_col)
			return (-1);
		if (se2->se_col < se1->se_col)
			return (1);
		return (0);
	}
	if (se1->se_type == SCREEN && se2->se_type == SCREEN) {
		if (se1->se_index < se2->se_index)
			return (-1);
		if (se1->se_index > se2->se_index)
			return (1);
		if (se1->se_col < se2->se_col)
			return (-1);
		if (se2->se_col < se1->se_col)
			return (1);
		return (0);
	}
	if (se1->se_type == SAVED)
		return (-1);
	return (1);
}
/*  Adjust the selection to a word or line boundary. If the include endpoint is
 *  non NULL then the selection is forced to be large enough to include it.
 */
static void
adjust_selection(include)
	struct selst *include;
{
	struct selst *se1, *se2;
	int i, len;
	char *s;

	if (selection_unit == CHAR)
		return;

	if (selcmp(&selend1, &selend2) <= 0) {
		se1 = &selend1;
		se2 = &selend2;
	} else {
		se2 = &selend1;
		se1 = &selend2;
	}
	if (selection_unit == WORD) {
		i = se1->se_col;
		s = se1->se_type == SCREEN
		    ? screen->text[se1->se_index]
		    : sline[se1->se_index]->sl_text;
		while (i > 0 && cclass(s[i]) == cclass(s[i - 1]))
			i--;
		se1->se_col = i;
		i = se2->se_col;
		if (se2 == include || selcmp(se2, &selanchor) == 0)
			i++;
		if (se2->se_type == SCREEN) {
			s = screen->text[se2->se_index];
			len = cwidth;
		} else {
			s = sline[se2->se_index]->sl_text;
			len = sline[se2->se_index]->sl_length;
		}
		while (i < len && cclass(s[i]) == cclass(s[i - 1]))
			i++;
		se2->se_col = (i > len) ? cwidth : i;
	} else if (selection_unit == LINE) {
		se1->se_col = 0;
		se2->se_col = cwidth;
	}
}
/*  Convert the currently marked screen selection as a text string and save it
 *  as the current saved selection.  0 is returned for a success, -1 for a failure.
 */
static int
save_selection(void)
{
	char *str, *s;
	int i, len, total, col1, col2;
	struct selst *se1, *se2;
	struct slinest *sl;

	if (selend1.se_type == NOSEL || selend2.se_type == NOSEL)
		return (-1);
	if (selend1.se_type == selend2.se_type
	    && selend1.se_index == selend2.se_index
	    && selend1.se_col == selend2.se_col)
		return (-1);

	if (selection_text != NULL)
		free(selection_text);

	/* Set se1 and se2 to point to the first and second selection
	 * endpoints. */
	if (selcmp(&selend1, &selend2) <= 0) {
		se1 = &selend1;
		se2 = &selend2;
	} else {
		se2 = &selend1;
		se1 = &selend2;
	}
	str = (char *) cmalloc(total = 1);
	if (se1->se_type == SAVED) {
		col1 = se1->se_col;
		for (i = se1->se_index; i >= 0; i--) {
			sl = sline[i];
			if (se2->se_type == SAVED && se2->se_index == i) {
				col2 = se2->se_col - 1;
				i = 0;	/* force loop exit */
			} else
				col2 = cwidth - 1;
			len = sl->sl_length;
			s = convert_line(sl->sl_text, &len, col1, col2);
			str = realloc(str, total + len);
			strncpy(str + total - 1, s, len);
			total += len;
			col1 = 0;
		}
	}
	if (se2->se_type == SCREEN) {
		if (se1->se_type == SCREEN) {
			i = se1->se_index;
			col1 = se1->se_col;
		} else {
			i = 0;
			col1 = 0;
		}
		for (; i <= se2->se_index; i++) {
			col2 = i == se2->se_index ? se2->se_col : cwidth;
			if (--col2 < 0)
				break;
			len = cwidth;
			s = convert_line(screen->text[i], &len, col1, col2);
			str = realloc(str, total + len);
			strncpy(str + total - 1, s, len);
			total += len;
			col1 = 0;
		}
	}
	str[total - 1] = 0;
	selection_text = str;
	selection_length = total - 1;
	return (0);
}
/*  Determine if the current selection overlaps row1-row2 and if it does then
 *  remove it from the screen.
 */
static void
check_selection(row1, row2)
	int row1, row2;
{
	int r1, r2, x;

	if (selend1.se_type == NOSEL || selend2.se_type == NOSEL)
		return;

	r1 = selend1.se_type == SCREEN ? selend1.se_index : -1;
	r2 = selend2.se_type == SCREEN ? selend2.se_index : -1;
	if (r1 > r2) {
		x = r1;
		r1 = r2;
		r2 = x;
	}
	if (row2 < r1 || row1 > r2)
		return;
	show_selection(0, cheight - 1, 0, cwidth - 1);
	selend2.se_type = NOSEL;
}
/*  Return a character class for selecting words
 */
static int
cclass(c)
	int c;
{
	return (char_class[c]);
}


syntax highlighted by Code2HTML, v. 0.9.1