diff options
-rw-r--r-- | src/screen.C | 3697 |
1 files changed, 3697 insertions, 0 deletions
diff --git a/src/screen.C b/src/screen.C new file mode 100644 index 0000000..f3c6d57 --- /dev/null +++ b/src/screen.C @@ -0,0 +1,3697 @@ +/*---------------------------------------------------------------------------* + * File: screen.C + *---------------------------------------------------------------------------* + * + * Copyright (c) 1997-2001 Geoff Wing <gcw@pobox.com> + * Copyright (c) 2003-2007 Marc Lehmann <schmorp@schmorp.de> + * Copyright (c) 2015 Emanuele Giaquinta <e.giaquinta@glauco.it> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + *--------------------------------------------------------------------------*/ + +/* + * This file handles _all_ screen updates and selections + */ + +#include "../config.h" /* NECESSARY */ +#include "rxvt.h" /* NECESSARY */ +#include "rxvtperl.h" /* NECESSARY */ + +#include <inttypes.h> + +static inline void +fill_text (text_t *start, text_t value, int len) +{ + while (len--) + *start++ = value; +} + +/* ------------------------------------------------------------------------- */ +#define TABSIZE 8 /* default tab size */ + +/* ------------------------------------------------------------------------- * + * GENERAL SCREEN AND SELECTION UPDATE ROUTINES * + * ------------------------------------------------------------------------- */ +#define ZERO_SCROLLBACK() \ + if (option (Opt_scrollTtyOutput)) \ + view_start = 0 +#define CLEAR_SELECTION() \ + selection.beg.row = selection.beg.col \ + = selection.end.row = selection.end.col = 0 +#define CLEAR_ALL_SELECTION() \ + selection.beg.row = selection.beg.col \ + = selection.mark.row = selection.mark.col \ + = selection.end.row = selection.end.col = 0 + +#define ROW_AND_COL_IS_AFTER(A, B, C, D) \ + (((A) > (C)) || (((A) == (C)) && ((B) > (D)))) +#define ROW_AND_COL_IS_BEFORE(A, B, C, D) \ + (((A) < (C)) || (((A) == (C)) && ((B) < (D)))) +#define ROW_AND_COL_IN_ROW_AFTER(A, B, C, D) \ + (((A) == (C)) && ((B) > (D))) +#define ROW_AND_COL_IN_ROW_AT_OR_AFTER(A, B, C, D) \ + (((A) == (C)) && ((B) >= (D))) +#define ROW_AND_COL_IN_ROW_BEFORE(A, B, C, D) \ + (((A) == (C)) && ((B) < (D))) +#define ROW_AND_COL_IN_ROW_AT_OR_BEFORE(A, B, C, D) \ + (((A) == (C)) && ((B) <= (D))) + +/* these must be row_col_t */ +#define ROWCOL_IS_AFTER(X, Y) \ + ROW_AND_COL_IS_AFTER ((X).row, (X).col, (Y).row, (Y).col) +#define ROWCOL_IS_BEFORE(X, Y) \ + ROW_AND_COL_IS_BEFORE ((X).row, (X).col, (Y).row, (Y).col) +#define ROWCOL_IN_ROW_AFTER(X, Y) \ + ROW_AND_COL_IN_ROW_AFTER ((X).row, (X).col, (Y).row, (Y).col) +#define ROWCOL_IN_ROW_BEFORE(X, Y) \ + ROW_AND_COL_IN_ROW_BEFORE ((X).row, (X).col, (Y).row, (Y).col) +#define ROWCOL_IN_ROW_AT_OR_AFTER(X, Y) \ + ROW_AND_COL_IN_ROW_AT_OR_AFTER ((X).row, (X).col, (Y).row, (Y).col) +#define ROWCOL_IN_ROW_AT_OR_BEFORE(X, Y) \ + ROW_AND_COL_IN_ROW_AT_OR_BEFORE ((X).row, (X).col, (Y).row, (Y).col) + +/* + * CLEAR_CHARS: clear <num> chars starting from pixel position <x,y> + */ +#define CLEAR_CHARS(x, y, num) \ + if (mapped) \ + XClearArea (dpy, vt, x, y, \ + (unsigned int)Width2Pixel (num), \ + (unsigned int)Height2Pixel (1), False) + +/* ------------------------------------------------------------------------- * + * SCREEN `COMMON' ROUTINES * + * ------------------------------------------------------------------------- */ + +/* Fill part/all of a line with blanks. */ +void +rxvt_term::scr_blank_line (line_t &l, unsigned int col, unsigned int width, rend_t efs) const NOTHROW +{ + if (!l.valid ()) + { + l.alloc (); + col = 0; + width = ncol; + } + + l.touch (); + + efs &= ~RS_baseattrMask; // remove italic etc. fontstyles + efs = SET_FONT (efs, FONTSET (efs)->find_font (' ')); + + text_t *et = l.t + col; + rend_t *er = l.r + col; + + while (width--) + { + *et++ = ' '; + *er++ = efs; + } +} + +/* ------------------------------------------------------------------------- */ +/* Fill a full line with blanks - make sure it is allocated first */ +void +rxvt_term::scr_blank_screen_mem (line_t &l, rend_t efs) const NOTHROW +{ + scr_blank_line (l, 0, ncol, efs); + + l.l = 0; + l.f = 0; +} + +// nuke a single wide character at the given column +void +rxvt_term::scr_kill_char (line_t &l, int col) const NOTHROW +{ + // find begin + while (col > 0 && l.t[col] == NOCHAR) + col--; + + rend_t rend = l.r[col] & ~RS_baseattrMask; + rend = SET_FONT (rend, FONTSET (rend)->find_font (' ')); + + l.touch (); + + // found start, nuke + do { + l.t[col] = ' '; + l.r[col] = rend; + col++; + } while (col < ncol && l.t[col] == NOCHAR); +} + +// set the rendition of a single wide character beginning at the given column +void +rxvt_term::scr_set_char_rend (line_t &l, int col, rend_t rend) +{ + do { + l.r[col] = rend; + col++; + } while (col < ncol && l.t[col] == NOCHAR); +} + +/* ------------------------------------------------------------------------- * + * SCREEN INITIALISATION * + * ------------------------------------------------------------------------- */ + +void +rxvt_term::scr_alloc () NOTHROW +{ + int tsize = sizeof (text_t) * ncol; + int rsize = sizeof (rend_t) * ncol; + + // we assume that rend_t size is a sufficient alignment + // factor for text_t and line_t values, and we only + // need to adjust tsize. + tsize = (tsize + sizeof (rend_t) - 1); + tsize -= tsize % sizeof (rend_t); + + int all_rows = total_rows + nrow + nrow; + + chunk_size = (sizeof (line_t) + rsize + tsize) * all_rows; + chunk = chunk_alloc (chunk_size, 0); + + char *base = (char *)chunk + sizeof (line_t) * all_rows; + + for (int row = 0; row < all_rows; ++row) + { + line_t &l = ((line_t *)chunk) [row]; + + l.t = (text_t *)base; base += tsize; + l.r = (rend_t *)base; base += rsize; + l.l = -1; + l.f = 0; + } + + drawn_buf = (line_t *)chunk; + swap_buf = drawn_buf + nrow; + row_buf = swap_buf + nrow; +} + +void +rxvt_term::copy_line (line_t &dst, line_t &src) +{ + scr_blank_screen_mem (dst, DEFAULT_RSTYLE); + dst.l = min (src.l, ncol); + memcpy (dst.t, src.t, sizeof (text_t) * dst.l); + memcpy (dst.r, src.r, sizeof (rend_t) * dst.l); + dst.f = src.f; +} + +void ecb_cold +rxvt_term::scr_reset () +{ +#if ENABLE_OVERLAY + scr_overlay_off (); +#endif + + view_start = 0; + num_scr = 0; + + if (ncol == 0) + ncol = 80; + + if (nrow == 0) + nrow = 24; + + if (ncol == prev_ncol && nrow == prev_nrow) + return; + + // we need at least two lines for wrapping to work correctly + while (nrow + saveLines < 2) + { + //TODO//FIXME + saveLines++; + prev_nrow--; + top_row--; + } + + want_refresh = 1; + + int prev_total_rows = prev_nrow + saveLines; + total_rows = nrow + saveLines; + + screen.tscroll = 0; + screen.bscroll = nrow - 1; + + void *prev_chunk = chunk; + size_t prev_chunk_size = chunk_size; + line_t *prev_drawn_buf = drawn_buf; + line_t *prev_swap_buf = swap_buf; + line_t *prev_row_buf = row_buf; + + scr_alloc (); + + if (!prev_row_buf) + { + top_row = 0; + term_start = 0; + + memset (charsets, 'B', sizeof (charsets)); + rstyle = DEFAULT_RSTYLE; + screen.flags = Screen_DefaultFlags; + screen.cur.row = screen.cur.col = 0; + screen.charset = 0; + current_screen = PRIMARY; + scr_cursor (SAVE); + +#if NSCREENS + swap.flags = Screen_DefaultFlags; + swap.cur.row = swap.cur.col = 0; + swap.charset = 0; + current_screen = SECONDARY; + scr_cursor (SAVE); + current_screen = PRIMARY; +#endif + + selection.text = NULL; + selection.len = 0; + selection.op = SELECTION_CLEAR; + selection.screen = PRIMARY; + selection.clicks = 0; + selection.clip_text = NULL; + selection.clip_len = 0; + } + else + { + /* + * add or delete rows as appropriate + */ + + int common_col = min (prev_ncol, ncol); + + for (int row = min (nrow, prev_nrow); row--; ) + { + scr_blank_screen_mem (drawn_buf [row], DEFAULT_RSTYLE); + memcpy (drawn_buf [row].t, prev_drawn_buf [row].t, sizeof (text_t) * common_col); + memcpy (drawn_buf [row].r, prev_drawn_buf [row].r, sizeof (rend_t) * common_col); + + copy_line (swap_buf [row], prev_swap_buf [row]); + } + + int p = MOD (term_start + prev_nrow, prev_total_rows); // previous row + int pend = MOD (term_start + top_row , prev_total_rows); + int q = total_rows; // rewrapped row + + if (top_row) + { + // Re-wrap lines. This is rather ugly, possibly because I am too dumb + // to come up with a lean and mean algorithm. + // TODO: maybe optimise when width didn't change + + row_col_t ocur = screen.cur; + ocur.row = MOD (term_start + ocur.row, prev_total_rows); + + do + { + p = MOD (p - 1, prev_total_rows); + assert (prev_row_buf [MOD (p, prev_total_rows)].t); + int plines = 1; + int llen = prev_row_buf [MOD (p, prev_total_rows)].l; + + while (p != pend && prev_row_buf [MOD (p - 1, prev_total_rows)].is_longer ()) + { + p = MOD (p - 1, prev_total_rows); + + plines++; + llen += prev_ncol; + } + + int qlines = max (0, (llen - 1) / ncol) + 1; + + // drop partial lines completely + if (q < qlines) + break; + + q -= qlines; + + int lofs = 0; + line_t *qline; + + // re-assemble the full line by destination lines + for (int qrow = q; qlines--; qrow++) + { + qline = row_buf + qrow; + qline->alloc (); // redundant with next line + qline->l = ncol; + qline->is_longer (1); + + int qcol = 0; + + // see below for cursor adjustment rationale + if (p == ocur.row) + screen.cur.row = q - (total_rows - nrow); + + // fill a single destination line + while (lofs < llen && qcol < ncol) + { + int prow = lofs / prev_ncol; + int pcol = lofs % prev_ncol; + + prow = MOD (p + prow, prev_total_rows); + + // we only adjust the cursor _row_ and put it into + // the topmost line of "long line" it was in, as + // this seems to upset applications/shells/readline + // least. + if (prow == ocur.row) + screen.cur.row = q - (total_rows - nrow); + + line_t &pline = prev_row_buf [prow]; + + int len = min (min (prev_ncol - pcol, ncol - qcol), llen - lofs); + + memcpy (qline->t + qcol, pline.t + pcol, len * sizeof (text_t)); + memcpy (qline->r + qcol, pline.r + pcol, len * sizeof (rend_t)); + + lofs += len; + qcol += len; + } + } + + qline->l = llen ? MOD (llen - 1, ncol) + 1 : 0; + qline->is_longer (0); + scr_blank_line (*qline, qline->l, ncol - qline->l, DEFAULT_RSTYLE); + } + while (p != pend && q > 0); + + term_start = total_rows - nrow; + top_row = q - term_start; + + // make sure all terminal lines exist + while (top_row > 0) + scr_blank_screen_mem (ROW (--top_row), DEFAULT_RSTYLE); + } + else + { + // if no scrollback exists (yet), wing, instead of wrap + + for (int row = min (nrow, prev_nrow); row--; ) + { + line_t &src = prev_row_buf [MOD (term_start + row, prev_total_rows)]; + line_t &dst = row_buf [row]; + + copy_line (dst, src); + } + + for (int row = prev_nrow; row < nrow; row++) + scr_blank_screen_mem (row_buf [row], DEFAULT_RSTYLE); + + term_start = 0; + } + + clamp_it (screen.cur.row, 0, nrow - 1); + clamp_it (screen.cur.col, 0, ncol - 1); + } + + for (int row = nrow; row--; ) + { + if (!ROW (row).valid ()) scr_blank_screen_mem (ROW (row), DEFAULT_RSTYLE); + if (!swap_buf [row].valid ()) scr_blank_screen_mem (swap_buf [row], DEFAULT_RSTYLE); + if (!drawn_buf [row].valid ()) scr_blank_screen_mem (drawn_buf [row], DEFAULT_RSTYLE); + } + + chunk_free (prev_chunk, prev_chunk_size); + + free (tabs); + tabs = (char *)rxvt_malloc (ncol); + + for (int col = ncol; col--; ) + tabs [col] = col % TABSIZE == 0; + + CLEAR_ALL_SELECTION (); + + prev_nrow = nrow; + prev_ncol = ncol; + + tt_winch (); + + HOOK_INVOKE ((this, HOOK_RESET, DT_END)); +} + +void ecb_cold +rxvt_term::scr_release () NOTHROW +{ + chunk_free (chunk, chunk_size); + chunk = 0; + row_buf = 0; + + free (tabs); + tabs = 0; +} + +/* ------------------------------------------------------------------------- */ +/* + * Hard/Soft reset + */ +void ecb_cold +rxvt_term::scr_poweron () +{ + scr_release (); + + prev_nrow = prev_ncol = 0; + rvideo_mode = false; + scr_soft_reset (); + scr_reset (); + + scr_clear (true); + scr_refresh (); +} + +void ecb_cold +rxvt_term::scr_soft_reset () NOTHROW +{ + /* only affects modes, nothing drastic such as clearing the screen */ +#if ENABLE_OVERLAY + scr_overlay_off (); +#endif + + if (current_screen != PRIMARY) + scr_swap_screen (); + + scr_scroll_region (0, MAX_ROWS - 1); + scr_rendition (0, ~RS_None); + scr_insert_mode (0); +} + +/* ------------------------------------------------------------------------- * + * PROCESS SCREEN COMMANDS * + * ------------------------------------------------------------------------- */ +/* + * Save and Restore cursor + * XTERM_SEQ: Save cursor : ESC 7 + * XTERM_SEQ: Restore cursor: ESC 8 + */ +void +rxvt_term::scr_cursor (cursor_mode mode) NOTHROW +{ + screen_t *s; + +#if NSCREENS && !defined(NO_SECONDARY_SCREEN_CURSOR) + if (current_screen == SECONDARY) + s = &swap; + else +#endif + s = &screen; + + switch (mode) + { + case SAVE: + s->s_cur.row = screen.cur.row; + s->s_cur.col = screen.cur.col; + s->s_rstyle = rstyle; + s->s_charset = screen.charset; + s->s_charset_char = charsets[screen.charset]; + break; + + case RESTORE: + want_refresh = 1; + screen.cur.row = s->s_cur.row; + screen.cur.col = s->s_cur.col; + screen.flags &= ~Screen_WrapNext; + rstyle = s->s_rstyle; + screen.charset = s->s_charset; + charsets[screen.charset] = s->s_charset_char; + set_font_style (); + break; + } + + /* boundary check in case screen size changed between SAVE and RESTORE */ + min_it (s->cur.row, nrow - 1); + min_it (s->cur.col, ncol - 1); + assert (s->cur.row >= 0); + assert (s->cur.col >= 0); +} + +void +rxvt_term::scr_swap_screen () NOTHROW +{ + if (!option (Opt_secondaryScreen)) + return; + + for (int i = prev_nrow; i--; ) + ::swap (ROW(i), swap_buf [i]); + + ::swap (screen.cur, swap.cur); + + screen.cur.row = clamp (screen.cur.row, 0, prev_nrow - 1); + screen.cur.col = clamp (screen.cur.col, 0, prev_ncol - 1); +} + +/* ------------------------------------------------------------------------- */ +/* + * Swap between primary and secondary screens + * XTERM_SEQ: Primary screen : ESC [ ? 4 7 h + * XTERM_SEQ: Secondary screen: ESC [ ? 4 7 l + */ +void +rxvt_term::scr_change_screen (int scrn) +{ + if (scrn == current_screen) + return; + + want_refresh = 1; + view_start = 0; + + /* check for boundary cross */ + row_col_t pos; + pos.row = pos.col = 0; + if (ROWCOL_IS_BEFORE (selection.beg, pos) + && ROWCOL_IS_AFTER (selection.end, pos)) + CLEAR_SELECTION (); + + current_screen = scrn; + +#if NSCREENS + if (option (Opt_secondaryScreen)) + { + num_scr = 0; + + scr_swap_screen (); + + ::swap (screen.charset, swap.charset); + ::swap (screen.flags, swap.flags); + screen.flags |= Screen_VisibleCursor; + swap.flags |= Screen_VisibleCursor; + } + else +#endif + if (option (Opt_secondaryScroll)) + scr_scroll_text (0, prev_nrow - 1, prev_nrow); +} + +// clear WrapNext indicator, solidifying position on next line +void +rxvt_term::scr_do_wrap () NOTHROW +{ + if (!(screen.flags & Screen_WrapNext)) + return; + + screen.flags &= ~Screen_WrapNext; + + screen.cur.col = 0; + + if (screen.cur.row == screen.bscroll) + scr_scroll_text (screen.tscroll, screen.bscroll, 1); + else if (screen.cur.row < nrow - 1) + screen.cur.row++; +} + +/* ------------------------------------------------------------------------- */ +/* + * Change the colour for following text + */ +void +rxvt_term::scr_color (unsigned int color, int fgbg) NOTHROW +{ + if (!IN_RANGE_INC (color, minCOLOR, maxTermCOLOR24)) + color = fgbg; + + if (fgbg == Color_fg) + rstyle = SET_FGCOLOR (rstyle, color); + else + rstyle = SET_BGCOLOR (rstyle, color); +} + +/* ------------------------------------------------------------------------- */ +/* + * Change the rendition style for following text + */ +void +rxvt_term::scr_rendition (int set, int style) NOTHROW +{ + if (set) + rstyle |= style; + else if (style == ~RS_None) + rstyle = DEFAULT_RSTYLE; + else + rstyle &= ~style; +} + +/* ------------------------------------------------------------------------- */ +/* + * Scroll text between <row1> and <row2> inclusive, by <count> lines + * count positive ==> scroll up + * count negative ==> scroll down + */ +int ecb_hot +rxvt_term::scr_scroll_text (int row1, int row2, int count) NOTHROW +{ + if (count == 0 || (row1 > row2)) + return 0; + + want_refresh = 1; + num_scr += count; + + if (count > 0 + && row1 == 0 + && (current_screen == PRIMARY || option (Opt_secondaryScroll))) + { + min_it (count, total_rows - (nrow - (row2 + 1))); + + top_row = max (top_row - count, -saveLines); + + // sever bottommost line + { + line_t &l = ROW(row2); + l.is_longer (0); + l.touch (); + } + + // scroll everything up 'count' lines + term_start = (term_start + count) % total_rows; + + // now copy lines below the scroll region bottom to the + // bottom of the screen again, so they look as if they + // hadn't moved. + for (int i = nrow; --i > row2; ) + { + line_t &l1 = ROW(i - count); + line_t &l2 = ROW(i); + + ::swap (l1, l2); + l2.touch (); + } + + // erase newly scrolled-in lines + for (int i = count; i--; ) + { + line_t &l = ROW(row2 - i); + + // optimise if already cleared, can be significant on slow machines + // could be rolled into scr_blank_screen_mem + if (l.r && l.l < ncol - 1 && !((l.r[l.l + 1] ^ rstyle) & (RS_fgMask | RS_bgMask))) + { + scr_blank_line (l, 0, l.l, rstyle); + l.l = 0; + l.f = 0; + } + else + scr_blank_screen_mem (l, rstyle); + } + + // move and/or clear selection, if any + if (selection.op && current_screen == selection.screen + && selection.beg.row <= row2) + { + selection.beg.row -= count; + selection.end.row -= count; + selection.mark.row -= count; + + selection_check (0); + } + + // finally move the view window, if desired + if (option (Opt_scrollWithBuffer) + && view_start != 0 + && view_start != -saveLines) + scr_page (count); + + if (SHOULD_INVOKE (HOOK_SCROLL_BACK)) + HOOK_INVOKE ((this, HOOK_SCROLL_BACK, DT_INT, count, DT_INT, top_row, DT_END)); + } + else + { + if (selection.op && current_screen == selection.screen) + { + if ((selection.beg.row < row1 && selection.end.row > row1) + || (selection.beg.row < row2 && selection.end.row > row2) + || (selection.beg.row - count < row1 && selection.beg.row >= row1) + || (selection.beg.row - count > row2 && selection.beg.row <= row2) + || (selection.end.row - count < row1 && selection.end.row >= row1) + || (selection.end.row - count > row2 && selection.end.row <= row2)) + { + CLEAR_ALL_SELECTION (); + selection.op = SELECTION_CLEAR; + } + else if (selection.end.row >= row1 && selection.end.row <= row2) + { + /* move selected region too */ + selection.beg.row -= count; + selection.end.row -= count; + selection.mark.row -= count; + + selection_check (0); + } + } + + // use a simple and robust scrolling algorithm, this + // part of scr_scroll_text is not time-critical. + + // sever line above scroll region + if (row1) + { + line_t &l = ROW(row1 - 1); + l.is_longer (0); + l.touch (); + } + + int rows = row2 - row1 + 1; + + min_it (count, rows); + + line_t *temp_buf = rxvt_temp_buf<line_t> (rows); + + for (int row = 0; row < rows; row++) + { + temp_buf [row] = ROW(row1 + (row + count + rows) % rows); + + if (!IN_RANGE_EXC (row + count, 0, rows)) + scr_blank_screen_mem (temp_buf [row], rstyle); + } + + for (int row = 0; row < rows; row++) + ROW(row1 + row) = temp_buf [row]; + + // sever bottommost line + { + line_t &l = ROW(row2); + l.is_longer (0); + l.touch (); + } + } + + return count; +} + +/* ------------------------------------------------------------------------- */ +/* + * Add text given in <str> of length <len> to screen struct + */ +void ecb_hot +rxvt_term::scr_add_lines (const wchar_t *str, int len, int minlines) NOTHROW +{ + if (len <= 0) /* sanity */ + return; + + bool checksel; + unicode_t c; + int ncol = this->ncol; + const wchar_t *strend = str + len; + + want_refresh = 1; + ZERO_SCROLLBACK (); + + if (minlines > 0) + { + minlines += screen.cur.row - screen.bscroll; + min_it (minlines, screen.cur.row - top_row); + + if (minlines > 0 + && screen.tscroll == 0 + && screen.bscroll == nrow - 1) + { + /* _at least_ this many lines need to be scrolled */ + scr_scroll_text (screen.tscroll, screen.bscroll, minlines); + screen.cur.row -= minlines; + } + } + + assert (screen.cur.col < ncol); + assert (screen.cur.row < nrow + && screen.cur.row >= top_row); + int row = screen.cur.row; + + checksel = selection.op && current_screen == selection.screen ? 1 : 0; + + line_t *line = &ROW(row); + + while (str < strend) + { + c = (unicode_t)*str++; // convert to rxvt-unicodes representation + + if (ecb_unlikely (c < 0x20)) + if (c == C0_LF) + { + max_it (line->l, screen.cur.col); + + screen.flags &= ~Screen_WrapNext; + + if (screen.cur.row == screen.bscroll) + scr_scroll_text (screen.tscroll, screen.bscroll, 1); + else if (screen.cur.row < (nrow - 1)) + row = ++screen.cur.row; + + line = &ROW(row); /* _must_ refresh */ + continue; + } + else if (c == C0_CR) + { + max_it (line->l, screen.cur.col); + + screen.flags &= ~Screen_WrapNext; + screen.cur.col = 0; + continue; + } + else if (c == C0_HT) + { + scr_tab (1, true); + continue; + } + + if (ecb_unlikely ( + checksel /* see if we're writing within selection */ + && !ROWCOL_IS_BEFORE (screen.cur, selection.beg) + && ROWCOL_IS_BEFORE (screen.cur, selection.end) + )) + { + checksel = 0; + /* + * If we wrote anywhere in the selected area, kill the selection + * XXX: should we kill the mark too? Possibly, but maybe that + * should be a similar check. + */ + CLEAR_SELECTION (); + } + + if (ecb_unlikely (screen.flags & Screen_WrapNext)) + { + scr_do_wrap (); + + line->l = ncol; + line->is_longer (1); + + row = screen.cur.row; + line = &ROW(row); /* _must_ refresh */ + } + + // some utf-8 decoders "decode" surrogate characters: let's fix this. + if (ecb_unlikely (IN_RANGE_INC (c, 0xd800, 0xdfff))) + c = 0xfffd; + + // rely on wcwidth to tell us the character width, do wcwidth before + // further replacements, as wcwidth might return -1 for the line + // drawing characters below as they might be invalid in the current + // locale. + int width = WCWIDTH (c); + + if (ecb_unlikely (charsets [screen.charset] == '0')) // DEC SPECIAL + { + // vt100 special graphics and line drawing + // 5f-7e standard vt100 + // 40-5e rxvt extension for extra curses acs chars + static uint16_t vt100_0[62] = { // 41 .. 7e + 0x2191, 0x2193, 0x2192, 0x2190, 0x2588, 0x259a, 0x2603, // 41-47 hi mr. snowman! + 0, 0, 0, 0, 0, 0, 0, 0, // 48-4f + 0, 0, 0, 0, 0, 0, 0, 0, // 50-57 + 0, 0, 0, 0, 0, 0, 0, 0x0020, // 58-5f + 0x25c6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1, // 60-67 + 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba, // 68-6f + 0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c, // 70-77 + 0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, // 78-7e + }; + + if (c >= 0x41 && c <= 0x7e && vt100_0[c - 0x41]) + { + c = vt100_0[c - 0x41]; + width = 1; // vt100 line drawing characters are always single-width + } + } + + if (ecb_unlikely (screen.flags & Screen_Insert)) + scr_insdel_chars (width, INSERT); + + if (width != 0) + { +#if !UNICODE_3 + // trim characters we can't store directly :( + if (c >= 0x10000) +# if ENABLE_COMBINING + c = rxvt_composite.compose (c); // map to lower 16 bits +# else + c = 0xfffd; +# endif +#endif + + rend_t rend = SET_FONT (rstyle, FONTSET (rstyle)->find_font (c)); + + // if the character doesn't fit into the remaining columns... + if (ecb_unlikely (screen.cur.col > ncol - width && ncol >= width)) + { + if (screen.flags & Screen_Autowrap) + { + // ... artificially enlargen the previous one + c = NOCHAR; + // and try the same character next loop iteration + --str; + } + else + screen.cur.col = ncol - width; + } + + // nuke the character at this position, if required + // due to wonderful coincidences everywhere else in this loop + // we never have to check for overwriting a wide char itself, + // only its tail. + if (ecb_unlikely (line->t[screen.cur.col] == NOCHAR)) + scr_kill_char (*line, screen.cur.col); + + line->touch (); + + do + { + line->t[screen.cur.col] = c; + line->r[screen.cur.col] = rend; + + if (ecb_likely (screen.cur.col < ncol - 1)) + screen.cur.col++; + else + { + line->l = ncol; + if (screen.flags & Screen_Autowrap) + screen.flags |= Screen_WrapNext; + + goto end_of_line; + } + + c = NOCHAR; + } + while (ecb_unlikely (--width > 0)); + + // pad with spaces when overwriting wide character with smaller one + for (int c = screen.cur.col; ecb_unlikely (c < ncol && line->t[c] == NOCHAR); c++) + { + line->t[c] = ' '; + line->r[c] = rend; + } + +end_of_line: + ; + } +#if ENABLE_COMBINING + else // width == 0 + { + if (c != 0xfeff) // ignore BOM + { + // handle combining characters + // we just tag the accent on the previous on-screen character. + // this is arguably not correct, but also arguably not wrong. + // we don't handle double-width characters nicely yet. + line_t *linep; + text_t *tp; + rend_t *rp; + + if (screen.cur.col > 0) + { + linep = line; + tp = line->t + screen.cur.col - 1; + rp = line->r + screen.cur.col - 1; + } + else if (screen.cur.row > 0 + && ROW(screen.cur.row - 1).is_longer ()) + { + linep = &ROW(screen.cur.row - 1); + tp = linep->t + ncol - 1; + rp = linep->r + ncol - 1; + } + else + continue; + + linep->touch (); + + while (*tp == NOCHAR && tp > linep->t) + tp--, rp--; + + // first try to find a precomposed character + unicode_t n = rxvt_compose (*tp, c); + if (n == NOCHAR) + n = rxvt_composite.compose (*tp, c); + + *tp = n; + *rp = SET_FONT (*rp, FONTSET (*rp)->find_font (*tp)); + } + } +#endif + } + + max_it (line->l, screen.cur.col); + + assert (screen.cur.row >= 0); +} + +/* ------------------------------------------------------------------------- */ +/* + * Process Backspace. Move back the cursor back a position, wrap if have to + * XTERM_SEQ: CTRL-H + */ +void +rxvt_term::scr_backspace () NOTHROW +{ + if (screen.cur.col == 0) + { + if (screen.cur.row > 0) + { +#ifdef TERMCAP_HAS_BW + screen.cur.col = ncol - 1; + --screen.cur.row; + + want_refresh = 1; +#endif + } + } + else if (screen.flags & Screen_WrapNext) + screen.flags &= ~Screen_WrapNext; + else + scr_gotorc (0, -1, RELATIVE); +} + +/* ------------------------------------------------------------------------- */ +/* + * Process Horizontal Tab + * count: +ve = forward; -ve = backwards + * XTERM_SEQ: CTRL-I + */ +void +rxvt_term::scr_tab (int count, bool ht) NOTHROW +{ + int i, x; + + want_refresh = 1; + i = x = screen.cur.col; + + if (count == 0) + return; + else if (count > 0) + { + line_t &l = ROW(screen.cur.row); + rend_t base_rend = l.r[i]; + ht &= l.t[i] == ' '; + + for (; ++i < ncol; ) + if (tabs[i]) + { + x = i; + + if (!--count) + break; + } + else + ht &= l.t[i] == ' ' + && RS_SAME (l.r[i], base_rend); + + if (count) + x = ncol - 1; + + // store horizontal tab commands as characters inside the text + // buffer so they can be selected and pasted. + if (ht && option (Opt_pastableTabs)) + { + base_rend = SET_FONT (base_rend, 0); + + l.touch (x); + + i = screen.cur.col; + + l.t[i] = '\t'; + l.r[i] = base_rend; + + while (++i < x) + { + l.t[i] = NOCHAR; + l.r[i] = base_rend; + } + } + } + else /* if (count < 0) */ + { + for (; --i >= 0; ) + if (tabs[i]) + { + x = i; + if (!++count) + |