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