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