main
display.c
1/* Copyright (c) 1985 Ceriel J.H. Jacobs */
2
3#include "in_all.h"
4#include "display.h"
5#include "assert.h"
6#include "machine.h"
7#include "term.h"
8#include "output.h"
9#include "options.h"
10#include "process.h"
11#include "getline.h"
12#include "main.h"
13#define getline yap_getline
14#include "utf.h"
15#undef getline
16#include "ansi.h"
17
18int pagesize;
19int maxpagesize;
20int scrollsize;
21struct scr_info scr_info;
22int status;
23
24static char *do_line(char *str, int reallydispl);
25static void flush_display_buffer(char *buf, char **p);
26static int rune_width(Rune r);
27static int decode_cell(const char *s, const char **next, int *width,
28 int *is_underscore);
29int wcwidth(wchar_t wc);
30
31/*
32 * Fill n lines of the screen, each with "str".
33 */
34
35static void
36fillscr(char *str, int n)
37{
38
39 while (n-- > 0) {
40 putline(str);
41 }
42}
43
44/*
45 * Skip "n" screenlines of line "p", and return what's left of it.
46 */
47
48static char *
49skiplines(char *p, int n)
50{
51
52 while (n-- > 0) {
53 p = do_line(p, 0);
54 scr_info.currentpos--;
55 }
56 return p;
57}
58
59/*
60 * Redraw screen.
61 * "n" = 1 if it is a real redraw, 0 if one page must be displayed.
62 * It is also called when yap receives a stop signal.
63 */
64
65void
66redraw(int n)
67{
68 struct scr_info *p = &scr_info;
69 int i;
70
71 i = pagesize;
72 if (n && p->currentpos) {
73 i = p->currentpos;
74 }
75 (void)display(p->firstline, p->nf, i, 1);
76}
77
78/*
79 * Compute return value for the routines "display" and "scrollf".
80 * This return value indicates wether we are at the end of file
81 * or at the start, or both.
82 * "s" contains that part of the last line that was not displayed.
83 */
84
85static int
86compretval(const char *s)
87{
88 int i;
89 struct scr_info *p = &scr_info;
90
91 i = 0;
92 if (!s || (!*s && !getline(p->lastline + 1, 1))) {
93 i = EOFILE;
94 }
95 if (p->firstline == 1 && !p->nf) {
96 i |= START;
97 }
98 status = i;
99 return i;
100}
101
102/*
103 * Display nlines, starting at line n, not displaying the first
104 * nd screenlines of n.
105 * If reallydispl = 0, the actual displaying is not performed,
106 * only the computing associated with it is done.
107 */
108
109int
110display(long n, int nd, int nlines, int reallydispl)
111{
112
113 struct scr_info *s = &scr_info;
114 char *p; /* pointer to line to be displayed */
115
116 if (startcomm) { /* No displaying on a command from the
117 * yap command line. In this case, displaying
118 * will be done after executing the command,
119 * by a redraw.
120 */
121 reallydispl = 0;
122 }
123 if (!n) {
124 n = 1L;
125 nd = 0;
126 }
127 if (reallydispl) { /* move cursor to starting point */
128 if (stupid) {
129 putline(currentfile);
130 putline(", line ");
131 prnum(n);
132 nlines--;
133 }
134 if (cflag) {
135 putline("\r\n");
136 } else {
137 home();
138 clrscreen();
139 }
140 }
141 /*
142 * Now, do computations and display
143 */
144 s->currentpos = 0;
145 s->nf = nd;
146 s->head = s->tail;
147 s->tail->cnt = 0;
148 s->tail->line = n;
149 p = skiplines(getline(n, 1), nd);
150 while (nlines && p) {
151 /*
152 * While there is room,
153 * and there is something left to display ...
154 */
155 (s->tail->cnt)++;
156 nlines--;
157 if (*(p = do_line(p, reallydispl)) == '\0') {
158 /*
159 * File-line finished, get next one ...
160 */
161 p = getline(++n, 1);
162 if (nlines && p) {
163 s->tail = s->tail->next;
164 s->tail->cnt = 0;
165 s->tail->line = n;
166 }
167 }
168 }
169 if (!stupid) {
170 s->currentpos += nlines;
171 if (reallydispl) {
172 fillscr("~\r\n", nlines);
173 fillscr("\r\n", maxpagesize - s->currentpos);
174 }
175 }
176 return compretval(p);
177}
178
179/*
180 * Scroll forwards n lines.
181 */
182
183int
184scrollf(int n, int reallydispl)
185{
186
187 struct scr_info *s = &scr_info;
188 char *p;
189 long ll;
190 int i;
191
192 /*
193 * First, find out how many screenlines of the last line were already
194 * on the screen, and possibly above it.
195 */
196
197 if (n <= 0 || (status & EOFILE))
198 return status;
199 if (startcomm)
200 reallydispl = 0;
201 /*
202 * Find out where to begin displaying
203 */
204 i = s->tail->cnt;
205 if ((ll = s->lastline) == s->firstline)
206 i += s->nf;
207 p = skiplines(getline(ll, 1), i);
208 /*
209 * Now, place the cursor at the first free line
210 */
211 if (reallydispl && !stupid) {
212 clrbline();
213 mgoto(s->currentpos);
214 }
215 /*
216 * Now display lines, keeping track of which lines are on the screen.
217 */
218 while (n-- > 0) { /* There are still rows to be displayed */
219 if (!*p) { /* End of line, get next one */
220 if (!(p = getline(++ll, 1))) {
221 /*
222 * No lines left. At end of file
223 */
224 break;
225 }
226 s->tail = s->tail->next;
227 s->tail->cnt = 0;
228 s->tail->line = ll;
229 }
230 if (s->currentpos >= maxpagesize) {
231 /*
232 * No room, delete first screen-line
233 */
234 s->currentpos--;
235 s->nf++;
236 if (--(s->head->cnt) == 0) {
237 /*
238 * The first file-line on the screen is wiped
239 * out completely; update administration
240 * accordingly.
241 */
242 s->nf = 0;
243 s->head = s->head->next;
244 assert(s->head->cnt > 0);
245 }
246 }
247 s->tail->cnt++;
248 p = do_line(p, reallydispl);
249 }
250 return compretval(p);
251}
252
253/*
254 * Scroll back n lines
255 */
256
257int
258scrollb(int n, int reallydispl)
259{
260
261 struct scr_info *s = &scr_info;
262 char *p; /* Holds string to be displayed */
263 int i;
264 int count;
265 long ln; /* a line number */
266 int nodispl;
267 int cannotscroll; /* stupid or no insert-line */
268
269 /*
270 * First, find out where to start
271 */
272 if ((count = n) <= 0 || (status & START))
273 return status;
274 if (startcomm)
275 reallydispl = 0;
276 cannotscroll = stupid || (!*AL && !*SR);
277 ln = s->firstline;
278 nodispl = s->nf;
279 while (count) { /* While scrolling back ... */
280 i = nodispl;
281 if (i) {
282 /*
283 * There were screen-lines of s->firstline that were not
284 * displayed.
285 * We can use them now, but only "count" of them.
286 */
287 if (i > count)
288 i = count;
289 s->currentpos += i;
290 nodispl -= i;
291 count -= i;
292 } else { /* Get previous line */
293 if (ln == 1)
294 break; /* isn't there ... */
295 p = getline(--ln, 1);
296 /*
297 * Make it the first line of the screen and compute
298 * how many screenlines it takes. These lines are not
299 * displayed, but nodispl is set to this count, so
300 * that it will be nonzero next time around
301 */
302 nodispl = 0;
303 do { /* Find out how many screenlines */
304 nodispl++;
305 p = skiplines(p, 1);
306 } while (*p);
307 }
308 }
309 n -= count;
310 if ((i = s->currentpos) > maxpagesize)
311 i = maxpagesize;
312 if (reallydispl && hardcopy)
313 i = n;
314 /*
315 * Now that we know where to start, we can use "display" to do the
316 * rest of the computing for us, and maybe even the displaying ...
317 */
318 i = display(ln,
319 nodispl,
320 i,
321 reallydispl && cannotscroll);
322 if (cannotscroll || !reallydispl) {
323 /*
324 * Yes, "display" did the displaying, or we did'nt have to
325 * display at all.
326 * I like it, but the user obviously does not.
327 * Let him buy another (smarter) terminal ...
328 */
329 return i;
330 }
331 /*
332 * Now, all we have to do is the displaying. And we are dealing with
333 * a smart terminal (it can insert lines or scroll back).
334 */
335 home();
336 /*
337 * Insert lines all at once
338 */
339 for (i = n; i; i--) {
340 if (DB && *CE) {
341 /*
342 * Grumble..., terminal retains lines below, so we have
343 * to clear the lines that we push off the screen
344 */
345 clrbline();
346 home();
347 }
348 if (*SR) {
349 scrollreverse();
350 } else {
351 insert_line(0);
352 }
353 }
354 p = skiplines(getline(ln = s->firstline, 1), s->nf);
355 for (i = 0; i < n; i++) {
356 p = do_line(p, 1);
357 s->currentpos--;
358 if (!*p) {
359 p = getline(++ln, 1);
360 }
361 }
362 return count;
363}
364
365/*
366 * Process a line.
367 * If reallydispl > 0 then display it.
368 */
369
370static char *
371do_line(char *str, int reallydispl)
372{
373 char buf[1024];
374 char *p = buf;
375 int pos = COLS;
376 int do_ul = 0, do_hl = 0;
377 int lastmode = 0, lasthlmode = 0;
378 int c2;
379 int cell_width;
380 int tab_width;
381 int is_underscore;
382 int cell_len;
383 int next_len;
384 int next_is_underscore;
385 unsigned char uc;
386 const char *cell_start;
387 const char *cursor;
388 const char *next;
389 enum ansi_kind ansi_kind;
390
391 while (*str && pos > 0) {
392 uc = (unsigned char)*str;
393 if (uc == 0x1b && ansi_escape(str, &next, &ansi_kind)) {
394 if (ansi_kind == ANSI_SGR) {
395 cell_len = next - str;
396 if (reallydispl &&
397 p + cell_len >= &buf[sizeof(buf) - 1]) {
398 flush_display_buffer(buf, &p);
399 }
400 if (reallydispl) {
401 (void)memcpy(p, str, (size_t)cell_len);
402 p += cell_len;
403 }
404 }
405 str = (char *)next;
406 continue;
407 }
408 if (uc < ' ' && (cell_len = match(str, &c2, sppat)) > 0) {
409 /*
410 * We found a string that matches, and thus must be
411 * echoed literally
412 */
413 if ((pos - c2) <= 0) {
414 /*
415 * It did not fit
416 */
417 break;
418 }
419 pos -= c2;
420 if (reallydispl) {
421 flush_display_buffer(buf, &p);
422 cell_start = str;
423 str += cell_len;
424 uc = (unsigned char)*str;
425 *str = '\0';
426 putline((char *)cell_start);
427 *str = (char)uc;
428 } else {
429 str += cell_len;
430 }
431 continue;
432 }
433
434 cell_start = str;
435 cell_len = decode_cell(str, &next, &cell_width, &is_underscore);
436 cursor = next;
437 do_hl = 0;
438 if (*cursor == '\b' && *(cursor + 1) != '\0') {
439 next_len = decode_cell(cursor + 1, &next, &tab_width,
440 &next_is_underscore);
441 if (!(is_underscore && *(cursor + 1 + next_len) != '\b')) {
442 while (*cursor == '\b' && *(cursor + 1) != '\0') {
443 cell_start = cursor + 1;
444 cell_len = decode_cell(cell_start, &next, &cell_width,
445 &is_underscore);
446 cursor = next;
447 do_hl = 1;
448 }
449 }
450 }
451 do_ul = 1;
452 /*
453 * Find underline sequences ...
454 */
455 if (is_underscore && *cursor == '\b' && *(cursor + 1) != '\0') {
456 cell_start = cursor + 1;
457 cell_len = decode_cell(cell_start, &next, &cell_width,
458 &is_underscore);
459 cursor = next;
460 } else {
461 if (*cursor == '\b' && *(cursor + 1) == '_') {
462 cursor += 2;
463 } else {
464 do_ul = 0;
465 }
466 }
467 if (cell_width == -1) {
468 tab_width = 8 - ((COLS - pos) & 0x07);
469 if (pos - tab_width < 0) {
470 break;
471 }
472 } else if (cell_width == -2) {
473 if (pos <= 1) {
474 break;
475 }
476 } else if (cell_width > pos) {
477 break;
478 }
479 if (reallydispl && do_hl != lasthlmode) {
480 flush_display_buffer(buf, &p);
481 if (do_hl) {
482 bold();
483 } else {
484 end_bold();
485 }
486 }
487 lasthlmode = do_hl;
488 if (reallydispl && do_ul != lastmode) {
489 flush_display_buffer(buf, &p);
490 if (do_ul) {
491 underline();
492 } else {
493 end_underline();
494 }
495 }
496 lastmode = do_ul;
497 if (cell_width == -1) {
498 pos -= tab_width;
499 if (reallydispl) {
500 if (expandtabs) {
501 while (tab_width-- > 0) {
502 *p++ = ' ';
503 }
504 } else {
505 flush_display_buffer(buf, &p);
506 givetab();
507 }
508 }
509 str = (char *)cursor;
510 continue;
511 }
512 if (reallydispl && p + cell_len >= &buf[sizeof(buf) - 1]) {
513 flush_display_buffer(buf, &p);
514 }
515 if (cell_width == -2) {
516 if (reallydispl) {
517 if (p + 2 >= &buf[sizeof(buf) - 1]) {
518 flush_display_buffer(buf, &p);
519 }
520 *p++ = '^';
521 *p++ = *cell_start ^ 0x40;
522 }
523 pos -= 2;
524 str = (char *)cursor;
525 continue;
526 }
527 if (reallydispl) {
528 (void)memcpy(p, cell_start, (size_t)cell_len);
529 p += cell_len;
530 }
531 if (cell_width >= 0) {
532 pos -= cell_width;
533 if (reallydispl && do_ul && *UC && cell_width == 1 && pos > 0) {
534 /*
535 * Underlining apparently is done one
536 * character at a time.
537 */
538 flush_display_buffer(buf, &p);
539 backspace();
540 underchar();
541 }
542 str = (char *)cursor;
543 continue;
544 }
545 str = (char *)cursor;
546 }
547 if (reallydispl) {
548 flush_display_buffer(buf, &p);
549 if (pos > 0 || (pos <= 0 && (!AM || XN))) {
550 putline("\r\n");
551 }
552 /*
553 * The next should be here! I.e. it may not be before printing
554 * the newline. This has to do with XN. We don't know exactly
555 * WHEN the terminal will stop ignoring the newline.
556 * I have for example a terminal (Ampex a230) that will
557 * continue to ignore the newline after a clear to end of line
558 * sequence, but not after an end_underline sequence.
559 */
560 if (lastmode) {
561 end_underline();
562 }
563 if (lasthlmode) {
564 end_bold();
565 }
566 }
567 scr_info.currentpos++;
568 return str;
569}
570
571static void
572flush_display_buffer(char *buf, char **p)
573{
574 if (*p == buf) {
575 return;
576 }
577 **p = '\0';
578 putline(buf);
579 *p = buf;
580}
581
582static int
583rune_width(Rune r)
584{
585 int width;
586
587 width = wcwidth((wchar_t)r);
588 if (width < 0) {
589 return 1;
590 }
591 return width;
592}
593
594static int
595decode_cell(const char *s, const char **next, int *width, int *is_underscore)
596{
597 const char *p;
598 Rune r;
599 Rune mark;
600 int len;
601 int mark_len;
602 int mark_width;
603
604 len = chartorune(&r, s);
605 if (len <= 0) {
606 len = 1;
607 r = Runeerror;
608 }
609 *is_underscore = (len == 1 && *s == '_');
610 if (*s == '\t') {
611 *width = -1;
612 *next = s + 1;
613 return 1;
614 }
615 if ((unsigned char)*s < ' ' || (unsigned char)*s == 0x7f) {
616 *width = -2;
617 *next = s + 1;
618 return 1;
619 }
620 *width = rune_width(r);
621 p = s + len;
622 while (*p) {
623 if (*p == '\b' || *p == '\t' || (unsigned char)*p < ' ' ||
624 (unsigned char)*p == 0x7f) {
625 break;
626 }
627 mark_len = chartorune(&mark, p);
628 if (mark_len <= 0) {
629 break;
630 }
631 mark_width = rune_width(mark);
632 if (mark_width != 0) {
633 break;
634 }
635 p += mark_len;
636 len += mark_len;
637 }
638 *next = p;
639 return len;
640}
641
642/* ARGSUSED */
643int
644setmark(long cnt)
645{ /* Set a mark on the current page */
646 struct scr_info *p = &scr_info;
647 (void)cnt;
648
649 p->savfirst = p->firstline;
650 p->savnf = p->nf;
651 return 0;
652}
653
654/* ARGSUSED */
655int
656tomark(long cnt)
657{ /* Go to the mark */
658 struct scr_info *p = &scr_info;
659 (void)cnt;
660
661 (void)display(p->savfirst, p->savnf, pagesize, 1);
662 return 0;
663}
664
665/* ARGSUSED */
666int
667exgmark(long cnt)
668{ /* Exchange mark and current page */
669 struct scr_info *p = &scr_info;
670 long svfirst;
671 int svnf;
672 (void)cnt;
673
674 svfirst = p->firstline;
675 svnf = p->nf;
676 tomark(0L);
677 p->savfirst = svfirst;
678 p->savnf = svnf;
679 return 0;
680}
681
682void
683d_clean()
684{ /* Clean up */
685 struct scr_info *p = &scr_info;
686
687 p->savnf = 0;
688 p->savfirst = 0;
689 p->head = p->tail;
690 p->head->line = 0;
691 p->currentpos = 0;
692}