/* redraw.c */ /* Author: * Steve Kirkendall * 14407 SW Teal Blvd. #C * Beaverton, OR 97005 * kirkenda@cs.pdx.edu */ /* This file contains functions that draw text on the screen. The major entry * points are: * redrawrange() - called from modify.c to give hints about what parts * of the screen need to be redrawn. * redraw() - redraws the screen (or part of it) and positions * the cursor where it belongs. * idx2col() - converts a markidx() value to a logical column number. */ #include "config.h" #include "vi.h" /* This variable contains the line number that smartdrawtext() knows best */ static long smartlno; /* This function remebers where changes were made, so that the screen can be * redraw in a more efficient manner. */ static long redrawafter; /* line# of first line that must be redrawn */ static long preredraw; /* line# of last line changed, before change */ static long postredraw; /* line# of last line changed, after change */ void redrawrange(after, pre, post) long after; /* lower bound of redrawafter */ long pre; /* upper bound of preredraw */ long post; /* upper bound of postredraw */ { if (after == redrawafter) { /* multiple insertions/deletions at the same place -- combine * them */ preredraw -= (post - pre); if (postredraw < post) { preredraw += (post - postredraw); postredraw = post; } if (redrawafter > preredraw) { redrawafter = preredraw; } if (redrawafter < 1L) { redrawafter = 0L; preredraw = postredraw = INFINITY; } } else if (postredraw > 0L) { /* multiple changes in different places -- redraw everything * after "after". */ postredraw = preredraw = INFINITY; if (after < redrawafter) redrawafter = after; } else { /* first change */ redrawafter = after; preredraw = pre; postredraw = post; } } #ifndef NO_CHARATTR /* see if a given line uses character attribute strings */ static int hasattr(lno, text) long lno; /* the line# of the cursor */ REG char *text; /* the text of the line, from fetchline */ { static long plno; /* previous line number */ static long chgs; /* previous value of changes counter */ static int panswer;/* previous answer */ char *scan; /* if charattr is off, then the answer is "no, it doesn't" */ if (!*o_charattr) { chgs = 0; /* <- forces us to check if charattr is later set */ return FALSE; } /* if we already know the answer, return it... */ if (lno == plno && chgs == changes) { return panswer; } /* get the line & look for "\fX" */ if (!text[0] || !text[1] || !text[2]) { panswer = FALSE; } else { for (scan = text; scan[2] && !(scan[0] == '\\' && scan[1] == 'f'); scan++) { } panswer = (scan[2] != '\0'); } /* save the results */ plno = lno; chgs = changes; /* return the results */ return panswer; } #endif /* This function converts a MARK to a column number. It doesn't automatically * adjust for leftcol; that must be done by the calling function */ int idx2col(curs, text, inputting) MARK curs; /* the line# & index# of the cursor */ REG char *text; /* the text of the line, from fetchline */ int inputting; /* boolean: called from input() ? */ { static MARK pcursor;/* previous cursor, for possible shortcut */ static MARK pcol; /* column number for pcol */ static long chgs; /* previous value of changes counter */ REG int col; /* used to count column numbers */ REG int idx; /* used to count down the index */ REG int i; /* for now, assume we have to start counting at the left edge */ col = 0; idx = markidx(curs); /* if the file hasn't changed & line number is the same & it has no * embedded character attribute strings, can we do shortcuts? */ if (chgs == changes && !((curs ^ pcursor) & ~(BLKSIZE - 1)) #ifndef NO_CHARATTR && !hasattr(markline(curs), text) #endif ) { /* no movement? */ if (curs == pcursor) { /* return the column of the char; for tabs, return its last column */ if (text[idx] == '\t' && !inputting && !*o_list) { return pcol + *o_tabstop - (pcol % *o_tabstop) - 1; } else { return pcol; } } /* movement to right? */ if (curs > pcursor) { /* start counting from previous place */ col = pcol; idx = markidx(curs) - markidx(pcursor); text += markidx(pcursor); } } /* count over to the char after the idx position */ while (idx > 0 && (i = *text)) /* yes, ASSIGNMENT! */ { if (i == '\t' && !*o_list) { col += *o_tabstop; col -= col % *o_tabstop; } else if (i >= '\0' && i < ' ' || i == '\177') { col += 2; } #ifndef NO_CHARATTR else if (i == '\\' && text[1] == 'f' && text[2] && *o_charattr) { text += 2; /* plus one more at bottom of loop */ idx -= 2; } #endif else { col++; } text++; idx--; } /* save stuff to speed next call */ pcursor = curs; pcol = col; chgs = changes; /* return the column of the char; for tabs, return its last column */ if (*text == '\t' && !inputting && !*o_list) { return col + *o_tabstop - (col % *o_tabstop) - 1; } else { return col; } } /* This function is similar to idx2col except that it takes care of sideways * scrolling - for the given line, at least. */ int mark2phys(m, text, inputting) MARK m; /* a mark to convert */ char *text; /* the line that m refers to */ int inputting; /* boolean: caled from input() ? */ { int i; i = idx2col(m, text, inputting); while (i < leftcol) { leftcol -= *o_sidescroll; mustredraw = TRUE; redrawrange(1L, INFINITY, INFINITY); qaddch('\r'); /* drawtext(text); */ } while (i > rightcol) { leftcol += *o_sidescroll; mustredraw = TRUE; redrawrange(1L, INFINITY, INFINITY); qaddch('\r'); /* drawtext(text); */ } physcol = i - leftcol; physrow = markline(m) - topline; return physcol; } /* This function draws a single line of text on the screen. The screen's * cursor is assumed to be located at the leftmost column of the appropriate * row. */ static void drawtext(text, clr) REG char *text; /* the text to draw */ int clr; /* boolean: do a clrtoeol? */ { REG int col; /* column number */ REG int i; REG int tabstop; /* *o_tabstop */ REG int limitcol; /* leftcol or leftcol + COLS */ int abnormal; /* boolean: charattr != A_NORMAL? */ #ifndef NO_SENTENCE /* if we're hiding format lines, and this is one of them, then hide it */ if (*o_hideformat && *text == '.') { clrtoeol(); #if OSK qaddch('\l'); #else qaddch('\n'); #endif return; } #endif /* move some things into registers... */ limitcol = leftcol; tabstop = *o_tabstop; abnormal = FALSE; #ifndef CRUNCH if (clr) clrtoeol(); #endif /* skip stuff that was scrolled off left edge */ for (col = 0; (i = *text) && col < limitcol; /* yes, ASSIGNMENT! */ text++) { if (i == '\t' && !*o_list) { col = col + tabstop - (col % tabstop); } else if (i >= 0 && i < ' ' || i == '\177') { col += 2; } #ifndef NO_CHARATTR else if (i == '\\' && text[1] == 'f' && text[2] && *o_charattr) { text += 2; /* plus one more as part of "for" loop */ /* since this attribute might carry over, we need it */ switch (*text) { case 'R': case 'P': attrset(A_NORMAL); abnormal = FALSE; break; case 'B': attrset(A_BOLD); abnormal = TRUE; break; case 'U': attrset(A_UNDERLINE); abnormal = TRUE; break; case 'I': attrset(A_ALTCHARSET); abnormal = TRUE; break; } } #endif else { col++; } } /* adjust for control char that was partially visible */ while (col > limitcol) { qaddch(' '); limitcol++; } /* now for the visible characters */ for (limitcol = leftcol + COLS; (i = *text) && col < limitcol; text++) { if (i == '\t' && !*o_list) { i = col + tabstop - (col % tabstop); #if 1 /* * With modern terminals, autowrap occurs only * if a new character is added after limitcol. * -- miquels@cistron.nl 23-Jan-2002 */ if (i < limitcol || (has_AM && i <= limitcol)) #else if (i < limitcol) #endif { #ifdef CRUNCH if (!clr && has_PT && !((i - leftcol) & 7)) #else if (has_PT && !((i - leftcol) & 7)) #endif { do { qaddch('\t'); col += 8; /* not exact! */ } while (col < i); col = i; /* NOW it is exact */ } else { do { qaddch(' '); col++; } while (col < i); } } else /* tab ending after screen? next line! */ { col = limitcol; if (has_AM) { addch('\n'); /* GB */ } } } else if (i >= 0 && i < ' ' || i == '\177') { col += 2; qaddch('^'); if (col <= limitcol) { qaddch(i ^ '@'); } } #ifndef NO_CHARATTR else if (i == '\\' && text[1] == 'f' && text[2] && *o_charattr) { text += 2; /* plus one more as part of "for" loop */ switch (*text) { case 'R': case 'P': attrset(A_NORMAL); abnormal = FALSE; break; case 'B': attrset(A_BOLD); abnormal = TRUE; break; case 'U': attrset(A_UNDERLINE); abnormal = TRUE; break; case 'I': attrset(A_ALTCHARSET); abnormal = TRUE; break; } } #endif else { col++; qaddch(i); } } /* get ready for the next line */ #ifndef NO_CHARATTR if (abnormal) { attrset(A_NORMAL); } #endif if (*o_list && col < limitcol) { qaddch('$'); col++; } #ifdef CRUNCH if (clr && col < limitcol) { clrtoeol(); } #endif #if 1 /* * With modern terminals, autowrap occurs only * if a new character is added after limitcol. * -- miquels@cistron.nl 22-Feb-1998 */ if (!has_AM || col <= limitcol) #else if (!has_AM || col < limitcol) #endif { addch('\n'); } } #ifndef CRUNCH static void nudgecursor(same, scan, new, lno) int same; /* number of chars to be skipped over */ char *scan; /* where the same chars end */ char *new; /* where the visible part of the line starts */ long lno; /* line number of this line */ { if (same > 0) { if (same < 5) { /* move the cursor by overwriting */ while (same > 0) { qaddch(scan[-same]); same--; } } else { /* move the cursor by calling move() */ move((int)(lno - topline), (int)(scan - new)); } } } #endif /* not CRUNCH */ /* This function draws a single line of text on the screen, possibly with * some cursor optimization. The cursor is repositioned before drawing * begins, so its position before doesn't really matter. */ static void smartdrawtext(text, lno) REG char *text; /* the text to draw */ long lno; /* line number of the text */ { #ifdef CRUNCH move((int)(lno - topline), 0); drawtext(text, TRUE); #else /* not CRUNCH */ static char old[256]; /* how the line looked last time */ char new[256]; /* how it looks now */ char *build; /* used to put chars into new[] */ char *scan; /* used for moving thru new[] or old[] */ char *end; /* last non-blank changed char */ char *shift; /* used to insert/delete chars */ int same; /* length of a run of unchanged chars */ int limitcol; int col; int i; # ifndef NO_CHARATTR /* if this line has attributes, do it the dumb way instead */ if (hasattr(lno, text)) { move((int)(lno - topline), 0); drawtext(text, TRUE); return; } # endif # ifndef NO_SENTENCE /* if this line is a format line, & we're hiding format lines, then * let the dumb drawtext() function handle it */ if (*o_hideformat && *text == '.') { move((int)(lno - topline), 0); drawtext(text, TRUE); return; } # endif /* skip stuff that was scrolled off left edge */ limitcol = leftcol; for (col = 0; (i = *text) && col < limitcol; /* yes, ASSIGNMENT! */ text++) { if (i == '\t' && !*o_list) { col = col + *o_tabstop - (col % *o_tabstop); } else if (i >= 0 && i < ' ' || i == '\177') { col += 2; } else { col++; } } /* adjust for control char that was partially visible */ build = new; while (col > limitcol) { *build++ = ' '; limitcol++; } /* now for the visible characters */ for (limitcol = leftcol + COLS; (i = *text) && col < limitcol; text++) { if (i == '\t' && !*o_list) { i = col + *o_tabstop - (col % *o_tabstop); while (col < i && col < limitcol) { *build++ = ' '; col++; } } else if (i >= 0 && i < ' ' || i == '\177') { col += 2; *build++ = '^'; if (col <= limitcol) { *build++ = (i ^ '@'); } } else { col++; *build++ = i; } } if (col < limitcol && *o_list) { *build++ = '$'; col++; } end = build; while (col < limitcol) { *build++ = ' '; col++; } /* locate the last non-blank character */ while (end > new && end[-1] == ' ') { end--; } /* can we optimize the displaying of this line? */ if (lno != smartlno) { /* nope, can't optimize - different line */ move((int)(lno - topline), 0); for (scan = new, build = old; scan < end; ) { qaddch(*scan); *build++ = *scan++; } if (end < new + COLS) { clrtoeol(); while (build < old + COLS) { *build++ = ' '; } } smartlno = lno; return; } /* skip any initial unchanged characters */ for (scan = new, build = old; scan < end && *scan == *build; scan++, build++) { } move((int)(lno - topline), (int)(scan - new)); /* The in-between characters must be changed */ same = 0; while (scan < end) { /* is this character a match? */ if (scan[0] == build[0]) { same++; } else /* do we want to insert? */ if (scan < end - 1 && scan[1] == build[0] && (has_IC || has_IM)) { nudgecursor(same, scan, new, lno); same = 0; insch(*scan); for (shift = old + COLS; --shift > build; ) { shift[0] = shift[-1]; } *build = *scan; } else /* do we want to delete? */ if (build < old + COLS - 1 && scan[0] == build[1] && has_DC) { nudgecursor(same, scan, new, lno); same = 0; delch(); same++; for (shift = build; shift < old + COLS - 1; shift++) { shift[0] = shift[1]; } *shift = ' '; } else /* we must overwrite */ { nudgecursor(same, scan, new, lno); same = 0; addch(*scan); *build = *scan; } build++; scan++; } /* maybe clear to EOL */ while (build < old + COLS && *build == ' ') { build++; } if (build < old + COLS) { nudgecursor(same, scan, new, lno); same = 0; clrtoeol(); while (build < old + COLS) { *build++ = ' '; } } #endif /* not CRUNCH */ } /* This function is used in visual mode for drawing the screen (or just parts * of the screen, if that's all thats needed). It also takes care of * scrolling. */ void redraw(curs, inputting) MARK curs; /* where to leave the screen's cursor */ int inputting; /* boolean: being called from input() ? */ { char *text; /* a line of text to display */ static long chgs; /* previous changes level */ long l; int i; /* if curs == MARK_UNSET, then we should reset internal vars */ if (curs == MARK_UNSET) { if (topline < 1 || topline > nlines) { topline = 1L; } else { move(LINES - 1, 0); clrtoeol(); } leftcol = 0; mustredraw = TRUE; redrawafter = INFINITY; preredraw = 0L; postredraw = 0L; chgs = 0; smartlno = 0L; return; } /* figure out which column the cursor will be in */ l = markline(curs); text = fetchline(l); mark2phys(curs, text, inputting); /* adjust topline, if necessary, to get the cursor on the screen */ if (l >= topline && l <= botline) { /* it is on the screen already */ /* if the file was changed but !mustredraw, then redraw line */ if (chgs != changes && !mustredraw) { smartdrawtext(text, l); } } else if (l < topline && l > topline - LINES && (has_SR || has_AL)) { /* near top - scroll down */ if (!mustredraw) { move(0,0); while (l < topline) { topline--; if (has_SR) { do_SR(); } else { insertln(); } text = fetchline(topline); drawtext(text, FALSE); do_UP(); } /* blank out the last line */ move(LINES - 1, 0); clrtoeol(); } else { topline = l; redrawafter = INFINITY; preredraw = 0L; postredraw = 0L; } } else if (l > topline && l < botline + LINES) { /* near bottom -- scroll up */ if (!mustredraw #if 1 || redrawafter == preredraw && preredraw == botline && postredraw == l #endif ) { move(LINES - 1,0); clrtoeol(); while (l > botline) { topline++; /* <-- also adjusts botline */ text = fetchline(botline); drawtext(text, FALSE); } mustredraw = FALSE; } else { topline = l - (LINES - 2); redrawafter = INFINITY; preredraw = 0L; postredraw = 0L; } } else { /* distant line - center it & force a redraw */ topline = l - (LINES / 2) - 1; if (topline < 1) { topline = 1; } mustredraw = TRUE; redrawafter = INFINITY; preredraw = 0L; postredraw = 0L; } /* Now... do we really have to redraw? */ if (mustredraw) { /* If redrawfter (and friends) aren't set, assume we should * redraw everything. */ if (redrawafter == INFINITY) { redrawafter = 0L; preredraw = postredraw = INFINITY; } /* adjust smartlno to correspond with inserted/deleted lines */ if (smartlno >= redrawafter) { if (smartlno < preredraw) { smartlno = 0L; } else { smartlno += (postredraw - preredraw); } } /* should we insert some lines into the screen? */ if (preredraw < postredraw && preredraw <= botline) { /* lines were inserted into the file */ /* decide where insertion should start */ if (preredraw < topline) { l = topline; } else { l = preredraw; } /* insert the lines... maybe */ if (l + postredraw - preredraw > botline || !has_AL) { /* Whoa! a whole screen full - just redraw */ preredraw = postredraw = INFINITY; } else { /* really insert 'em */ move((int)(l - topline), 0); for (i = postredraw - preredraw; i > 0; i--) { insertln(); } /* NOTE: the contents of those lines will be * drawn as part of the regular redraw loop. */ /* clear the last line */ move(LINES - 1, 0); clrtoeol(); } } /* do we want to delete some lines from the screen? */ if (preredraw > postredraw && postredraw <= botline) { if (preredraw > botline || !has_DL) { postredraw = preredraw = INFINITY; } else /* we'd best delete some lines from the screen */ { /* clear the last line, so it doesn't look * ugly as it gets pulled up into the screen */ move(LINES - 1, 0); clrtoeol(); /* delete the lines */ move((int)(postredraw - topline), 0); for (l = postredraw; l < preredraw && l <= botline; l++) { deleteln(); } /* draw the lines that are now newly visible * at the bottom of the screen */ i = LINES - 1 + (postredraw - preredraw); move(i, 0); for (l = topline + i; l <= botline; l++) { /* clear this line */ clrtoeol(); /* draw the line, or ~ for non-lines */ if (l <= nlines) { text = fetchline(l); drawtext(text, FALSE); } else { addstr("~\n"); } } } } /* redraw the current line */ l = markline(curs); pfetch(l); smartdrawtext(ptext, l); /* decide where we should start redrawing from */ if (redrawafter < topline) { l = topline; } else { l = redrawafter; } move((int)(l - topline), 0); /* draw the other lines */ for (; l <= botline && l < postredraw; l++) { /* we already drew the current line, so skip it now */ if (l == smartlno) { #if OSK qaddch('\l'); #else qaddch('\n'); #endif continue; } /* draw the line, or ~ for non-lines */ if (l <= nlines) { text = fetchline(l); drawtext(text, TRUE); } else { qaddch('~'); clrtoeol(); addch('\n'); } } mustredraw = FALSE; } /* force total (non-partial) redraw next time if not set */ redrawafter = INFINITY; preredraw = 0L; postredraw = 0L; /* move the cursor to where it belongs */ move((int)(markline(curs) - topline), physcol); wqrefresh(stdscr); chgs = changes; }