master xplshn/aruu / cmd / extra / yap / display.c
  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}