2 * Copyright (c) 1993, 1994
3 * The Regents of the University of California. All rights reserved.
4 * Copyright (c) 1992, 1993, 1994, 1995, 1996
5 * Keith Bostic. All rights reserved.
7 * See the LICENSE file for redistribution information.
14 static const char sccsid[] = "@(#)vs_line.c 10.19 (Berkeley) 9/26/96";
16 static const char rcsid[] =
20 #include <sys/types.h>
21 #include <sys/queue.h>
24 #include <bitstring.h>
29 #include "../common/common.h"
32 #ifdef VISIBLE_TAB_CHARS
40 * Update one line on the screen.
42 * PUBLIC: int vs_line __P((SCR *, SMAP *, size_t *, size_t *));
45 vs_line(sp, smp, yp, xp)
53 size_t chlen, cno_cnt, cols_per_screen, len, nlen;
54 size_t offset_in_char, offset_in_line, oldx, oldy;
55 size_t scno, skip_cols, skip_screens;
56 int ch, dne, is_cached, is_partial, is_tab, no_draw;
57 int list_tab, list_dollar;
58 char *p, *cbp, *ecbp, cbuf[128];
60 #if defined(DEBUG) && 0
61 TRACE(sp, "vs_line: row %u: line: %u off: %u\n",
62 smp - HMAP, smp->lno, smp->off);
65 * If ex modifies the screen after ex output is already on the screen,
66 * don't touch it -- we'll get scrolling wrong, at best.
69 if (!F_ISSET(sp, SC_TINPUT_INFO) && VIP(sp)->totalcount > 1)
71 if (F_ISSET(sp, SC_SCR_EXWROTE) && smp - HMAP != LASTLINE(sp))
75 * Assume that, if the cache entry for the line is filled in, the
76 * line is already on the screen, and all we need to do is return
77 * the cursor position. If the calling routine doesn't need the
78 * cursor position, we can just return.
80 is_cached = SMAP_CACHE(smp);
81 if (yp == NULL && (is_cached || no_draw))
85 * A nasty side effect of this routine is that it returns the screen
86 * position for the "current" character. Not pretty, but this is the
87 * only routine that really knows what's out there.
89 * Move to the line. This routine can be called by vs_sm_position(),
90 * which uses it to fill in the cache entry so it can figure out what
91 * the real contents of the screen are. Because of this, we have to
92 * return to whereever we started from.
95 (void)gp->scr_cursor(sp, &oldy, &oldx);
96 (void)gp->scr_move(sp, smp - HMAP, 0);
99 dne = db_get(sp, smp->lno, 0, &p, &len);
102 * Special case if we're printing the info/mode line. Skip printing
103 * the leading number, as well as other minor setup. The only time
104 * this code paints the mode line is when the user is entering text
105 * for a ":" command, so we can put the code here instead of dealing
106 * with the empty line logic below. This is a kludge, but it's pretty
107 * much confined to this module.
109 * Set the number of columns for this screen.
110 * Set the number of chars or screens to skip until a character is to
113 cols_per_screen = sp->cols;
114 if (O_ISSET(sp, O_LEFTRIGHT)) {
116 skip_cols = smp->coff;
118 skip_screens = smp->soff - 1;
119 skip_cols = skip_screens * cols_per_screen;
122 list_tab = O_ISSET(sp, O_LIST);
123 if (F_ISSET(sp, SC_TINPUT_INFO))
126 list_dollar = list_tab;
129 * If O_NUMBER is set, the line doesn't exist and it's line
130 * number 1, i.e., an empty file, display the line number.
132 * If O_NUMBER is set, the line exists and the first character
133 * on the screen is the first character in the line, display
137 * If O_NUMBER set, decrement the number of columns in the
138 * first screen. DO NOT CHANGE THIS -- IT'S RIGHT! The
139 * rest of the code expects this to reflect the number of
140 * columns in the first screen, regardless of the number of
141 * columns we're going to skip.
143 if (O_ISSET(sp, O_NUMBER)) {
144 cols_per_screen -= O_NUMBER_LENGTH;
145 if ((!dne || smp->lno == 1) && skip_cols == 0) {
146 nlen = snprintf(cbuf, sizeof(cbuf),
147 O_NUMBER_FMT, (u_long)smp->lno);
148 (void)gp->scr_addstr(sp, cbuf, nlen);
154 * Special case non-existent lines and the first line of an empty
155 * file. In both cases, the cursor position is 0, but corrected
156 * as necessary for the O_NUMBER field, if it was displayed.
158 if (dne || len == 0) {
159 /* Fill in the cursor. */
160 if (yp != NULL && smp->lno == sp->lno) {
162 *xp = sp->cols - cols_per_screen;
165 /* If the line is on the screen, quit. */
166 if (is_cached || no_draw)
169 /* Set line cache information. */
170 smp->c_sboff = smp->c_eboff = 0;
171 smp->c_scoff = smp->c_eclen = 0;
174 * Lots of special cases for empty lines, but they only apply
175 * if we're displaying the first screen of the line.
191 empty: (void)gp->scr_addstr(sp,
192 KEY_NAME(sp, ch), KEY_LEN(sp, ch));
195 (void)gp->scr_clrtoeol(sp);
196 (void)gp->scr_move(sp, oldy, oldx);
201 * If we just wrote this or a previous line, we cached the starting
202 * and ending positions of that line. The way it works is we keep
203 * information about the lines displayed in the SMAP. If we're
204 * painting the screen in the forward direction, this saves us from
205 * reformatting the physical line for every line on the screen. This
206 * wins big on binary files with 10K lines.
208 * Test for the first screen of the line, then the current screen line,
209 * then the line behind us, then do the hard work. Note, it doesn't
210 * do us any good to have a line in front of us -- it would be really
211 * hard to try and figure out tabs in the reverse direction, i.e. how
212 * many spaces a tab takes up in the reverse direction depends on
213 * what characters preceded it.
215 * Test for the first screen of the line.
217 if (skip_cols == 0) {
218 smp->c_sboff = offset_in_line = 0;
219 smp->c_scoff = offset_in_char = 0;
220 p = &p[offset_in_line];
224 /* Test to see if we've seen this exact line before. */
226 offset_in_line = smp->c_sboff;
227 offset_in_char = smp->c_scoff;
228 p = &p[offset_in_line];
230 /* Set cols_per_screen to 2nd and later line length. */
231 if (O_ISSET(sp, O_LEFTRIGHT) || skip_cols > cols_per_screen)
232 cols_per_screen = sp->cols;
236 /* Test to see if we saw an earlier part of this line before. */
238 SMAP_CACHE(tsmp = smp - 1) && tsmp->lno == smp->lno) {
239 if (tsmp->c_eclen != tsmp->c_ecsize) {
240 offset_in_line = tsmp->c_eboff;
241 offset_in_char = tsmp->c_eclen;
243 offset_in_line = tsmp->c_eboff + 1;
247 /* Put starting info for this line in the cache. */
248 smp->c_sboff = offset_in_line;
249 smp->c_scoff = offset_in_char;
250 p = &p[offset_in_line];
252 /* Set cols_per_screen to 2nd and later line length. */
253 if (O_ISSET(sp, O_LEFTRIGHT) || skip_cols > cols_per_screen)
254 cols_per_screen = sp->cols;
262 /* Do it the hard way, for leftright scrolling screens. */
263 if (O_ISSET(sp, O_LEFTRIGHT)) {
264 for (; offset_in_line < len; ++offset_in_line) {
265 chlen = (ch = *(u_char *)p++) == '\t' && !list_tab ?
266 TAB_OFF(scno) : KEY_LEN(sp, ch);
267 if ((scno += chlen) >= skip_cols)
271 /* Set cols_per_screen to 2nd and later line length. */
272 cols_per_screen = sp->cols;
274 /* Put starting info for this line in the cache. */
275 if (offset_in_line >= len) {
276 smp->c_sboff = offset_in_line;
278 } else if (scno != skip_cols) {
279 smp->c_sboff = offset_in_line;
281 offset_in_char = chlen - (scno - skip_cols);
284 smp->c_sboff = ++offset_in_line;
289 /* Do it the hard way, for historic line-folding screens. */
291 for (; offset_in_line < len; ++offset_in_line) {
292 chlen = (ch = *(u_char *)p++) == '\t' && !list_tab ?
293 TAB_OFF(scno) : KEY_LEN(sp, ch);
294 if ((scno += chlen) < cols_per_screen)
296 scno -= cols_per_screen;
298 /* Set cols_per_screen to 2nd and later line length. */
299 cols_per_screen = sp->cols;
302 * If crossed the last skipped screen boundary, start
303 * displaying the characters.
305 if (--skip_screens == 0)
309 /* Put starting info for this line in the cache. */
311 smp->c_sboff = offset_in_line;
312 smp->c_scoff = offset_in_char = chlen - scno;
315 smp->c_sboff = ++offset_in_line;
322 * Set the number of characters to skip before reaching the cursor
323 * character. Offset by 1 and use 0 as a flag value. Vs_line is
324 * called repeatedly with a valid pointer to a cursor position.
325 * Don't fill anything in unless it's the right line and the right
326 * character, and the right part of the character...
329 smp->lno != sp->lno || sp->cno < offset_in_line ||
330 offset_in_line + cols_per_screen < sp->cno) {
332 /* If the line is on the screen, quit. */
333 if (is_cached || no_draw)
336 cno_cnt = (sp->cno - offset_in_line) + 1;
338 /* This is the loop that actually displays characters. */
339 ecbp = (cbp = cbuf) + sizeof(cbuf) - 1;
340 for (is_partial = 0, scno = 0;
341 offset_in_line < len; ++offset_in_line, offset_in_char = 0) {
342 if ((ch = *(u_char *)p++) == '\t' && !list_tab) {
343 scno += chlen = TAB_OFF(scno) - offset_in_char;
346 scno += chlen = KEY_LEN(sp, ch) - offset_in_char;
351 * Only display up to the right-hand column. Set a flag if
352 * the entire character wasn't displayed for use in setting
353 * the cursor. If reached the end of the line, set the cache
354 * info for the screen. Don't worry about there not being
355 * characters to display on the next screen, its lno/off won't
356 * match up in that case.
358 if (scno >= cols_per_screen) {
360 chlen -= scno - cols_per_screen;
361 smp->c_ecsize = smp->c_eclen = chlen;
362 scno = cols_per_screen;
364 smp->c_ecsize = chlen;
365 chlen -= scno - cols_per_screen;
366 smp->c_eclen = chlen;
368 if (scno > cols_per_screen)
371 smp->c_eboff = offset_in_line;
373 /* Terminate the loop. */
374 offset_in_line = len;
378 * If the caller wants the cursor value, and this was the
379 * cursor character, set the value. There are two ways to
380 * put the cursor on a character -- if it's normal display
381 * mode, it goes on the last column of the character. If
382 * it's input mode, it goes on the first. In normal mode,
383 * set the cursor only if the entire character was displayed.
386 --cno_cnt == 0 && (F_ISSET(sp, SC_TINPUT) || !is_partial)) {
388 if (F_ISSET(sp, SC_TINPUT))
392 if (O_ISSET(sp, O_NUMBER) &&
393 !F_ISSET(sp, SC_TINPUT_INFO) && skip_cols == 0)
394 *xp += O_NUMBER_LENGTH;
396 /* If the line is on the screen, quit. */
397 if (is_cached || no_draw)
401 /* If the line is on the screen, don't display anything. */
402 if (is_cached || no_draw)
407 (void)gp->scr_addstr(sp, cbuf, cbp - cbuf); \
411 * Display the character. We do tab expansion here because
412 * the screen interface doesn't have any way to set the tab
413 * length. Note, it's theoretically possible for chlen to
414 * be larger than cbuf, if the user set a impossibly large
424 if (cbp + chlen >= ecbp)
426 for (kp = KEY_NAME(sp, ch) + offset_in_char; chlen--;)
431 if (scno < cols_per_screen) {
432 /* If didn't paint the whole line, update the cache. */
433 smp->c_ecsize = smp->c_eclen = KEY_LEN(sp, ch);
434 smp->c_eboff = len - 1;
437 * If not the info/mode line, and O_LIST set, and at the
438 * end of the line, and the line ended on this screen,
444 chlen = KEY_LEN(sp, '$');
445 if (cbp + chlen >= ecbp)
447 for (kp = KEY_NAME(sp, '$'); chlen--;)
451 /* If still didn't paint the whole line, clear the rest. */
452 if (scno < cols_per_screen)
453 (void)gp->scr_clrtoeol(sp);
456 /* Flush any buffered characters. */
460 ret1: (void)gp->scr_move(sp, oldy, oldx);
466 * Repaint the numbers on all the lines.
468 * PUBLIC: int vs_number __P((SCR *));
477 size_t len, oldy, oldx;
484 /* No reason to do anything if we're in input mode on the info line. */
485 if (F_ISSET(sp, SC_TINPUT_INFO))
489 * Try and avoid getting the last line in the file, by getting the
490 * line after the last line in the screen -- if it exists, we know
491 * we have to to number all the lines in the screen. Get the one
492 * after the last instead of the last, so that the info line doesn't
493 * fool us. (The problem is that file_lline will lie, and tell us
494 * that the info line is the last line in the file.) If that test
495 * fails, we have to check each line for existence.
497 exist = db_exist(sp, TMAP->lno + 1);
499 (void)gp->scr_cursor(sp, &oldy, &oldx);
500 for (smp = HMAP; smp <= TMAP; ++smp) {
501 /* Numbers are only displayed for the first screen line. */
502 if (O_ISSET(sp, O_LEFTRIGHT)) {
510 * The first line of an empty file gets numbered, otherwise
511 * number any existing line.
513 if (smp->lno != 1 && !exist && !db_exist(sp, smp->lno))
516 (void)gp->scr_move(sp, smp - HMAP, 0);
517 len = snprintf(nbuf, sizeof(nbuf),
518 O_NUMBER_FMT, (u_long)smp->lno);
519 (void)gp->scr_addstr(sp, nbuf, len);
521 (void)gp->scr_move(sp, oldy, oldx);