]> CyberLeo.Net >> Repos - FreeBSD/releng/9.2.git/blob - contrib/nvi/vi/vs_line.c
- Copy stable/9 to releng/9.2 as part of the 9.2-RELEASE cycle.
[FreeBSD/releng/9.2.git] / contrib / nvi / vi / vs_line.c
1 /*-
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.
6  *
7  * See the LICENSE file for redistribution information.
8  */
9
10 #include "config.h"
11
12 #ifndef lint
13 #if 0
14 static const char sccsid[] = "@(#)vs_line.c     10.19 (Berkeley) 9/26/96";
15 #endif
16 static const char rcsid[] =
17   "$FreeBSD$";
18 #endif /* not lint */
19
20 #include <sys/types.h>
21 #include <sys/queue.h>
22 #include <sys/time.h>
23
24 #include <bitstring.h>
25 #include <limits.h>
26 #include <stdio.h>
27 #include <string.h>
28
29 #include "../common/common.h"
30 #include "vi.h"
31
32 #ifdef VISIBLE_TAB_CHARS
33 #define TABCH   '-'
34 #else
35 #define TABCH   ' '
36 #endif
37
38 /*
39  * vs_line --
40  *      Update one line on the screen.
41  *
42  * PUBLIC: int vs_line __P((SCR *, SMAP *, size_t *, size_t *));
43  */
44 int
45 vs_line(sp, smp, yp, xp)
46         SCR *sp;
47         SMAP *smp;
48         size_t *xp, *yp;
49 {
50         CHAR_T *kp;
51         GS *gp;
52         SMAP *tsmp;
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];
59
60 #if defined(DEBUG) && 0
61         TRACE(sp, "vs_line: row %u: line: %u off: %u\n",
62             smp - HMAP, smp->lno, smp->off);
63 #endif
64         /*
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.
67          */
68         no_draw = 0;
69         if (!F_ISSET(sp, SC_TINPUT_INFO) && VIP(sp)->totalcount > 1)
70                 no_draw = 1;
71         if (F_ISSET(sp, SC_SCR_EXWROTE) && smp - HMAP != LASTLINE(sp))
72                 no_draw = 1;
73
74         /*
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.
79          */
80         is_cached = SMAP_CACHE(smp);
81         if (yp == NULL && (is_cached || no_draw))
82                 return (0);
83
84         /*
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.
88          *
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.
93          */
94         gp = sp->gp;
95         (void)gp->scr_cursor(sp, &oldy, &oldx);
96         (void)gp->scr_move(sp, smp - HMAP, 0);
97
98         /* Get the line. */
99         dne = db_get(sp, smp->lno, 0, &p, &len);
100
101         /*
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.
108          *
109          * Set the number of columns for this screen.
110          * Set the number of chars or screens to skip until a character is to
111          * be displayed.
112          */
113         cols_per_screen = sp->cols;
114         if (O_ISSET(sp, O_LEFTRIGHT)) {
115                 skip_screens = 0;
116                 skip_cols = smp->coff;
117         } else {
118                 skip_screens = smp->soff - 1;
119                 skip_cols = skip_screens * cols_per_screen;
120         }
121
122         list_tab = O_ISSET(sp, O_LIST);
123         if (F_ISSET(sp, SC_TINPUT_INFO))
124                 list_dollar = 0;
125         else {
126                 list_dollar = list_tab;
127
128                 /*
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.
131                  *
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
134                  * the line number.
135                  *
136                  * !!!
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.
142                  */
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);
149                         }
150                 }
151         }
152
153         /*
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.
157          */
158         if (dne || len == 0) {
159                 /* Fill in the cursor. */
160                 if (yp != NULL && smp->lno == sp->lno) {
161                         *yp = smp - HMAP;
162                         *xp = sp->cols - cols_per_screen;
163                 }
164
165                 /* If the line is on the screen, quit. */
166                 if (is_cached || no_draw)
167                         goto ret1;
168
169                 /* Set line cache information. */
170                 smp->c_sboff = smp->c_eboff = 0;
171                 smp->c_scoff = smp->c_eclen = 0;
172
173                 /*
174                  * Lots of special cases for empty lines, but they only apply
175                  * if we're displaying the first screen of the line.
176                  */
177                 if (skip_cols == 0)
178                         if (dne) {
179                                 if (smp->lno == 1) {
180                                         if (list_dollar) {
181                                                 ch = '$';
182                                                 goto empty;
183                                         }
184                                 } else {
185                                         ch = '~';
186                                         goto empty;
187                                 }
188                         } else
189                                 if (list_dollar) {
190                                         ch = '$';
191 empty:                                  (void)gp->scr_addstr(sp,
192                                             KEY_NAME(sp, ch), KEY_LEN(sp, ch));
193                                 }
194
195                 (void)gp->scr_clrtoeol(sp);
196                 (void)gp->scr_move(sp, oldy, oldx);
197                 return (0);
198         }
199
200         /*
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.
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          * Test for the first screen of the line.
216          */
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];
221                 goto display;
222         }
223
224         /* Test to see if we've seen this exact line before. */
225         if (is_cached) {
226                 offset_in_line = smp->c_sboff;
227                 offset_in_char = smp->c_scoff;
228                 p = &p[offset_in_line];
229
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;
233                 goto display;
234         }
235
236         /* Test to see if we saw an earlier part of this line before. */
237         if (smp != HMAP &&
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;
242                 } else {
243                         offset_in_line = tsmp->c_eboff + 1;
244                         offset_in_char = 0;
245                 }
246
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];
251
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;
255                 goto display;
256         }
257
258         scno = 0;
259         offset_in_line = 0;
260         offset_in_char = 0;
261
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)
268                                 break;
269                 }
270
271                 /* Set cols_per_screen to 2nd and later line length. */
272                 cols_per_screen = sp->cols;
273
274                 /* Put starting info for this line in the cache. */
275                 if (offset_in_line >= len) {
276                         smp->c_sboff = offset_in_line;
277                         smp->c_scoff = 255;
278                 } else if (scno != skip_cols) {
279                         smp->c_sboff = offset_in_line;
280                         smp->c_scoff =
281                             offset_in_char = chlen - (scno - skip_cols);
282                         --p;
283                 } else {
284                         smp->c_sboff = ++offset_in_line;
285                         smp->c_scoff = 0;
286                 }
287         }
288
289         /* Do it the hard way, for historic line-folding screens. */
290         else {
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)
295                                 continue;
296                         scno -= cols_per_screen;
297
298                         /* Set cols_per_screen to 2nd and later line length. */
299                         cols_per_screen = sp->cols;
300
301                         /*
302                          * If crossed the last skipped screen boundary, start
303                          * displaying the characters.
304                          */
305                         if (--skip_screens == 0)
306                                 break;
307                 }
308
309                 /* Put starting info for this line in the cache. */
310                 if (scno != 0) {
311                         smp->c_sboff = offset_in_line;
312                         smp->c_scoff = offset_in_char = chlen - scno;
313                         --p;
314                 } else {
315                         smp->c_sboff = ++offset_in_line;
316                         smp->c_scoff = 0;
317                 }
318         }
319
320 display:
321         /*
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...
327          */
328         if (yp == NULL ||
329             smp->lno != sp->lno || sp->cno < offset_in_line ||
330             offset_in_line + cols_per_screen < sp->cno) {
331                 cno_cnt = 0;
332                 /* If the line is on the screen, quit. */
333                 if (is_cached || no_draw)
334                         goto ret1;
335         } else
336                 cno_cnt = (sp->cno - offset_in_line) + 1;
337
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;
344                         is_tab = 1;
345                 } else {
346                         scno += chlen = KEY_LEN(sp, ch) - offset_in_char;
347                         is_tab = 0;
348                 }
349
350                 /*
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.
357                  */
358                 if (scno >= cols_per_screen) {
359                         if (is_tab == 1) {
360                                 chlen -= scno - cols_per_screen;
361                                 smp->c_ecsize = smp->c_eclen = chlen;
362                                 scno = cols_per_screen;
363                         } else {
364                                 smp->c_ecsize = chlen;
365                                 chlen -= scno - cols_per_screen;
366                                 smp->c_eclen = chlen;
367
368                                 if (scno > cols_per_screen)
369                                         is_partial = 1;
370                         }
371                         smp->c_eboff = offset_in_line;
372
373                         /* Terminate the loop. */
374                         offset_in_line = len;
375                 }
376
377                 /*
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.
384                  */
385                 if (cno_cnt &&
386                     --cno_cnt == 0 && (F_ISSET(sp, SC_TINPUT) || !is_partial)) {
387                         *yp = smp - HMAP;
388                         if (F_ISSET(sp, SC_TINPUT))
389                                 *xp = scno - chlen;
390                         else
391                                 *xp = scno - 1;
392                         if (O_ISSET(sp, O_NUMBER) &&
393                             !F_ISSET(sp, SC_TINPUT_INFO) && skip_cols == 0)
394                                 *xp += O_NUMBER_LENGTH;
395
396                         /* If the line is on the screen, quit. */
397                         if (is_cached || no_draw)
398                                 goto ret1;
399                 }
400
401                 /* If the line is on the screen, don't display anything. */
402                 if (is_cached || no_draw)
403                         continue;
404
405 #define FLUSH {                                                         \
406         *cbp = '\0';                                                    \
407         (void)gp->scr_addstr(sp, cbuf, cbp - cbuf);                     \
408         cbp = cbuf;                                                     \
409 }
410                 /*
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
415                  * tabstop.
416                  */
417                 if (is_tab)
418                         while (chlen--) {
419                                 if (cbp >= ecbp)
420                                         FLUSH;
421                                 *cbp++ = TABCH;
422                         }
423                 else {
424                         if (cbp + chlen >= ecbp)
425                                 FLUSH;
426                         for (kp = KEY_NAME(sp, ch) + offset_in_char; chlen--;)
427                                 *cbp++ = *kp++;
428                 }
429         }
430
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;
435
436                 /*
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,
439                  * add a trailing $.
440                  */
441                 if (list_dollar) {
442                         ++scno;
443
444                         chlen = KEY_LEN(sp, '$');
445                         if (cbp + chlen >= ecbp)
446                                 FLUSH;
447                         for (kp = KEY_NAME(sp, '$'); chlen--;)
448                                 *cbp++ = *kp++;
449                 }
450
451                 /* If still didn't paint the whole line, clear the rest. */
452                 if (scno < cols_per_screen)
453                         (void)gp->scr_clrtoeol(sp);
454         }
455
456         /* Flush any buffered characters. */
457         if (cbp > cbuf)
458                 FLUSH;
459
460 ret1:   (void)gp->scr_move(sp, oldy, oldx);
461         return (0);
462 }
463
464 /*
465  * vs_number --
466  *      Repaint the numbers on all the lines.
467  *
468  * PUBLIC: int vs_number __P((SCR *));
469  */
470 int
471 vs_number(sp)
472         SCR *sp;
473 {
474         GS *gp;
475         SMAP *smp;
476         VI_PRIVATE *vip;
477         size_t len, oldy, oldx;
478         int exist;
479         char nbuf[10];
480
481         gp = sp->gp;
482         vip = VIP(sp);
483
484         /* No reason to do anything if we're in input mode on the info line. */
485         if (F_ISSET(sp, SC_TINPUT_INFO))
486                 return (0);
487
488         /*
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.
496          */
497         exist = db_exist(sp, TMAP->lno + 1);
498
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)) {
503                         if (smp->coff != 0)
504                                 continue;
505                 } else
506                         if (smp->soff != 1)
507                                 continue;
508
509                 /*
510                  * The first line of an empty file gets numbered, otherwise
511                  * number any existing line.
512                  */
513                 if (smp->lno != 1 && !exist && !db_exist(sp, smp->lno))
514                         break;
515
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);
520         }
521         (void)gp->scr_move(sp, oldy, oldx);
522         return (0);
523 }