source: trunk/third/nvi/vi/vs_line.c @ 14302

Revision 14302, 13.9 KB checked in by ghudson, 25 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r14301, which included commits to RCS files with non-trunk default branches.
Line 
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
13static const char sccsid[] = "@(#)vs_line.c     10.19 (Berkeley) 9/26/96";
14#endif /* not lint */
15
16#include <sys/types.h>
17#include <sys/queue.h>
18#include <sys/time.h>
19
20#include <bitstring.h>
21#include <limits.h>
22#include <stdio.h>
23#include <string.h>
24
25#include "../common/common.h"
26#include "vi.h"
27
28#ifdef VISIBLE_TAB_CHARS
29#define TABCH   '-'
30#else
31#define TABCH   ' '
32#endif
33
34/*
35 * vs_line --
36 *      Update one line on the screen.
37 *
38 * PUBLIC: int vs_line __P((SCR *, SMAP *, size_t *, size_t *));
39 */
40int
41vs_line(sp, smp, yp, xp)
42        SCR *sp;
43        SMAP *smp;
44        size_t *xp, *yp;
45{
46        CHAR_T *kp;
47        GS *gp;
48        SMAP *tsmp;
49        size_t chlen, cno_cnt, cols_per_screen, len, nlen;
50        size_t offset_in_char, offset_in_line, oldx, oldy;
51        size_t scno, skip_cols, skip_screens;
52        int ch, dne, is_cached, is_partial, is_tab;
53        int list_tab, list_dollar;
54        char *p, *cbp, *ecbp, cbuf[128];
55
56#if defined(DEBUG) && 0
57        TRACE(sp, "vs_line: row %u: line: %u off: %u\n",
58            smp - HMAP, smp->lno, smp->off);
59#endif
60        /*
61         * If ex modifies the screen after ex output is already on the screen,
62         * don't touch it -- we'll get scrolling wrong, at best.
63         */
64        if (!F_ISSET(sp, SC_TINPUT_INFO) && VIP(sp)->totalcount > 1)
65                return (0);
66        if (F_ISSET(sp, SC_SCR_EXWROTE) && smp - HMAP != LASTLINE(sp))
67                return (0);
68
69        /*
70         * Assume that, if the cache entry for the line is filled in, the
71         * line is already on the screen, and all we need to do is return
72         * the cursor position.  If the calling routine doesn't need the
73         * cursor position, we can just return.
74         */
75        is_cached = SMAP_CACHE(smp);
76        if (yp == NULL && is_cached)
77                return (0);
78
79        /*
80         * A nasty side effect of this routine is that it returns the screen
81         * position for the "current" character.  Not pretty, but this is the
82         * only routine that really knows what's out there.
83         *
84         * Move to the line.  This routine can be called by vs_sm_position(),
85         * which uses it to fill in the cache entry so it can figure out what
86         * the real contents of the screen are.  Because of this, we have to
87         * return to whereever we started from.
88         */
89        gp = sp->gp;
90        (void)gp->scr_cursor(sp, &oldy, &oldx);
91        (void)gp->scr_move(sp, smp - HMAP, 0);
92
93        /* Get the line. */
94        dne = db_get(sp, smp->lno, 0, &p, &len);
95
96        /*
97         * Special case if we're printing the info/mode line.  Skip printing
98         * the leading number, as well as other minor setup.  The only time
99         * this code paints the mode line is when the user is entering text
100         * for a ":" command, so we can put the code here instead of dealing
101         * with the empty line logic below.  This is a kludge, but it's pretty
102         * much confined to this module.
103         *
104         * Set the number of columns for this screen.
105         * Set the number of chars or screens to skip until a character is to
106         * be displayed.
107         */
108        cols_per_screen = sp->cols;
109        if (O_ISSET(sp, O_LEFTRIGHT)) {
110                skip_screens = 0;
111                skip_cols = smp->coff;
112        } else {
113                skip_screens = smp->soff - 1;
114                skip_cols = skip_screens * cols_per_screen;
115        }
116
117        list_tab = O_ISSET(sp, O_LIST);
118        if (F_ISSET(sp, SC_TINPUT_INFO))
119                list_dollar = 0;
120        else {
121                list_dollar = list_tab;
122
123                /*
124                 * If O_NUMBER is set, the line doesn't exist and it's line
125                 * number 1, i.e., an empty file, display the line number.
126                 *
127                 * If O_NUMBER is set, the line exists and the first character
128                 * on the screen is the first character in the line, display
129                 * the line number.
130                 *
131                 * !!!
132                 * If O_NUMBER set, decrement the number of columns in the
133                 * first screen.  DO NOT CHANGE THIS -- IT'S RIGHT!  The
134                 * rest of the code expects this to reflect the number of
135                 * columns in the first screen, regardless of the number of
136                 * columns we're going to skip.
137                 */
138                if (O_ISSET(sp, O_NUMBER)) {
139                        cols_per_screen -= O_NUMBER_LENGTH;
140                        if ((!dne || smp->lno == 1) && skip_cols == 0) {
141                                nlen = snprintf(cbuf,
142                                    sizeof(cbuf), O_NUMBER_FMT, smp->lno);
143                                (void)gp->scr_addstr(sp, cbuf, nlen);
144                        }
145                }
146        }
147
148        /*
149         * Special case non-existent lines and the first line of an empty
150         * file.  In both cases, the cursor position is 0, but corrected
151         * as necessary for the O_NUMBER field, if it was displayed.
152         */
153        if (dne || len == 0) {
154                /* Fill in the cursor. */
155                if (yp != NULL && smp->lno == sp->lno) {
156                        *yp = smp - HMAP;
157                        *xp = sp->cols - cols_per_screen;
158                }
159
160                /* If the line is on the screen, quit. */
161                if (is_cached)
162                        goto ret1;
163
164                /* Set line cache information. */
165                smp->c_sboff = smp->c_eboff = 0;
166                smp->c_scoff = smp->c_eclen = 0;
167
168                /*
169                 * Lots of special cases for empty lines, but they only apply
170                 * if we're displaying the first screen of the line.
171                 */
172                if (skip_cols == 0)
173                        if (dne) {
174                                if (smp->lno == 1) {
175                                        if (list_dollar) {
176                                                ch = '$';
177                                                goto empty;
178                                        }
179                                } else {
180                                        ch = '~';
181                                        goto empty;
182                                }
183                        } else
184                                if (list_dollar) {
185                                        ch = '$';
186empty:                                  (void)gp->scr_addstr(sp,
187                                            KEY_NAME(sp, ch), KEY_LEN(sp, ch));
188                                }
189
190                (void)gp->scr_clrtoeol(sp);
191                (void)gp->scr_move(sp, oldy, oldx);
192                return (0);
193        }
194
195        /*
196         * If we just wrote this or a previous line, we cached the starting
197         * and ending positions of that line.  The way it works is we keep
198         * information about the lines displayed in the SMAP.  If we're
199         * painting the screen in the forward direction, this saves us from
200         * reformatting the physical line for every line on the screen.  This
201         * wins big on binary files with 10K lines.
202         *
203         * Test for the first screen of the line, then the current screen line,
204         * then the line behind us, then do the hard work.  Note, it doesn't
205         * do us any good to have a line in front of us -- it would be really
206         * hard to try and figure out tabs in the reverse direction, i.e. how
207         * many spaces a tab takes up in the reverse direction depends on
208         * what characters preceded it.
209         *
210         * Test for the first screen of the line.
211         */
212        if (skip_cols == 0) {
213                smp->c_sboff = offset_in_line = 0;
214                smp->c_scoff = offset_in_char = 0;
215                p = &p[offset_in_line];
216                goto display;
217        }
218
219        /* Test to see if we've seen this exact line before. */
220        if (is_cached) {
221                offset_in_line = smp->c_sboff;
222                offset_in_char = smp->c_scoff;
223                p = &p[offset_in_line];
224
225                /* Set cols_per_screen to 2nd and later line length. */
226                if (O_ISSET(sp, O_LEFTRIGHT) || skip_cols > cols_per_screen)
227                        cols_per_screen = sp->cols;
228                goto display;
229        }
230
231        /* Test to see if we saw an earlier part of this line before. */
232        if (smp != HMAP &&
233            SMAP_CACHE(tsmp = smp - 1) && tsmp->lno == smp->lno) {
234                if (tsmp->c_eclen != tsmp->c_ecsize) {
235                        offset_in_line = tsmp->c_eboff;
236                        offset_in_char = tsmp->c_eclen;
237                } else {
238                        offset_in_line = tsmp->c_eboff + 1;
239                        offset_in_char = 0;
240                }
241
242                /* Put starting info for this line in the cache. */
243                smp->c_sboff = offset_in_line;
244                smp->c_scoff = offset_in_char;
245                p = &p[offset_in_line];
246
247                /* Set cols_per_screen to 2nd and later line length. */
248                if (O_ISSET(sp, O_LEFTRIGHT) || skip_cols > cols_per_screen)
249                        cols_per_screen = sp->cols;
250                goto display;
251        }
252
253        scno = 0;
254        offset_in_line = 0;
255        offset_in_char = 0;
256
257        /* Do it the hard way, for leftright scrolling screens. */
258        if (O_ISSET(sp, O_LEFTRIGHT)) {
259                for (; offset_in_line < len; ++offset_in_line) {
260                        chlen = (ch = *(u_char *)p++) == '\t' && !list_tab ?
261                            TAB_OFF(scno) : KEY_LEN(sp, ch);
262                        if ((scno += chlen) >= skip_cols)
263                                break;
264                }
265
266                /* Set cols_per_screen to 2nd and later line length. */
267                cols_per_screen = sp->cols;
268
269                /* Put starting info for this line in the cache. */
270                if (scno != skip_cols) {
271                        smp->c_sboff = offset_in_line;
272                        smp->c_scoff =
273                            offset_in_char = chlen - (scno - skip_cols);
274                        --p;
275                } else {
276                        smp->c_sboff = ++offset_in_line;
277                        smp->c_scoff = 0;
278                }
279        }
280
281        /* Do it the hard way, for historic line-folding screens. */
282        else {
283                for (; offset_in_line < len; ++offset_in_line) {
284                        chlen = (ch = *(u_char *)p++) == '\t' && !list_tab ?
285                            TAB_OFF(scno) : KEY_LEN(sp, ch);
286                        if ((scno += chlen) < cols_per_screen)
287                                continue;
288                        scno -= cols_per_screen;
289
290                        /* Set cols_per_screen to 2nd and later line length. */
291                        cols_per_screen = sp->cols;
292
293                        /*
294                         * If crossed the last skipped screen boundary, start
295                         * displaying the characters.
296                         */
297                        if (--skip_screens == 0)
298                                break;
299                }
300
301                /* Put starting info for this line in the cache. */
302                if (scno != 0) {
303                        smp->c_sboff = offset_in_line;
304                        smp->c_scoff = offset_in_char = chlen - scno;
305                        --p;
306                } else {
307                        smp->c_sboff = ++offset_in_line;
308                        smp->c_scoff = 0;
309                }
310        }
311
312display:
313        /*
314         * Set the number of characters to skip before reaching the cursor
315         * character.  Offset by 1 and use 0 as a flag value.  Vs_line is
316         * called repeatedly with a valid pointer to a cursor position.
317         * Don't fill anything in unless it's the right line and the right
318         * character, and the right part of the character...
319         */
320        if (yp == NULL ||
321            smp->lno != sp->lno || sp->cno < offset_in_line ||
322            offset_in_line + cols_per_screen < sp->cno) {
323                cno_cnt = 0;
324                /* If the line is on the screen, quit. */
325                if (is_cached)
326                        goto ret1;
327        } else
328                cno_cnt = (sp->cno - offset_in_line) + 1;
329
330        /* This is the loop that actually displays characters. */
331        ecbp = (cbp = cbuf) + sizeof(cbuf) - 1;
332        for (is_partial = 0, scno = 0;
333            offset_in_line < len; ++offset_in_line, offset_in_char = 0) {
334                if ((ch = *(u_char *)p++) == '\t' && !list_tab) {
335                        scno += chlen = TAB_OFF(scno) - offset_in_char;
336                        is_tab = 1;
337                } else {
338                        scno += chlen = KEY_LEN(sp, ch) - offset_in_char;
339                        is_tab = 0;
340                }
341
342                /*
343                 * Only display up to the right-hand column.  Set a flag if
344                 * the entire character wasn't displayed for use in setting
345                 * the cursor.  If reached the end of the line, set the cache
346                 * info for the screen.  Don't worry about there not being
347                 * characters to display on the next screen, its lno/off won't
348                 * match up in that case.
349                 */
350                if (scno >= cols_per_screen) {
351                        if (is_tab == 1) {
352                                chlen -= scno - cols_per_screen;
353                                smp->c_ecsize = smp->c_eclen = chlen;
354                                scno = cols_per_screen;
355                        } else {
356                                smp->c_ecsize = chlen;
357                                chlen -= scno - cols_per_screen;
358                                smp->c_eclen = chlen;
359
360                                if (scno > cols_per_screen)
361                                        is_partial = 1;
362                        }
363                        smp->c_eboff = offset_in_line;
364
365                        /* Terminate the loop. */
366                        offset_in_line = len;
367                }
368
369                /*
370                 * If the caller wants the cursor value, and this was the
371                 * cursor character, set the value.  There are two ways to
372                 * put the cursor on a character -- if it's normal display
373                 * mode, it goes on the last column of the character.  If
374                 * it's input mode, it goes on the first.  In normal mode,
375                 * set the cursor only if the entire character was displayed.
376                 */
377                if (cno_cnt &&
378                    --cno_cnt == 0 && (F_ISSET(sp, SC_TINPUT) || !is_partial)) {
379                        *yp = smp - HMAP;
380                        if (F_ISSET(sp, SC_TINPUT))
381                                *xp = scno - chlen;
382                        else
383                                *xp = scno - 1;
384                        if (O_ISSET(sp, O_NUMBER) &&
385                            !F_ISSET(sp, SC_TINPUT_INFO) && skip_cols == 0)
386                                *xp += O_NUMBER_LENGTH;
387
388                        /* If the line is on the screen, quit. */
389                        if (is_cached)
390                                goto ret1;
391                }
392
393                /* If the line is on the screen, don't display anything. */
394                if (is_cached)
395                        continue;
396
397#define FLUSH {                                                         \
398        *cbp = '\0';                                                    \
399        (void)gp->scr_addstr(sp, cbuf, cbp - cbuf);                     \
400        cbp = cbuf;                                                     \
401}
402                /*
403                 * Display the character.  We do tab expansion here because
404                 * the screen interface doesn't have any way to set the tab
405                 * length.  Note, it's theoretically possible for chlen to
406                 * be larger than cbuf, if the user set a impossibly large
407                 * tabstop.
408                 */
409                if (is_tab)
410                        while (chlen--) {
411                                if (cbp >= ecbp)
412                                        FLUSH;
413                                *cbp++ = TABCH;
414                        }
415                else {
416                        if (cbp + chlen >= ecbp)
417                                FLUSH;
418                        for (kp = KEY_NAME(sp, ch) + offset_in_char; chlen--;)
419                                *cbp++ = *kp++;
420                }
421        }
422
423        if (scno < cols_per_screen) {
424                /* If didn't paint the whole line, update the cache. */
425                smp->c_ecsize = smp->c_eclen = KEY_LEN(sp, ch);
426                smp->c_eboff = len - 1;
427
428                /*
429                 * If not the info/mode line, and O_LIST set, and at the
430                 * end of the line, and the line ended on this screen,
431                 * add a trailing $.
432                 */
433                if (list_dollar) {
434                        ++scno;
435
436                        chlen = KEY_LEN(sp, '$');
437                        if (cbp + chlen >= ecbp)
438                                FLUSH;
439                        for (kp = KEY_NAME(sp, '$'); chlen--;)
440                                *cbp++ = *kp++;
441                }
442
443                /* If still didn't paint the whole line, clear the rest. */
444                if (scno < cols_per_screen)
445                        (void)gp->scr_clrtoeol(sp);
446        }
447
448        /* Flush any buffered characters. */
449        if (cbp > cbuf)
450                FLUSH;
451
452ret1:   (void)gp->scr_move(sp, oldy, oldx);
453        return (0);
454}
455
456/*
457 * vs_number --
458 *      Repaint the numbers on all the lines.
459 *
460 * PUBLIC: int vs_number __P((SCR *));
461 */
462int
463vs_number(sp)
464        SCR *sp;
465{
466        GS *gp;
467        SMAP *smp;
468        VI_PRIVATE *vip;
469        size_t len, oldy, oldx;
470        int exist;
471        char nbuf[10];
472
473        gp = sp->gp;
474        vip = VIP(sp);
475
476        /* No reason to do anything if we're in input mode on the info line. */
477        if (F_ISSET(sp, SC_TINPUT_INFO))
478                return (0);
479
480        /*
481         * Try and avoid getting the last line in the file, by getting the
482         * line after the last line in the screen -- if it exists, we know
483         * we have to to number all the lines in the screen.  Get the one
484         * after the last instead of the last, so that the info line doesn't
485         * fool us.  (The problem is that file_lline will lie, and tell us
486         * that the info line is the last line in the file.) If that test
487         * fails, we have to check each line for existence.
488         */
489        exist = db_exist(sp, TMAP->lno + 1);
490
491        (void)gp->scr_cursor(sp, &oldy, &oldx);
492        for (smp = HMAP; smp <= TMAP; ++smp) {
493                /* Numbers are only displayed for the first screen line. */
494                if (O_ISSET(sp, O_LEFTRIGHT)) {
495                        if (smp->coff != 0)
496                                continue;
497                } else
498                        if (smp->soff != 1)
499                                continue;
500
501                /*
502                 * The first line of an empty file gets numbered, otherwise
503                 * number any existing line.
504                 */
505                if (smp->lno != 1 && !exist && !db_exist(sp, smp->lno))
506                        break;
507
508                (void)gp->scr_move(sp, smp - HMAP, 0);
509                len = snprintf(nbuf, sizeof(nbuf), O_NUMBER_FMT, smp->lno);
510                (void)gp->scr_addstr(sp, nbuf, len);
511        }
512        (void)gp->scr_move(sp, oldy, oldx);
513        return (0);
514}
Note: See TracBrowser for help on using the repository browser.