+ for (i = cols; i < oldcols; i++)
+ clear_cc(line, i);
+
+ /*
+ * If we're shrinking the line, we now bodily move the
+ * entire cc section from where it started to where it now
+ * needs to be. (We have to do this before the resize, so
+ * that the data we're copying is still there. However, if
+ * we're expanding, we have to wait until _after_ the
+ * resize so that the space we're copying into is there.)
+ */
+ if (cols < oldcols)
+ memmove(line->chars + cols, line->chars + oldcols,
+ (line->size - line->cols) * TSIZE);
+
+ /*
+ * Now do the actual resize, leaving the _same_ amount of
+ * cc space as there was to begin with.
+ */
+ line->size += cols - oldcols;
+ line->chars = sresize(line->chars, line->size, TTYPE);
+ line->cols = cols;
+
+ /*
+ * If we're expanding the line, _now_ we move the cc
+ * section.
+ */
+ if (cols > oldcols)
+ memmove(line->chars + cols, line->chars + oldcols,
+ (line->size - line->cols) * TSIZE);
+
+ /*
+ * Go through what's left of the original line, and adjust
+ * the first cc_next pointer in each list. (All the
+ * subsequent ones are still valid because they are
+ * relative offsets within the cc block.) Also do the same
+ * to the head of the cc_free list.
+ */
+ for (i = 0; i < oldcols && i < cols; i++)
+ if (line->chars[i].cc_next)
+ line->chars[i].cc_next += cols - oldcols;
+ if (line->cc_free)
+ line->cc_free += cols - oldcols;
+
+ /*
+ * And finally fill in the new space with erase chars. (We
+ * don't have to worry about cc lists here, because we
+ * _know_ the erase char doesn't have one.)
+ */
+ for (i = oldcols; i < cols; i++)
+ line->chars[i] = term->basic_erase_char;
+
+#ifdef TERM_CC_DIAGS
+ cc_check(line);
+#endif
+ }
+}
+
+/*
+ * Get the number of lines in the scrollback.
+ */
+static int sblines(Terminal *term)
+{
+ int sblines = count234(term->scrollback);
+ if (term->cfg.erase_to_scrollback &&
+ term->alt_which && term->alt_screen) {
+ sblines += term->alt_sblines;
+ }
+ return sblines;
+}
+
+/*
+ * Retrieve a line of the screen or of the scrollback, according to
+ * whether the y coordinate is non-negative or negative
+ * (respectively).
+ */
+static termline *lineptr(Terminal *term, int y, int lineno, int screen)
+{
+ termline *line;
+ tree234 *whichtree;
+ int treeindex;
+
+ if (y >= 0) {
+ whichtree = term->screen;
+ treeindex = y;
+ } else {
+ int altlines = 0;
+
+ assert(!screen);
+
+ if (term->cfg.erase_to_scrollback &&
+ term->alt_which && term->alt_screen) {
+ altlines = term->alt_sblines;
+ }
+ if (y < -altlines) {
+ whichtree = term->scrollback;
+ treeindex = y + altlines + count234(term->scrollback);
+ } else {
+ whichtree = term->alt_screen;
+ treeindex = y + term->alt_sblines;
+ /* treeindex = y + count234(term->alt_screen); */
+ }
+ }
+ if (whichtree == term->scrollback) {
+ unsigned char *cline = index234(whichtree, treeindex);
+ line = decompressline(cline, NULL);
+ } else {
+ line = index234(whichtree, treeindex);
+ }
+
+ /* We assume that we don't screw up and retrieve something out of range. */
+ if (line == NULL) {
+ fatalbox("line==NULL in terminal.c\n"
+ "lineno=%d y=%d w=%d h=%d\n"
+ "count(scrollback=%p)=%d\n"
+ "count(screen=%p)=%d\n"
+ "count(alt=%p)=%d alt_sblines=%d\n"
+ "whichtree=%p treeindex=%d\n\n"
+ "Please contact <putty@projects.tartarus.org> "
+ "and pass on the above information.",
+ lineno, y, term->cols, term->rows,
+ term->scrollback, count234(term->scrollback),
+ term->screen, count234(term->screen),
+ term->alt_screen, count234(term->alt_screen), term->alt_sblines,
+ whichtree, treeindex);
+ }
+ assert(line != NULL);
+
+ resizeline(term, line, term->cols);
+ /* FIXME: should we sort the compressed scrollback out here? */
+
+ return line;
+}
+
+#define lineptr(x) (lineptr)(term,x,__LINE__,FALSE)
+#define scrlineptr(x) (lineptr)(term,x,__LINE__,TRUE)
+
+static void term_schedule_tblink(Terminal *term);
+static void term_schedule_cblink(Terminal *term);
+
+static void term_timer(void *ctx, long now)
+{
+ Terminal *term = (Terminal *)ctx;
+ int update = FALSE;
+
+ if (term->tblink_pending && now - term->next_tblink >= 0) {
+ term->tblinker = !term->tblinker;
+ term->tblink_pending = FALSE;
+ term_schedule_tblink(term);
+ update = TRUE;
+ }
+
+ if (term->cblink_pending && now - term->next_cblink >= 0) {
+ term->cblinker = !term->cblinker;
+ term->cblink_pending = FALSE;
+ term_schedule_cblink(term);
+ update = TRUE;
+ }
+
+ if (term->in_vbell && now - term->vbell_end >= 0) {
+ term->in_vbell = FALSE;
+ update = TRUE;
+ }
+
+ if (update ||
+ (term->window_update_pending && now - term->next_update >= 0))
+ term_update(term);
+}
+
+static void term_schedule_update(Terminal *term)
+{
+ if (!term->window_update_pending) {
+ term->window_update_pending = TRUE;
+ term->next_update = schedule_timer(UPDATE_DELAY, term_timer, term);
+ }
+}
+
+/*
+ * Call this whenever the terminal window state changes, to queue
+ * an update.
+ */
+static void seen_disp_event(Terminal *term)
+{
+ term->seen_disp_event = TRUE; /* for scrollback-reset-on-activity */
+ term_schedule_update(term);
+}
+
+/*
+ * Call when the terminal's blinking-text settings change, or when
+ * a text blink has just occurred.
+ */
+static void term_schedule_tblink(Terminal *term)
+{
+ if (term->blink_is_real) {
+ if (!term->tblink_pending)
+ term->next_tblink = schedule_timer(TBLINK_DELAY, term_timer, term);
+ term->tblink_pending = TRUE;
+ } else {
+ term->tblinker = 1; /* reset when not in use */
+ term->tblink_pending = FALSE;
+ }
+}
+
+/*
+ * Likewise with cursor blinks.
+ */
+static void term_schedule_cblink(Terminal *term)
+{
+ if (term->cfg.blink_cur && term->has_focus) {
+ if (!term->cblink_pending)
+ term->next_cblink = schedule_timer(CBLINK_DELAY, term_timer, term);
+ term->cblink_pending = TRUE;
+ } else {
+ term->cblinker = 1; /* reset when not in use */
+ term->cblink_pending = FALSE;
+ }
+}
+
+/*
+ * Call to reset cursor blinking on new output.
+ */
+static void term_reset_cblink(Terminal *term)
+{
+ seen_disp_event(term);
+ term->cblinker = 1;
+ term->cblink_pending = FALSE;
+ term_schedule_cblink(term);
+}
+
+/*
+ * Call to begin a visual bell.
+ */
+static void term_schedule_vbell(Terminal *term, int already_started,
+ long startpoint)
+{
+ long ticks_already_gone;
+
+ if (already_started)
+ ticks_already_gone = GETTICKCOUNT() - startpoint;
+ else
+ ticks_already_gone = 0;
+
+ if (ticks_already_gone < VBELL_DELAY) {
+ term->in_vbell = TRUE;
+ term->vbell_end = schedule_timer(VBELL_DELAY - ticks_already_gone,
+ term_timer, term);
+ } else {
+ term->in_vbell = FALSE;
+ }
+}
+
+/*
+ * Set up power-on settings for the terminal.
+ * If 'clear' is false, don't actually clear the primary screen, and
+ * position the cursor below the last non-blank line (scrolling if
+ * necessary).
+ */
+static void power_on(Terminal *term, int clear)
+{
+ term->alt_x = term->alt_y = 0;
+ term->savecurs.x = term->savecurs.y = 0;
+ term->alt_t = term->marg_t = 0;
+ if (term->rows != -1)
+ term->alt_b = term->marg_b = term->rows - 1;
+ else
+ term->alt_b = term->marg_b = 0;
+ if (term->cols != -1) {
+ int i;
+ for (i = 0; i < term->cols; i++)
+ term->tabs[i] = (i % 8 == 0 ? TRUE : FALSE);
+ }
+ term->alt_om = term->dec_om = term->cfg.dec_om;
+ term->alt_ins = term->insert = FALSE;
+ term->alt_wnext = term->wrapnext = term->save_wnext = FALSE;
+ term->alt_wrap = term->wrap = term->cfg.wrap_mode;
+ term->alt_cset = term->cset = term->save_cset = 0;
+ term->alt_utf = term->utf = term->save_utf = 0;
+ term->utf_state = 0;
+ term->alt_sco_acs = term->sco_acs = term->save_sco_acs = 0;
+ term->cset_attr[0] = term->cset_attr[1] = term->save_csattr = CSET_ASCII;
+ term->rvideo = 0;
+ term->in_vbell = FALSE;
+ term->cursor_on = 1;
+ term->big_cursor = 0;
+ term->default_attr = term->save_attr = term->curr_attr = ATTR_DEFAULT;
+ term->term_editing = term->term_echoing = FALSE;
+ term->app_cursor_keys = term->cfg.app_cursor;
+ term->app_keypad_keys = term->cfg.app_keypad;
+ term->use_bce = term->cfg.bce;
+ term->blink_is_real = term->cfg.blinktext;
+ term->erase_char = term->basic_erase_char;
+ term->alt_which = 0;
+ term_print_finish(term);
+ {
+ int i;
+ for (i = 0; i < 256; i++)
+ term->wordness[i] = term->cfg.wordness[i];
+ }
+ if (term->screen) {
+ swap_screen(term, 1, FALSE, FALSE);
+ erase_lots(term, FALSE, TRUE, TRUE);
+ swap_screen(term, 0, FALSE, FALSE);
+ if (clear)
+ erase_lots(term, FALSE, TRUE, TRUE);
+ term->curs.y = find_last_nonempty_line(term, term->screen) + 1;
+ if (term->curs.y == term->rows) {
+ term->curs.y--;
+ scroll(term, 0, term->rows - 1, 1, TRUE);
+ }
+ } else {
+ term->curs.y = 0;
+ }
+ term->curs.x = 0;
+ term_schedule_tblink(term);
+ term_schedule_cblink(term);
+}
+
+/*
+ * Force a screen update.
+ */
+void term_update(Terminal *term)
+{
+ Context ctx;
+
+ term->window_update_pending = FALSE;
+
+ ctx = get_ctx(term->frontend);
+ if (ctx) {
+ int need_sbar_update = term->seen_disp_event;
+ if (term->seen_disp_event && term->cfg.scroll_on_disp) {
+ term->disptop = 0; /* return to main screen */
+ term->seen_disp_event = 0;
+ need_sbar_update = TRUE;
+ }
+
+ if (need_sbar_update)
+ update_sbar(term);
+ do_paint(term, ctx, TRUE);
+ sys_cursor(term->frontend, term->curs.x, term->curs.y - term->disptop);
+ free_ctx(ctx);
+ }
+}
+
+/*
+ * Called from front end when a keypress occurs, to trigger
+ * anything magical that needs to happen in that situation.
+ */
+void term_seen_key_event(Terminal *term)
+{
+ /*
+ * On any keypress, clear the bell overload mechanism
+ * completely, on the grounds that large numbers of
+ * beeps coming from deliberate key action are likely
+ * to be intended (e.g. beeps from filename completion
+ * blocking repeatedly).
+ */
+ term->beep_overloaded = FALSE;
+ while (term->beephead) {
+ struct beeptime *tmp = term->beephead;
+ term->beephead = tmp->next;
+ sfree(tmp);
+ }
+ term->beeptail = NULL;
+ term->nbeeps = 0;
+
+ /*
+ * Reset the scrollback on keypress, if we're doing that.
+ */
+ if (term->cfg.scroll_on_key) {
+ term->disptop = 0; /* return to main screen */
+ seen_disp_event(term);
+ }
+}
+
+/*
+ * Same as power_on(), but an external function.
+ */
+void term_pwron(Terminal *term, int clear)
+{
+ power_on(term, clear);
+ if (term->ldisc) /* cause ldisc to notice changes */
+ ldisc_send(term->ldisc, NULL, 0, 0);
+ term->disptop = 0;
+ deselect(term);
+ term_update(term);
+}
+
+static void set_erase_char(Terminal *term)
+{
+ term->erase_char = term->basic_erase_char;
+ if (term->use_bce)
+ term->erase_char.attr = (term->curr_attr &
+ (ATTR_FGMASK | ATTR_BGMASK));
+}
+
+/*
+ * When the user reconfigures us, we need to check the forbidden-
+ * alternate-screen config option, disable raw mouse mode if the
+ * user has disabled mouse reporting, and abandon a print job if
+ * the user has disabled printing.
+ */
+void term_reconfig(Terminal *term, Config *cfg)
+{
+ /*
+ * Before adopting the new config, check all those terminal
+ * settings which control power-on defaults; and if they've
+ * changed, we will modify the current state as well as the
+ * default one. The full list is: Auto wrap mode, DEC Origin
+ * Mode, BCE, blinking text, character classes.
+ */
+ int reset_wrap, reset_decom, reset_bce, reset_tblink, reset_charclass;
+ int i;
+
+ reset_wrap = (term->cfg.wrap_mode != cfg->wrap_mode);
+ reset_decom = (term->cfg.dec_om != cfg->dec_om);
+ reset_bce = (term->cfg.bce != cfg->bce);
+ reset_tblink = (term->cfg.blinktext != cfg->blinktext);
+ reset_charclass = 0;
+ for (i = 0; i < lenof(term->cfg.wordness); i++)
+ if (term->cfg.wordness[i] != cfg->wordness[i])
+ reset_charclass = 1;
+
+ /*
+ * If the bidi or shaping settings have changed, flush the bidi
+ * cache completely.
+ */
+ if (term->cfg.arabicshaping != cfg->arabicshaping ||
+ term->cfg.bidi != cfg->bidi) {
+ for (i = 0; i < term->bidi_cache_size; i++) {
+ sfree(term->pre_bidi_cache[i].chars);
+ sfree(term->post_bidi_cache[i].chars);
+ term->pre_bidi_cache[i].width = -1;
+ term->pre_bidi_cache[i].chars = NULL;
+ term->post_bidi_cache[i].width = -1;
+ term->post_bidi_cache[i].chars = NULL;
+ }
+ }
+
+ term->cfg = *cfg; /* STRUCTURE COPY */
+
+ if (reset_wrap)
+ term->alt_wrap = term->wrap = term->cfg.wrap_mode;
+ if (reset_decom)
+ term->alt_om = term->dec_om = term->cfg.dec_om;
+ if (reset_bce) {
+ term->use_bce = term->cfg.bce;
+ set_erase_char(term);
+ }
+ if (reset_tblink) {
+ term->blink_is_real = term->cfg.blinktext;
+ }
+ if (reset_charclass)
+ for (i = 0; i < 256; i++)
+ term->wordness[i] = term->cfg.wordness[i];
+
+ if (term->cfg.no_alt_screen)
+ swap_screen(term, 0, FALSE, FALSE);
+ if (term->cfg.no_mouse_rep) {
+ term->xterm_mouse = 0;
+ set_raw_mouse_mode(term->frontend, 0);
+ }
+ if (term->cfg.no_remote_charset) {
+ term->cset_attr[0] = term->cset_attr[1] = CSET_ASCII;
+ term->sco_acs = term->alt_sco_acs = 0;
+ term->utf = 0;
+ }
+ if (!*term->cfg.printer) {
+ term_print_finish(term);
+ }
+ term_schedule_tblink(term);
+ term_schedule_cblink(term);
+}
+
+/*
+ * Clear the scrollback.
+ */
+void term_clrsb(Terminal *term)
+{
+ unsigned char *line;
+ term->disptop = 0;
+ while ((line = delpos234(term->scrollback, 0)) != NULL) {
+ sfree(line); /* this is compressed data, not a termline */
+ }
+ term->tempsblines = 0;
+ term->alt_sblines = 0;
+ update_sbar(term);
+}
+
+/*
+ * Initialise the terminal.
+ */
+Terminal *term_init(Config *mycfg, struct unicode_data *ucsdata,
+ void *frontend)
+{
+ Terminal *term;
+
+ /*
+ * Allocate a new Terminal structure and initialise the fields
+ * that need it.
+ */
+ term = snew(Terminal);
+ term->frontend = frontend;
+ term->ucsdata = ucsdata;
+ term->cfg = *mycfg; /* STRUCTURE COPY */
+ term->logctx = NULL;
+ term->compatibility_level = TM_PUTTY;
+ strcpy(term->id_string, "\033[?6c");
+ term->cblink_pending = term->tblink_pending = FALSE;
+ term->paste_buffer = NULL;
+ term->paste_len = 0;
+ term->last_paste = 0;
+ bufchain_init(&term->inbuf);
+ bufchain_init(&term->printer_buf);
+ term->printing = term->only_printing = FALSE;
+ term->print_job = NULL;
+ term->vt52_mode = FALSE;
+ term->cr_lf_return = FALSE;
+ term->seen_disp_event = FALSE;
+ term->xterm_mouse = term->mouse_is_down = FALSE;
+ term->reset_132 = FALSE;
+ term->cblinker = term->tblinker = 0;
+ term->has_focus = 1;
+ term->repeat_off = FALSE;
+ term->termstate = TOPLEVEL;
+ term->selstate = NO_SELECTION;
+ term->curstype = 0;
+
+ term->screen = term->alt_screen = term->scrollback = NULL;
+ term->tempsblines = 0;
+ term->alt_sblines = 0;
+ term->disptop = 0;
+ term->disptext = NULL;
+ term->dispcursx = term->dispcursy = -1;
+ term->tabs = NULL;
+ deselect(term);
+ term->rows = term->cols = -1;
+ power_on(term, TRUE);
+ term->beephead = term->beeptail = NULL;
+#ifdef OPTIMISE_SCROLL
+ term->scrollhead = term->scrolltail = NULL;
+#endif /* OPTIMISE_SCROLL */
+ term->nbeeps = 0;
+ term->lastbeep = FALSE;
+ term->beep_overloaded = FALSE;
+ term->attr_mask = 0xffffffff;
+ term->resize_fn = NULL;
+ term->resize_ctx = NULL;
+ term->in_term_out = FALSE;
+ term->ltemp = NULL;
+ term->ltemp_size = 0;
+ term->wcFrom = NULL;
+ term->wcTo = NULL;
+ term->wcFromTo_size = 0;
+
+ term->window_update_pending = FALSE;
+
+ term->bidi_cache_size = 0;
+ term->pre_bidi_cache = term->post_bidi_cache = NULL;
+
+ /* FULL-TERMCHAR */
+ term->basic_erase_char.chr = CSET_ASCII | ' ';
+ term->basic_erase_char.attr = ATTR_DEFAULT;
+ term->basic_erase_char.cc_next = 0;
+ term->erase_char = term->basic_erase_char;
+
+ return term;
+}
+
+void term_free(Terminal *term)
+{
+ termline *line;
+ struct beeptime *beep;
+ int i;
+
+ while ((line = delpos234(term->scrollback, 0)) != NULL)
+ sfree(line); /* compressed data, not a termline */
+ freetree234(term->scrollback);
+ while ((line = delpos234(term->screen, 0)) != NULL)
+ freeline(line);
+ freetree234(term->screen);
+ while ((line = delpos234(term->alt_screen, 0)) != NULL)
+ freeline(line);
+ freetree234(term->alt_screen);
+ if (term->disptext) {
+ for (i = 0; i < term->rows; i++)
+ freeline(term->disptext[i]);
+ }
+ sfree(term->disptext);
+ while (term->beephead) {
+ beep = term->beephead;
+ term->beephead = beep->next;
+ sfree(beep);
+ }
+ bufchain_clear(&term->inbuf);
+ if(term->print_job)
+ printer_finish_job(term->print_job);
+ bufchain_clear(&term->printer_buf);
+ sfree(term->paste_buffer);
+ sfree(term->ltemp);
+ sfree(term->wcFrom);
+ sfree(term->wcTo);
+
+ for (i = 0; i < term->bidi_cache_size; i++) {
+ sfree(term->pre_bidi_cache[i].chars);
+ sfree(term->post_bidi_cache[i].chars);
+ }
+ sfree(term->pre_bidi_cache);
+ sfree(term->post_bidi_cache);
+
+ expire_timer_context(term);
+
+ sfree(term);
+}
+
+/*
+ * Set up the terminal for a given size.
+ */
+void term_size(Terminal *term, int newrows, int newcols, int newsavelines)
+{
+ tree234 *newalt;
+ termline **newdisp, *line;
+ int i, j, oldrows = term->rows;
+ int sblen;
+ int save_alt_which = term->alt_which;
+
+ if (newrows == term->rows && newcols == term->cols &&
+ newsavelines == term->savelines)
+ return; /* nothing to do */
+
+ /* Behave sensibly if we're given zero (or negative) rows/cols */
+
+ if (newrows < 1) newrows = 1;
+ if (newcols < 1) newcols = 1;
+
+ deselect(term);
+ swap_screen(term, 0, FALSE, FALSE);
+
+ term->alt_t = term->marg_t = 0;
+ term->alt_b = term->marg_b = newrows - 1;
+
+ if (term->rows == -1) {
+ term->scrollback = newtree234(NULL);
+ term->screen = newtree234(NULL);
+ term->tempsblines = 0;
+ term->rows = 0;
+ }
+
+ /*
+ * Resize the screen and scrollback. We only need to shift
+ * lines around within our data structures, because lineptr()
+ * will take care of resizing each individual line if
+ * necessary. So:
+ *
+ * - If the new screen is longer, we shunt lines in from temporary
+ * scrollback if possible, otherwise we add new blank lines at
+ * the bottom.
+ *
+ * - If the new screen is shorter, we remove any blank lines at
+ * the bottom if possible, otherwise shunt lines above the cursor
+ * to scrollback if possible, otherwise delete lines below the
+ * cursor.
+ *
+ * - Then, if the new scrollback length is less than the
+ * amount of scrollback we actually have, we must throw some
+ * away.
+ */
+ sblen = count234(term->scrollback);
+ /* Do this loop to expand the screen if newrows > rows */
+ assert(term->rows == count234(term->screen));
+ while (term->rows < newrows) {
+ if (term->tempsblines > 0) {
+ unsigned char *cline;
+ /* Insert a line from the scrollback at the top of the screen. */
+ assert(sblen >= term->tempsblines);
+ cline = delpos234(term->scrollback, --sblen);
+ line = decompressline(cline, NULL);
+ sfree(cline);
+ line->temporary = FALSE; /* reconstituted line is now real */
+ term->tempsblines -= 1;
+ addpos234(term->screen, line, 0);
+ term->curs.y += 1;
+ term->savecurs.y += 1;
+ } else {
+ /* Add a new blank line at the bottom of the screen. */
+ line = newline(term, newcols, FALSE);
+ addpos234(term->screen, line, count234(term->screen));
+ }
+ term->rows += 1;
+ }
+ /* Do this loop to shrink the screen if newrows < rows */
+ while (term->rows > newrows) {
+ if (term->curs.y < term->rows - 1) {
+ /* delete bottom row, unless it contains the cursor */
+ sfree(delpos234(term->screen, term->rows - 1));
+ } else {
+ /* push top row to scrollback */
+ line = delpos234(term->screen, 0);
+ addpos234(term->scrollback, compressline(line), sblen++);
+ freeline(line);
+ term->tempsblines += 1;
+ term->curs.y -= 1;
+ term->savecurs.y -= 1;
+ }
+ term->rows -= 1;
+ }
+ assert(term->rows == newrows);
+ assert(count234(term->screen) == newrows);
+
+ /* Delete any excess lines from the scrollback. */
+ while (sblen > newsavelines) {
+ line = delpos234(term->scrollback, 0);
+ sfree(line);
+ sblen--;
+ }
+ if (sblen < term->tempsblines)
+ term->tempsblines = sblen;
+ assert(count234(term->scrollback) <= newsavelines);
+ assert(count234(term->scrollback) >= term->tempsblines);
+ term->disptop = 0;
+
+ /* Make a new displayed text buffer. */
+ newdisp = snewn(newrows, termline *);
+ for (i = 0; i < newrows; i++) {
+ newdisp[i] = newline(term, newcols, FALSE);
+ for (j = 0; j < newcols; j++)
+ newdisp[i]->chars[j].attr = ATTR_INVALID;
+ }
+ if (term->disptext) {
+ for (i = 0; i < oldrows; i++)
+ freeline(term->disptext[i]);
+ }
+ sfree(term->disptext);
+ term->disptext = newdisp;
+ term->dispcursx = term->dispcursy = -1;
+
+ /* Make a new alternate screen. */
+ newalt = newtree234(NULL);
+ for (i = 0; i < newrows; i++) {
+ line = newline(term, newcols, TRUE);
+ addpos234(newalt, line, i);
+ }
+ if (term->alt_screen) {
+ while (NULL != (line = delpos234(term->alt_screen, 0)))
+ freeline(line);
+ freetree234(term->alt_screen);
+ }
+ term->alt_screen = newalt;
+ term->alt_sblines = 0;
+
+ term->tabs = sresize(term->tabs, newcols, unsigned char);
+ {
+ int i;
+ for (i = (term->cols > 0 ? term->cols : 0); i < newcols; i++)
+ term->tabs[i] = (i % 8 == 0 ? TRUE : FALSE);
+ }
+
+ /* Check that the cursor positions are still valid. */
+ if (term->savecurs.y < 0)
+ term->savecurs.y = 0;
+ if (term->savecurs.y >= newrows)
+ term->savecurs.y = newrows - 1;
+ if (term->curs.y < 0)
+ term->curs.y = 0;
+ if (term->curs.y >= newrows)
+ term->curs.y = newrows - 1;
+ if (term->curs.x >= newcols)
+ term->curs.x = newcols - 1;
+ term->alt_x = term->alt_y = 0;
+ term->wrapnext = term->alt_wnext = FALSE;
+
+ term->rows = newrows;
+ term->cols = newcols;
+ term->savelines = newsavelines;
+
+ swap_screen(term, save_alt_which, FALSE, FALSE);
+
+ update_sbar(term);
+ term_update(term);
+ if (term->resize_fn)
+ term->resize_fn(term->resize_ctx, term->cols, term->rows);
+}
+
+/*
+ * Hand a function and context pointer to the terminal which it can
+ * use to notify a back end of resizes.
+ */
+void term_provide_resize_fn(Terminal *term,
+ void (*resize_fn)(void *, int, int),
+ void *resize_ctx)
+{
+ term->resize_fn = resize_fn;
+ term->resize_ctx = resize_ctx;
+ if (term->cols > 0 && term->rows > 0)
+ resize_fn(resize_ctx, term->cols, term->rows);
+}
+
+/* Find the bottom line on the screen that has any content.
+ * If only the top line has content, returns 0.
+ * If no lines have content, return -1.
+ */
+static int find_last_nonempty_line(Terminal * term, tree234 * screen)
+{
+ int i;
+ for (i = count234(screen) - 1; i >= 0; i--) {
+ termline *line = index234(screen, i);
+ int j;
+ for (j = 0; j < line->cols; j++)
+ if (!termchars_equal(&line->chars[j], &term->erase_char))
+ break;
+ if (j != line->cols) break;
+ }
+ return i;
+}
+
+/*
+ * Swap screens. If `reset' is TRUE and we have been asked to
+ * switch to the alternate screen, we must bring most of its
+ * configuration from the main screen and erase the contents of the
+ * alternate screen completely. (This is even true if we're already
+ * on it! Blame xterm.)
+ */
+static void swap_screen(Terminal *term, int which, int reset, int keep_cur_pos)
+{
+ int t;
+ tree234 *ttr;
+
+ if (!which)
+ reset = FALSE; /* do no weird resetting if which==0 */
+
+ if (which != term->alt_which) {
+ term->alt_which = which;
+
+ ttr = term->alt_screen;
+ term->alt_screen = term->screen;
+ term->screen = ttr;
+ term->alt_sblines = find_last_nonempty_line(term, term->alt_screen) + 1;
+ t = term->curs.x;
+ if (!reset && !keep_cur_pos)
+ term->curs.x = term->alt_x;
+ term->alt_x = t;
+ t = term->curs.y;
+ if (!reset && !keep_cur_pos)
+ term->curs.y = term->alt_y;
+ term->alt_y = t;
+ t = term->marg_t;
+ if (!reset) term->marg_t = term->alt_t;
+ term->alt_t = t;
+ t = term->marg_b;
+ if (!reset) term->marg_b = term->alt_b;
+ term->alt_b = t;
+ t = term->dec_om;
+ if (!reset) term->dec_om = term->alt_om;
+ term->alt_om = t;
+ t = term->wrap;
+ if (!reset) term->wrap = term->alt_wrap;
+ term->alt_wrap = t;
+ t = term->wrapnext;
+ if (!reset) term->wrapnext = term->alt_wnext;
+ term->alt_wnext = t;
+ t = term->insert;
+ if (!reset) term->insert = term->alt_ins;
+ term->alt_ins = t;
+ t = term->cset;
+ if (!reset) term->cset = term->alt_cset;
+ term->alt_cset = t;
+ t = term->utf;
+ if (!reset) term->utf = term->alt_utf;
+ term->alt_utf = t;
+ t = term->sco_acs;
+ if (!reset) term->sco_acs = term->alt_sco_acs;
+ term->alt_sco_acs = t;
+ }
+
+ if (reset && term->screen) {
+ /*
+ * Yes, this _is_ supposed to honour background-colour-erase.
+ */
+ erase_lots(term, FALSE, TRUE, TRUE);
+ }
+}
+
+/*
+ * Update the scroll bar.
+ */
+static void update_sbar(Terminal *term)
+{
+ int nscroll = sblines(term);
+ set_sbar(term->frontend, nscroll + term->rows,
+ nscroll + term->disptop, term->rows);
+}
+
+/*
+ * Check whether the region bounded by the two pointers intersects
+ * the scroll region, and de-select the on-screen selection if so.
+ */
+static void check_selection(Terminal *term, pos from, pos to)
+{
+ if (poslt(from, term->selend) && poslt(term->selstart, to))
+ deselect(term);
+}
+
+/*
+ * Scroll the screen. (`lines' is +ve for scrolling forward, -ve
+ * for backward.) `sb' is TRUE if the scrolling is permitted to
+ * affect the scrollback buffer.
+ */
+static void scroll(Terminal *term, int topline, int botline, int lines, int sb)
+{
+ termline *line;
+ int i, seltop, olddisptop, shift;
+
+ if (topline != 0 || term->alt_which != 0)
+ sb = FALSE;
+
+ olddisptop = term->disptop;
+ shift = lines;
+ if (lines < 0) {
+ while (lines < 0) {
+ line = delpos234(term->screen, botline);
+ resizeline(term, line, term->cols);
+ for (i = 0; i < term->cols; i++)
+ copy_termchar(line, i, &term->erase_char);
+ line->lattr = LATTR_NORM;
+ addpos234(term->screen, line, topline);
+
+ if (term->selstart.y >= topline && term->selstart.y <= botline) {
+ term->selstart.y++;
+ if (term->selstart.y > botline) {
+ term->selstart.y = botline + 1;
+ term->selstart.x = 0;
+ }
+ }
+ if (term->selend.y >= topline && term->selend.y <= botline) {
+ term->selend.y++;
+ if (term->selend.y > botline) {
+ term->selend.y = botline + 1;
+ term->selend.x = 0;
+ }
+ }
+
+ lines++;
+ }
+ } else {
+ while (lines > 0) {
+ line = delpos234(term->screen, topline);
+#ifdef TERM_CC_DIAGS
+ cc_check(line);
+#endif
+ if (sb && term->savelines > 0) {
+ int sblen = count234(term->scrollback);
+ /*
+ * We must add this line to the scrollback. We'll
+ * remove a line from the top of the scrollback if
+ * the scrollback is full.