version 1.0
[nvi.git] / vi / vs_line.c
blobb3d1a1695cec34dce41b44603fd4234de5d8ad3d
1 /*-
2 * Copyright (c) 1993
3 * The Regents of the University of California. All rights reserved.
5 * %sccs.include.redist.c%
6 */
8 #ifndef lint
9 static char sccsid[] = "$Id: vs_line.c,v 8.16 1993/12/23 17:42:05 bostic Exp $ (Berkeley) $Date: 1993/12/23 17:42:05 $";
10 #endif /* not lint */
12 #include <sys/types.h>
14 #include <curses.h>
15 #include <string.h>
17 #include "vi.h"
18 #include "svi_screen.h"
20 #if defined(DEBUG) && 0
21 #define TABCH '-'
22 #define TABSTR "--------------------"
23 #else
24 #define TABSTR " "
25 #define TABCH ' '
26 #endif
29 * svi_line --
30 * Update one line on the screen.
32 int
33 svi_line(sp, ep, smp, yp, xp)
34 SCR *sp;
35 EXF *ep;
36 SMAP *smp;
37 size_t *xp, *yp;
39 CHNAME const *cname;
40 SMAP *tsmp;
41 size_t chlen, cols_per_screen, cno_cnt, len, scno, skip_screens;
42 size_t offset_in_char, offset_in_line;
43 size_t oldy, oldx;
44 int ch, is_cached, is_infoline, is_partial, is_tab, listset;
45 char *p, nbuf[10];
47 #if defined(DEBUG) && 0
48 TRACE(sp, "svi_line: row %u: line: %u off: %u\n",
49 smp - HMAP, smp->lno, smp->off);
50 #endif
53 * Assume that, if the cache entry for the line is filled in, the
54 * line is already on the screen, and all we need to do is return
55 * the cursor position. If the calling routine doesn't need the
56 * cursor position, we can just return.
58 is_cached = SMAP_CACHE(smp);
59 if (yp == NULL && is_cached)
60 return (0);
63 * A nasty side effect of this routine is that it returns the screen
64 * position for the "current" character. Not pretty, but this is the
65 * only routine that really knows what's out there.
67 * Move to the line. This routine can be called by svi_sm_position(),
68 * which uses it to fill in the cache entry so it can figure out what
69 * the real contents of the screen are. Because of this, we have to
70 * return to whereever we started from.
72 getyx(stdscr, oldy, oldx);
73 MOVE(sp, smp - HMAP, 0);
75 /* Get the character map. */
76 cname = sp->gp->cname;
78 /* Get a copy of the line. */
79 p = file_gline(sp, ep, smp->lno, &len);
82 * Special case if we're printing the info/mode line. Skip printing
83 * the leading number, as well as other minor setup. If painting the
84 * line between two screens, it's always in reverse video. The only
85 * time this code paints the mode line is when the user is entering
86 * text for a ":" command, so we can put the code here instead of
87 * dealing with the empty line logic below. This is a kludge, but it's
88 * pretty much confined to this module.
90 * Set the number of screens to skip until a character is displayed.
91 * Left-right screens are special, because we don't bother building
92 * a buffer to be skipped over.
94 * Set the number of columns for this screen.
96 cols_per_screen = sp->cols;
97 if (is_infoline = ISINFOLINE(sp, smp)) {
98 listset = 0;
99 if (O_ISSET(sp, O_LEFTRIGHT))
100 skip_screens = 0;
101 else
102 skip_screens = smp->off - 1;
103 } else {
104 listset = O_ISSET(sp, O_LIST);
105 skip_screens = smp->off - 1;
108 * If O_NUMBER is set and it's line number 1 or the line exists
109 * and this is the first screen of a folding line or any left-
110 * right line, display the line number.
112 if (O_ISSET(sp, O_NUMBER)) {
113 cols_per_screen -= O_NUMBER_LENGTH;
114 if ((smp->lno == 1 || p != NULL) && skip_screens == 0) {
115 (void)snprintf(nbuf,
116 sizeof(nbuf), O_NUMBER_FMT, smp->lno);
117 ADDSTR(nbuf);
123 * Special case non-existent lines and the first line of an empty
124 * file. In both cases, the cursor position is 0, but corrected
125 * for the O_NUMBER field if it was displayed.
127 if (p == NULL || len == 0) {
128 /* Fill in the cursor. */
129 if (yp != NULL && smp->lno == sp->lno) {
130 *yp = smp - HMAP;
131 *xp = sp->cols - cols_per_screen;
134 /* If the line is on the screen, quit. */
135 if (is_cached)
136 goto ret;
138 /* Set line cacheing information. */
139 smp->c_sboff = smp->c_eboff = 0;
140 smp->c_scoff = smp->c_eclen = 0;
142 if (p == NULL) {
143 if (smp->lno != 1)
144 ADDCH(listset && skip_screens == 0 ? '$' : '~');
145 } else if (listset && skip_screens == 0)
146 ADDCH('$');
148 clrtoeol();
149 MOVEA(sp, oldy, oldx);
150 return (0);
154 * If we wrote a line that's this or a previous one, we can do this
155 * much more quickly -- we cached the starting and ending positions
156 * of that line. The way it works is we keep information about the
157 * lines displayed in the SMAP. If we're painting the screen in
158 * the forward, this saves us from reformatting the physical line for
159 * every line on the screen. This wins big on binary files with 10K
160 * lines.
162 * Test for the first screen of the line, then the current screen line,
163 * then the line behind us, then do the hard work. Note, it doesn't
164 * do us any good to have a line in front of us -- it would be really
165 * hard to try and figure out tabs in the reverse direction, i.e. how
166 * many spaces a tab takes up in the reverse direction depends on
167 * what characters preceded it.
169 if (smp->off == 1) {
170 smp->c_sboff = offset_in_line = 0;
171 smp->c_scoff = offset_in_char = 0;
172 p = &p[offset_in_line];
173 } else if (is_cached) {
174 offset_in_line = smp->c_sboff;
175 offset_in_char = smp->c_scoff;
176 p = &p[offset_in_line];
177 } else if (smp != HMAP &&
178 SMAP_CACHE(tsmp = smp - 1) && tsmp->lno == smp->lno) {
179 if (tsmp->c_eclen != tsmp->c_ecsize) {
180 offset_in_line = tsmp->c_eboff;
181 offset_in_char = tsmp->c_eclen;
182 } else {
183 offset_in_line = tsmp->c_eboff + 1;
184 offset_in_char = 0;
187 /* Put starting info for this line in the cache. */
188 smp->c_sboff = offset_in_line;
189 smp->c_scoff = offset_in_char;
190 p = &p[offset_in_line];
191 } else {
192 offset_in_line = 0;
193 offset_in_char = 0;
195 /* This is the loop that skips through screens. */
196 if (skip_screens == 0) {
197 smp->c_sboff = offset_in_line;
198 smp->c_scoff = offset_in_char;
199 } else for (scno = 0; offset_in_line < len; ++offset_in_line) {
200 scno += chlen =
201 (ch = *(u_char *)p++) == '\t' && !listset ?
202 TAB_OFF(sp, scno) : cname[ch].len;
203 if (scno < cols_per_screen)
204 continue;
206 * Reset cols_per_screen to second and subsequent line
207 * length.
209 scno -= cols_per_screen;
210 cols_per_screen = sp->cols;
213 * If crossed the last skipped screen boundary, start
214 * displaying the characters.
216 if (--skip_screens)
217 continue;
219 /* Put starting info for this line in the cache. */
220 if (scno) {
221 smp->c_sboff = offset_in_line;
222 smp->c_scoff = offset_in_char = chlen - scno;
223 --p;
224 } else {
225 smp->c_sboff = ++offset_in_line;
226 smp->c_scoff = 0;
228 break;
233 * Set the number of characters to skip before reaching the cursor
234 * character. Offset by 1 and use 0 as a flag value. Svi_line is
235 * called repeatedly with a valid pointer to a cursor position.
236 * Don't fill anything in unless it's the right line and the right
237 * character, and the right part of the character...
239 if (yp == NULL ||
240 smp->lno != sp->lno || sp->cno < offset_in_line ||
241 offset_in_line + cols_per_screen < sp->cno) {
242 cno_cnt = 0;
243 /* If the line is on the screen, quit. */
244 if (is_cached)
245 goto ret;
246 } else
247 cno_cnt = (sp->cno - offset_in_line) + 1;
249 /* This is the loop that actually displays characters. */
250 for (is_partial = 0, scno = 0;
251 offset_in_line < len; ++offset_in_line, offset_in_char = 0) {
252 if ((ch = *(u_char *)p++) == '\t' && !listset) {
253 scno += chlen = TAB_OFF(sp, scno) - offset_in_char;
254 is_tab = 1;
255 } else {
256 scno += chlen = cname[ch].len - offset_in_char;
257 is_tab = 0;
261 * Only display up to the right-hand column. Set a flag if
262 * the entire character wasn't displayed for use in setting
263 * the cursor. If reached the end of the line, set the cache
264 * info for the screen. Don't worry about there not being
265 * characters to display on the next screen, its lno/off won't
266 * match up in that case.
268 if (scno >= cols_per_screen) {
269 smp->c_ecsize = chlen;
270 chlen -= scno - cols_per_screen;
271 smp->c_eclen = chlen;
272 smp->c_eboff = offset_in_line;
273 if (scno > cols_per_screen)
274 is_partial = 1;
276 /* Terminate the loop. */
277 offset_in_line = len;
281 * If the caller wants the cursor value, and this was the
282 * cursor character, set the value. There are two ways to
283 * put the cursor on a character -- if it's normal display
284 * mode, it goes on the last column of the character. If
285 * it's input mode, it goes on the first. In normal mode,
286 * set the cursor only if the entire character was displayed.
288 if (cno_cnt &&
289 --cno_cnt == 0 && (F_ISSET(sp, S_INPUT) || !is_partial)) {
290 *yp = smp - HMAP;
291 if (F_ISSET(sp, S_INPUT))
292 *xp = scno - chlen;
293 else
294 *xp = scno - 1;
295 if (O_ISSET(sp, O_NUMBER) &&
296 !is_infoline && smp->off == 1)
297 *xp += O_NUMBER_LENGTH;
299 /* If the line is on the screen, quit. */
300 if (is_cached)
301 goto ret;
304 /* If the line is on the screen, don't display anything. */
305 if (is_cached)
306 continue;
309 * Display the character. If it's a tab and tabs aren't some
310 * ridiculous length, do it fast. (We do tab expansion here
311 * because curses doesn't have a way to set the tab length.)
313 if (is_tab) {
314 if (chlen <= sizeof(TABSTR) - 1) {
315 ADDNSTR(TABSTR, chlen);
316 } else
317 while (chlen--)
318 ADDCH(TABCH);
319 } else
320 ADDNSTR(cname[ch].name + offset_in_char, chlen);
323 if (scno < cols_per_screen) {
324 /* If didn't paint the whole line, update the cache. */
325 smp->c_ecsize = smp->c_eclen = cname[ch].len;
326 smp->c_eboff = len - 1;
329 * If not the info/mode line, and O_LIST set, and at the
330 * end of the line, and the line ended on this screen,
331 * add a trailing $.
333 if (listset) {
334 ++scno;
335 ADDCH('$');
338 /* If still didn't paint the whole line, clear the rest. */
339 if (scno < cols_per_screen)
340 clrtoeol();
343 ret: MOVEA(sp, oldy, oldx);
344 return (0);
348 * svi_number --
349 * Repaint the numbers on all the lines.
352 svi_number(sp, ep)
353 SCR *sp;
354 EXF *ep;
356 SMAP *smp;
357 recno_t lno;
358 size_t oldy, oldx;
359 char *p, nbuf[10];
362 * Try and avoid getting the last line in the file, by getting the
363 * line after the last line in the screen -- if it exists, we know
364 * we have to to number all the lines in the screen. Get the one
365 * after the last instead of the last, so that the info line doesn't
366 * fool us.
368 * If that test fails, we have to check each line for existence.
370 * XXX
371 * The problem is that file_lline will lie, and tell us that the
372 * info line is the last line in the file.
374 if ((p = file_gline(sp, ep, TMAP->lno - 1, NULL)) != NULL)
375 lno = TMAP->lno + 1;
377 getyx(stdscr, oldy, oldx);
378 for (smp = HMAP; smp <= TMAP; ++smp) {
379 if (smp->off != 1)
380 continue;
381 if (ISINFOLINE(sp, smp))
382 break;
383 if (smp->lno != 1)
384 if (p != NULL) {
385 if (smp->lno > lno)
386 break;
387 } else {
388 if ((p =
389 file_gline(sp, ep, smp->lno, NULL)) == NULL)
390 break;
391 p = NULL;
393 MOVE(sp, smp - HMAP, 0);
394 (void)snprintf(nbuf, sizeof(nbuf), O_NUMBER_FMT, smp->lno);
395 ADDSTR(nbuf);
397 MOVEA(sp, oldy, oldx);
398 return (0);