add s_column so svi can return sc_col vi/v_ntext.c
[nvi.git] / vi / vs_line.c
blob5b4fbb5770e0b525f8155366ea04089885d0ba7f
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.11 1993/11/18 13:51:07 bostic Exp $ (Berkeley) $Date: 1993/11/18 13:51:07 $";
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 recno_t lno;
42 size_t chlen, cols_per_screen, cno_cnt, len, scno, skip_screens;
43 size_t offset_in_char, offset_in_line;
44 size_t oldy, oldx;
45 int ch, is_cached, is_infoline, is_partial, is_tab, listset;
46 int reverse_video;
47 char *p, nbuf[10];
49 #if defined(DEBUG) && 0
50 TRACE(sp, "svi_line: row %u: line: %u off: %u\n",
51 smp - HMAP, smp->lno, smp->off);
52 #endif
55 * Assume that, if the cache entry for the line is filled in, the
56 * line is already on the screen, and all we need to do is return
57 * the cursor position. If the calling routine doesn't need the
58 * cursor position, we can just return.
60 is_cached = SMAP_CACHE(smp);
61 if (yp == NULL && is_cached)
62 return (0);
65 * A nasty side effect of this routine is that it returns the screen
66 * position for the "current" character. Not pretty, but this is the
67 * only routine that really knows what's out there.
69 * Move to the line. This routine can be called by svi_sm_position(),
70 * which uses it to fill in the cache entry so it can figure out what
71 * the real contents of the screen are. Because of this, we have to
72 * return to whereever we started from.
74 getyx(stdscr, oldy, oldx);
75 MOVE(sp, smp - HMAP, 0);
77 /* Get the character map. */
78 cname = sp->cname;
81 * Special case if we're printing the info/mode line. Skip printing
82 * the leading number, as well as other minor setup. If painting the
83 * line between two screens, it's always in reverse video. The only
84 * time this code paints the mode line is when the user is entering
85 * text for a ":" command, so we can put the code here instead of
86 * dealing with the empty line logic below. This is a kludge, but it's
87 * pretty much confined to this module.
89 * Set the number of screens to skip until a character is displayed.
90 * Left-right screens are special, because we don't bother building
91 * a buffer to be skipped over.
93 * If O_NUMBER is set and this is the first screen of a folding
94 * line or any left-right line, display the line number. Set
95 * the number of columns for this screen.
97 reverse_video = 0;
98 cols_per_screen = sp->cols;
99 if (is_infoline = ISINFOLINE(sp, smp)) {
100 if (sp->q.cqe_next != (void *)&sp->gp->dq) {
101 reverse_video = 1;
102 standout();
104 listset = 0;
105 if (O_ISSET(sp, O_LEFTRIGHT))
106 skip_screens = 0;
107 else
108 skip_screens = smp->off - 1;
109 } else {
110 listset = O_ISSET(sp, O_LIST);
111 skip_screens = smp->off - 1;
113 if (O_ISSET(sp, O_NUMBER) && skip_screens == 0) {
114 cols_per_screen -= O_NUMBER_LENGTH;
115 (void)snprintf(nbuf,
116 sizeof(nbuf), O_NUMBER_FMT, smp->lno);
117 ADDSTR(nbuf);
122 * Get a copy of the line. Special case non-existent lines and the
123 * first line of an empty file. In both cases, the cursor position
124 * is 0.
126 if ((p = file_gline(sp, ep, smp->lno, &len)) == NULL || len == 0) {
127 /* Fill in the cursor. */
128 if (yp != NULL && smp->lno == sp->lno) {
129 *yp = smp - HMAP;
130 *xp = O_ISSET(sp, O_NUMBER) ? O_NUMBER_LENGTH : 0;
133 /* If the line is on the screen, quit. */
134 if (is_cached)
135 goto ret;
137 /* Set line cacheing information. */
138 smp->c_sboff = smp->c_eboff = 0;
139 smp->c_scoff = smp->c_eclen = 0;
141 if (file_lline(sp, ep, &lno))
142 goto err;
143 if (smp->lno > lno) {
144 ADDCH(smp->lno == 1 ?
145 listset && skip_screens == 0 ? '$' : ' ' : '~');
146 } else if (p == NULL) {
147 GETLINE_ERR(sp, smp->lno);
148 err: MOVEA(sp, oldy, oldx);
149 return (1);
150 } else if (listset && skip_screens == 0)
151 ADDCH('$');
152 clrtoeol();
153 MOVEA(sp, oldy, oldx);
154 return (0);
158 * If we wrote a line that's this or a previous one, we can do this
159 * much more quickly -- we cached the starting and ending positions
160 * of that line. The way it works is we keep information about the
161 * lines displayed in the SMAP. If we're painting the screen in
162 * the forward, this saves us from reformatting the physical line for
163 * every line on the screen. This wins big on binary files with 10K
164 * lines.
166 * Test for the first screen of the line, then the current screen line,
167 * then the line behind us, then do the hard work. Note, it doesn't
168 * do us any good to have a line in front of us -- it would be really
169 * hard to try and figure out tabs in the reverse direction, i.e. how
170 * many spaces a tab takes up in the reverse direction depends on
171 * what characters preceded it.
173 if (smp->off == 1) {
174 smp->c_sboff = offset_in_line = 0;
175 smp->c_scoff = offset_in_char = 0;
176 p = &p[offset_in_line];
177 } else if (is_cached) {
178 offset_in_line = smp->c_sboff;
179 offset_in_char = smp->c_scoff;
180 p = &p[offset_in_line];
181 } else if (smp != HMAP &&
182 SMAP_CACHE(tsmp = smp - 1) && tsmp->lno == smp->lno) {
183 if (tsmp->c_eclen != tsmp->c_ecsize) {
184 offset_in_line = tsmp->c_eboff;
185 offset_in_char = tsmp->c_eclen;
186 } else {
187 offset_in_line = tsmp->c_eboff + 1;
188 offset_in_char = 0;
191 /* Put starting info for this line in the cache. */
192 smp->c_sboff = offset_in_line;
193 smp->c_scoff = offset_in_char;
194 p = &p[offset_in_line];
195 } else {
196 offset_in_line = 0;
197 offset_in_char = 0;
199 /* This is the loop that skips through screens. */
200 if (skip_screens == 0) {
201 smp->c_sboff = offset_in_line;
202 smp->c_scoff = offset_in_char;
203 } else for (scno = 0; offset_in_line < len; ++offset_in_line) {
204 scno += chlen =
205 (ch = *(u_char *)p++) == '\t' && !listset ?
206 TAB_OFF(sp, scno) : cname[ch].len;
207 if (scno < cols_per_screen)
208 continue;
210 * Reset cols_per_screen to second and subsequent line
211 * length.
213 scno -= cols_per_screen;
214 cols_per_screen = sp->cols;
217 * If crossed the last skipped screen boundary, start
218 * displaying the characters.
220 if (--skip_screens)
221 continue;
223 /* Put starting info for this line in the cache. */
224 if (scno) {
225 smp->c_sboff = offset_in_line;
226 smp->c_scoff = offset_in_char = chlen - scno;
227 --p;
228 } else {
229 smp->c_sboff = ++offset_in_line;
230 smp->c_scoff = 0;
232 break;
237 * Set the number of characters to skip before reaching the cursor
238 * character. Offset by 1 and use 0 as a flag value. Svi_line is
239 * called repeatedly with a valid pointer to a cursor position.
240 * Don't fill anything in unless it's the right line and the right
241 * character, and the right part of the character...
243 if (yp == NULL ||
244 smp->lno != sp->lno || sp->cno < offset_in_line ||
245 offset_in_line + cols_per_screen < sp->cno) {
246 cno_cnt = 0;
247 /* If the line is on the screen, quit. */
248 if (is_cached)
249 goto ret;
250 } else
251 cno_cnt = (sp->cno - offset_in_line) + 1;
253 /* This is the loop that actually displays characters. */
254 for (is_partial = 0, scno = 0;
255 offset_in_line < len; ++offset_in_line, offset_in_char = 0) {
256 if ((ch = *(u_char *)p++) == '\t' && !listset) {
257 scno += chlen = TAB_OFF(sp, scno) - offset_in_char;
258 is_tab = 1;
259 } else {
260 scno += chlen = cname[ch].len - offset_in_char;
261 is_tab = 0;
265 * Only display up to the right-hand column. Set a flag if
266 * the entire character wasn't displayed for use in setting
267 * the cursor. If reached the end of the line, set the cache
268 * info for the screen. Don't worry about there not being
269 * characters to display on the next screen, its lno/off won't
270 * match up in that case.
272 if (scno >= cols_per_screen) {
273 smp->c_ecsize = chlen;
274 chlen -= scno - cols_per_screen;
275 smp->c_eclen = chlen;
276 smp->c_eboff = offset_in_line;
277 if (scno > cols_per_screen)
278 is_partial = 1;
280 /* Terminate the loop. */
281 offset_in_line = len;
285 * If the caller wants the cursor value, and this was the
286 * cursor character, set the value. There are two ways to
287 * put the cursor on a character -- if it's normal display
288 * mode, it goes on the last column of the character. If
289 * it's input mode, it goes on the first. In normal mode,
290 * set the cursor only if the entire character was displayed.
292 if (cno_cnt &&
293 --cno_cnt == 0 && (F_ISSET(sp, S_INPUT) || !is_partial)) {
294 *yp = smp - HMAP;
295 if (F_ISSET(sp, S_INPUT))
296 *xp = scno - chlen;
297 else
298 *xp = scno - 1;
299 if (O_ISSET(sp, O_NUMBER) &&
300 !is_infoline && smp->off == 1)
301 *xp += O_NUMBER_LENGTH;
303 /* If the line is on the screen, quit. */
304 if (is_cached)
305 goto ret;
308 /* If the line is on the screen, don't display anything. */
309 if (is_cached)
310 continue;
313 * Display the character. If it's a tab and tabs aren't some
314 * ridiculous length, do it fast. (We do tab expansion here
315 * because curses doesn't have a way to set the tab length.)
317 if (is_tab) {
318 if (chlen <= sizeof(TABSTR) - 1) {
319 ADDNSTR(TABSTR, chlen);
320 } else
321 while (chlen--)
322 ADDCH(TABCH);
323 } else
324 ADDNSTR(cname[ch].name + offset_in_char, chlen);
327 if (scno < cols_per_screen) {
328 /* If didn't paint the whole line, update the cache. */
329 smp->c_ecsize = smp->c_eclen = cname[ch].len;
330 smp->c_eboff = len - 1;
333 * If not the info/mode line, and O_LIST set, and at the
334 * end of the line, and the line ended on this screen,
335 * add a trailing $.
337 if (listset) {
338 ++scno;
339 ADDCH('$');
342 /* If still didn't paint the whole line, clear the rest. */
343 if (scno < cols_per_screen)
344 clrtoeol();
347 ret: if (reverse_video)
348 standend();
349 MOVEA(sp, oldy, oldx);
350 return (0);