BSD 4_4_Lite2 release
[unix-history] / usr / src / contrib / nvi.1.43 / svi / svi_line.c
CommitLineData
ed554bc5
C
1/*-
2 * Copyright (c) 1993, 1994
3 * The Regents of the University of California. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 * must display the following acknowledgement:
15 * This product includes software developed by the University of
16 * California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#ifndef lint
fd88f5c5 35static char sccsid[] = "@(#)svi_line.c 9.1 (Berkeley) 11/9/94";
ed554bc5
C
36#endif /* not lint */
37
38#include <sys/types.h>
39#include <sys/queue.h>
40#include <sys/time.h>
41
42#include <bitstring.h>
43#include <limits.h>
44#include <signal.h>
45#include <stdio.h>
46#include <string.h>
47#include <termios.h>
48
49#include "compat.h"
50#include <curses.h>
51#include <db.h>
52#include <regex.h>
53
54#include "vi.h"
55#include "svi_screen.h"
56
57#if defined(DEBUG) && 0
58#define TABCH '-'
59#define TABSTR "--------------------"
60#else
61#define TABSTR " "
62#define TABCH ' '
63#endif
64
65/*
66 * svi_line --
67 * Update one line on the screen.
68 */
69int
fd88f5c5 70svi_line(sp, smp, yp, xp)
ed554bc5 71 SCR *sp;
ed554bc5
C
72 SMAP *smp;
73 size_t *xp, *yp;
74{
75 SMAP *tsmp;
76 size_t chlen, cols_per_screen, cno_cnt, len, scno, skip_screens;
77 size_t offset_in_char, offset_in_line;
78 size_t oldy, oldx;
fd88f5c5
C
79 int ch, is_cached, is_infoline, is_partial, is_tab;
80 int list_tab, list_dollar;
ed554bc5
C
81 char *p, nbuf[10];
82
83#if defined(DEBUG) && 0
84 TRACE(sp, "svi_line: row %u: line: %u off: %u\n",
85 smp - HMAP, smp->lno, smp->off);
86#endif
87
88 /*
89 * Assume that, if the cache entry for the line is filled in, the
90 * line is already on the screen, and all we need to do is return
91 * the cursor position. If the calling routine doesn't need the
92 * cursor position, we can just return.
93 */
94 is_cached = SMAP_CACHE(smp);
95 if (yp == NULL && is_cached)
96 return (0);
97
98 /*
99 * A nasty side effect of this routine is that it returns the screen
100 * position for the "current" character. Not pretty, but this is the
101 * only routine that really knows what's out there.
102 *
103 * Move to the line. This routine can be called by svi_sm_position(),
104 * which uses it to fill in the cache entry so it can figure out what
105 * the real contents of the screen are. Because of this, we have to
106 * return to whereever we started from.
107 */
108 getyx(stdscr, oldy, oldx);
109 MOVE(sp, smp - HMAP, 0);
110
111 /* Get a copy of the line. */
fd88f5c5 112 p = file_gline(sp, smp->lno, &len);
ed554bc5
C
113
114 /*
115 * Special case if we're printing the info/mode line. Skip printing
116 * the leading number, as well as other minor setup. If painting the
117 * line between two screens, it's always in reverse video. The only
118 * time this code paints the mode line is when the user is entering
119 * text for a ":" command, so we can put the code here instead of
120 * dealing with the empty line logic below. This is a kludge, but it's
121 * pretty much confined to this module.
122 *
123 * Set the number of screens to skip until a character is displayed.
124 * Left-right screens are special, because we don't bother building
125 * a buffer to be skipped over.
126 *
127 * Set the number of columns for this screen.
128 */
129 cols_per_screen = sp->cols;
fd88f5c5 130 list_tab = O_ISSET(sp, O_LIST);
ed554bc5 131 if (is_infoline = ISINFOLINE(sp, smp)) {
fd88f5c5 132 list_dollar = 0;
ed554bc5
C
133 if (O_ISSET(sp, O_LEFTRIGHT))
134 skip_screens = 0;
135 else
136 skip_screens = smp->off - 1;
137 } else {
fd88f5c5 138 list_dollar = list_tab;
ed554bc5
C
139 skip_screens = smp->off - 1;
140
141 /*
142 * If O_NUMBER is set and it's line number 1 or the line exists
143 * and this is the first screen of a folding line or any left-
144 * right line, display the line number.
145 */
146 if (O_ISSET(sp, O_NUMBER)) {
147 cols_per_screen -= O_NUMBER_LENGTH;
148 if ((smp->lno == 1 || p != NULL) && skip_screens == 0) {
149 (void)snprintf(nbuf,
150 sizeof(nbuf), O_NUMBER_FMT, smp->lno);
151 ADDSTR(nbuf);
152 }
153 }
154 }
155
156 /*
157 * Special case non-existent lines and the first line of an empty
158 * file. In both cases, the cursor position is 0, but corrected
159 * for the O_NUMBER field if it was displayed.
160 */
161 if (p == NULL || len == 0) {
162 /* Fill in the cursor. */
163 if (yp != NULL && smp->lno == sp->lno) {
164 *yp = smp - HMAP;
165 *xp = sp->cols - cols_per_screen;
166 }
167
168 /* If the line is on the screen, quit. */
169 if (is_cached)
170 goto ret;
171
172 /* Set line cacheing information. */
173 smp->c_sboff = smp->c_eboff = 0;
174 smp->c_scoff = smp->c_eclen = 0;
175
176 /* Lots of special cases for empty lines. */
177 if (skip_screens == 0)
178 if (p == NULL) {
179 if (smp->lno == 1) {
fd88f5c5 180 if (list_dollar) {
ed554bc5
C
181 ch = '$';
182 goto empty;
183 }
184 } else {
185 ch = '~';
186 goto empty;
187 }
188 } else
fd88f5c5 189 if (list_dollar) {
ed554bc5
C
190 ch = '$';
191empty: ADDCH(ch);
192 }
193
194 clrtoeol();
195 MOVEA(sp, oldy, oldx);
196 return (0);
197 }
198
199 /*
200 * If we wrote a line that's this or a previous one, we can do this
201 * much more quickly -- we cached the starting and ending positions
202 * of that line. The way it works is we keep information about the
203 * lines displayed in the SMAP. If we're painting the screen in
204 * the forward, this saves us from reformatting the physical line for
205 * every line on the screen. This wins big on binary files with 10K
206 * lines.
207 *
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.
214 */
215 if (smp->off == 1) {
216 smp->c_sboff = offset_in_line = 0;
217 smp->c_scoff = offset_in_char = 0;
218 p = &p[offset_in_line];
219 } else if (is_cached) {
220 offset_in_line = smp->c_sboff;
221 offset_in_char = smp->c_scoff;
222 p = &p[offset_in_line];
223 if (skip_screens != 0)
224 cols_per_screen = sp->cols;
225 } else if (smp != HMAP &&
226 SMAP_CACHE(tsmp = smp - 1) && tsmp->lno == smp->lno) {
227 if (tsmp->c_eclen != tsmp->c_ecsize) {
228 offset_in_line = tsmp->c_eboff;
229 offset_in_char = tsmp->c_eclen;
230 } else {
231 offset_in_line = tsmp->c_eboff + 1;
232 offset_in_char = 0;
233 }
234
235 /* Put starting info for this line in the cache. */
236 smp->c_sboff = offset_in_line;
237 smp->c_scoff = offset_in_char;
238 p = &p[offset_in_line];
239 if (skip_screens != 0)
240 cols_per_screen = sp->cols;
241 } else {
242 offset_in_line = 0;
243 offset_in_char = 0;
244
245 /* This is the loop that skips through screens. */
246 if (skip_screens == 0) {
247 smp->c_sboff = offset_in_line;
248 smp->c_scoff = offset_in_char;
249 } else for (scno = 0; offset_in_line < len; ++offset_in_line) {
250 scno += chlen =
fd88f5c5 251 (ch = *(u_char *)p++) == '\t' && !list_tab ?
ed554bc5
C
252 TAB_OFF(sp, scno) : KEY_LEN(sp, ch);
253 if (scno < cols_per_screen)
254 continue;
255 /*
256 * Reset cols_per_screen to second and subsequent line
257 * length.
258 */
259 scno -= cols_per_screen;
260 cols_per_screen = sp->cols;
261
262 /*
263 * If crossed the last skipped screen boundary, start
264 * displaying the characters.
265 */
266 if (--skip_screens)
267 continue;
268
269 /* Put starting info for this line in the cache. */
270 if (scno) {
271 smp->c_sboff = offset_in_line;
272 smp->c_scoff = offset_in_char = chlen - scno;
273 --p;
274 } else {
275 smp->c_sboff = ++offset_in_line;
276 smp->c_scoff = 0;
277 }
278 break;
279 }
280 }
281
282 /*
283 * Set the number of characters to skip before reaching the cursor
284 * character. Offset by 1 and use 0 as a flag value. Svi_line is
285 * called repeatedly with a valid pointer to a cursor position.
286 * Don't fill anything in unless it's the right line and the right
287 * character, and the right part of the character...
288 */
289 if (yp == NULL ||
290 smp->lno != sp->lno || sp->cno < offset_in_line ||
291 offset_in_line + cols_per_screen < sp->cno) {
292 cno_cnt = 0;
293 /* If the line is on the screen, quit. */
294 if (is_cached)
295 goto ret;
296 } else
297 cno_cnt = (sp->cno - offset_in_line) + 1;
298
299 /* This is the loop that actually displays characters. */
300 for (is_partial = 0, scno = 0;
301 offset_in_line < len; ++offset_in_line, offset_in_char = 0) {
fd88f5c5 302 if ((ch = *(u_char *)p++) == '\t' && !list_tab) {
ed554bc5
C
303 scno += chlen = TAB_OFF(sp, scno) - offset_in_char;
304 is_tab = 1;
305 } else {
306 scno += chlen = KEY_LEN(sp, ch) - offset_in_char;
307 is_tab = 0;
308 }
309
310 /*
311 * Only display up to the right-hand column. Set a flag if
312 * the entire character wasn't displayed for use in setting
313 * the cursor. If reached the end of the line, set the cache
314 * info for the screen. Don't worry about there not being
315 * characters to display on the next screen, its lno/off won't
316 * match up in that case.
317 */
318 if (scno >= cols_per_screen) {
319 smp->c_ecsize = chlen;
320 chlen -= scno - cols_per_screen;
321 smp->c_eclen = chlen;
322 smp->c_eboff = offset_in_line;
323 if (scno > cols_per_screen)
324 is_partial = 1;
325
326 /* Terminate the loop. */
327 offset_in_line = len;
328 }
329
330 /*
331 * If the caller wants the cursor value, and this was the
332 * cursor character, set the value. There are two ways to
333 * put the cursor on a character -- if it's normal display
334 * mode, it goes on the last column of the character. If
335 * it's input mode, it goes on the first. In normal mode,
336 * set the cursor only if the entire character was displayed.
337 */
338 if (cno_cnt &&
339 --cno_cnt == 0 && (F_ISSET(sp, S_INPUT) || !is_partial)) {
340 *yp = smp - HMAP;
341 if (F_ISSET(sp, S_INPUT))
342 *xp = scno - chlen;
343 else
344 *xp = scno - 1;
345 if (O_ISSET(sp, O_NUMBER) &&
346 !is_infoline && smp->off == 1)
347 *xp += O_NUMBER_LENGTH;
348
349 /* If the line is on the screen, quit. */
350 if (is_cached)
351 goto ret;
352 }
353
354 /* If the line is on the screen, don't display anything. */
355 if (is_cached)
356 continue;
357
358 /*
359 * Display the character. If it's a tab and tabs aren't some
360 * ridiculous length, do it fast. (We do tab expansion here
361 * because curses doesn't have a way to set the tab length.)
362 */
363 if (is_tab) {
364 if (chlen <= sizeof(TABSTR) - 1) {
365 ADDNSTR(TABSTR, chlen);
366 } else
367 while (chlen--)
368 ADDCH(TABCH);
369 } else
370 ADDNSTR(KEY_NAME(sp, ch) + offset_in_char, chlen);
371 }
372
373 if (scno < cols_per_screen) {
374 /* If didn't paint the whole line, update the cache. */
375 smp->c_ecsize = smp->c_eclen = KEY_LEN(sp, ch);
376 smp->c_eboff = len - 1;
377
378 /*
379 * If not the info/mode line, and O_LIST set, and at the
380 * end of the line, and the line ended on this screen,
381 * add a trailing $.
382 */
fd88f5c5 383 if (list_dollar) {
ed554bc5
C
384 ++scno;
385 ADDCH('$');
386 }
387
388 /* If still didn't paint the whole line, clear the rest. */
389 if (scno < cols_per_screen)
390 clrtoeol();
391 }
392
393ret: MOVEA(sp, oldy, oldx);
394 return (0);
395}
396
397/*
398 * svi_number --
399 * Repaint the numbers on all the lines.
400 */
401int
fd88f5c5 402svi_number(sp)
ed554bc5 403 SCR *sp;
ed554bc5
C
404{
405 SMAP *smp;
406 size_t oldy, oldx;
407 char *lp, nbuf[10];
408
409 /*
410 * Try and avoid getting the last line in the file, by getting the
411 * line after the last line in the screen -- if it exists, we know
412 * we have to to number all the lines in the screen. Get the one
413 * after the last instead of the last, so that the info line doesn't
414 * fool us.
415 *
416 * If that test fails, we have to check each line for existence.
417 *
418 * XXX
419 * The problem is that file_lline will lie, and tell us that the
420 * info line is the last line in the file.
421 */
fd88f5c5 422 lp = file_gline(sp, TMAP->lno + 1, NULL);
ed554bc5
C
423
424 getyx(stdscr, oldy, oldx);
425 for (smp = HMAP; smp <= TMAP; ++smp) {
426 if (smp->off != 1)
427 continue;
428 if (ISINFOLINE(sp, smp))
429 break;
430 if (smp->lno != 1 && lp == NULL &&
fd88f5c5 431 file_gline(sp, smp->lno, NULL) == NULL)
ed554bc5
C
432 break;
433 MOVE(sp, smp - HMAP, 0);
434 (void)snprintf(nbuf, sizeof(nbuf), O_NUMBER_FMT, smp->lno);
435 ADDSTR(nbuf);
436 }
437 MOVEA(sp, oldy, oldx);
438 return (0);
439}