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