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}