xref: /netbsd-src/external/gpl2/texinfo/dist/info/display.c (revision 29619d2afe564e54d657b83e5a3ae89584f83720)
1 /*	$NetBSD: display.c,v 1.1.1.1 2016/01/14 00:11:29 christos Exp $	*/
2 
3 /* display.c -- How to display Info windows.
4    Id: display.c,v 1.7 2004/04/11 17:56:45 karl Exp
5 
6    Copyright (C) 1993, 1997, 2003, 2004 Free Software Foundation, Inc.
7 
8    This program is free software; you can redistribute it and/or modify
9    it under the terms of the GNU General Public License as published by
10    the Free Software Foundation; either version 2, or (at your option)
11    any later version.
12 
13    This program is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16    GNU General Public License for more details.
17 
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 
22    Originally written by Brian Fox (bfox@ai.mit.edu). */
23 
24 #include "info.h"
25 #include "display.h"
26 
27 extern int info_any_buffered_input_p (void); /* Found in session.c. */
28 
29 static void free_display (DISPLAY_LINE **display);
30 static DISPLAY_LINE **make_display (int width, int height);
31 
32 void handle_tag (char *tag);
33 void handle_tag_start (char *tag);
34 void handle_tag_end (char *tag);
35 
36 /* An array of display lines which tell us what is currently visible on
37    the display.  */
38 DISPLAY_LINE **the_display = (DISPLAY_LINE **)NULL;
39 
40 /* Non-zero means do no output. */
41 int display_inhibited = 0;
42 
43 /* Initialize THE_DISPLAY to WIDTH and HEIGHT, with nothing in it. */
44 void
display_initialize_display(int width,int height)45 display_initialize_display (int width, int height)
46 {
47   free_display (the_display);
48   the_display = make_display (width, height);
49   display_clear_display (the_display);
50 }
51 
52 /* Clear all of the lines in DISPLAY making the screen blank. */
53 void
display_clear_display(DISPLAY_LINE ** display)54 display_clear_display (DISPLAY_LINE **display)
55 {
56   register int i;
57 
58   for (i = 0; display[i]; i++)
59     {
60       display[i]->text[0] = '\0';
61       display[i]->textlen = 0;
62       display[i]->inverse = 0;
63     }
64 }
65 
66 /* Non-zero if we didn't completely redisplay a window. */
67 int display_was_interrupted_p = 0;
68 
69 /* Update the windows pointed to by WINDOW in the_display.  This actually
70    writes the text on the screen. */
71 void
display_update_display(WINDOW * window)72 display_update_display (WINDOW *window)
73 {
74   register WINDOW *win;
75 
76   display_was_interrupted_p = 0;
77 
78   /* For every window in the list, check contents against the display. */
79   for (win = window; win; win = win->next)
80     {
81       /* Only re-display visible windows which need updating. */
82       if (((win->flags & W_WindowVisible) == 0) ||
83           ((win->flags & W_UpdateWindow) == 0) ||
84           (win->height == 0))
85         continue;
86 
87       display_update_one_window (win);
88       if (display_was_interrupted_p)
89         break;
90     }
91 
92   /* Always update the echo area. */
93   display_update_one_window (the_echo_area);
94 }
95 
96 void
handle_tag_start(char * tag)97 handle_tag_start (char *tag)
98 {
99   /* TODO really handle this tag.  */
100   return;
101 }
102 
103 void
handle_tag_end(char * tag)104 handle_tag_end (char *tag)
105 {
106   /* TODO really handle this tag.  */
107   return;
108 }
109 
110 void
handle_tag(char * tag)111 handle_tag (char *tag)
112 {
113     if (tag[0] == '/')
114       {
115 	tag++;
116 	handle_tag_end (tag);
117       }
118     else
119       handle_tag_start (tag);
120 }
121 
122 /* Display WIN on the_display.  Unlike display_update_display (), this
123    function only does one window. */
124 void
display_update_one_window(WINDOW * win)125 display_update_one_window (WINDOW *win)
126 {
127   register char *nodetext;      /* Current character to display. */
128   register char *last_node_char; /* Position of the last character in node. */
129   register int i;               /* General use index. */
130   char *printed_line;           /* Buffer for a printed line. */
131   int pl_index = 0;             /* Index into PRINTED_LINE. */
132   int line_index = 0;           /* Number of lines done so far. */
133   int pl_ignore = 0;		/* How many chars use zero width on screen. */
134   int allocated_win_width;
135   DISPLAY_LINE **display = the_display;
136 
137   /* If display is inhibited, that counts as an interrupted display. */
138   if (display_inhibited)
139     display_was_interrupted_p = 1;
140 
141   /* If the window has no height, or display is inhibited, quit now. */
142   if (!win->height || display_inhibited)
143     return;
144 
145   /* If the window's first row doesn't appear in the_screen, then it
146      cannot be displayed.  This can happen when the_echo_area is the
147      window to be displayed, and the screen has shrunk to less than one
148      line. */
149   if ((win->first_row < 0) || (win->first_row > the_screen->height))
150     return;
151 
152   /* Print each line in the window into our local buffer, and then
153      check the contents of that buffer against the display.  If they
154      differ, update the display. */
155   allocated_win_width = win->width + 1;
156   printed_line = (char *)xmalloc (allocated_win_width);
157 
158   if (!win->node || !win->line_starts)
159     goto done_with_node_display;
160 
161   nodetext = win->line_starts[win->pagetop];
162   last_node_char = win->node->contents + win->node->nodelen;
163 
164   for (; nodetext < last_node_char; nodetext++)
165     {
166       char *rep = NULL, *rep_carried_over, rep_temp[2];
167       int replen;
168 
169       if (isprint (*nodetext))
170         {
171           rep_temp[0] = *nodetext;
172           replen = 1;
173           rep_temp[1] = '\0';
174           rep = rep_temp;
175         }
176       else
177         {
178           if (*nodetext == '\r' || *nodetext == '\n')
179             {
180               replen = win->width - pl_index + pl_ignore;
181             }
182 	  else if (*nodetext == '\0'
183 		   && (nodetext + 2) < last_node_char
184 		   && *(nodetext + 1) == '\b'
185 		   && *(nodetext + 2) == '[')
186 	    {
187 	      /* Found new style tag/cookie \0\b[
188 		 Read until the closing tag \0\b] */
189 	      int element_len = 0;
190 	      char *element;
191 
192 	      /* Skip the escapes.  */
193 	      nodetext += 3;
194 
195 	      while (!(*nodetext == '\0'
196 		    && *(nodetext + 1) == '\b'
197 		    && *(nodetext + 2) == ']'))
198 		{
199 		  nodetext++;
200 		  element_len++;
201 		}
202 
203 	      element = (char *) malloc (element_len + 1);
204 	      strncpy (element, nodetext - element_len, element_len);
205 
206 	      /* Skip the escapes.  */
207 	      nodetext += 2;
208 	      pl_ignore += element_len + 5;
209 	      /* Append string terminator.  */
210 	      element[element_len] = '\0';
211 
212 	      handle_tag (element);
213 
214 	      /* Over and out */
215 	      free (element);
216 
217 	      continue;
218 	    }
219           else
220             {
221               rep = printed_representation (*nodetext, pl_index);
222               replen = strlen (rep);
223             }
224         }
225 
226       /* Support ANSI escape sequences under -R.  */
227       if (raw_escapes_p
228 	  && *nodetext == '\033'
229 	  && nodetext[1] == '['
230 	  && isdigit (nodetext[2]))
231 	{
232 	  if (nodetext[3] == 'm')
233 	    pl_ignore += 4;
234 	  else if (isdigit (nodetext[3]) && nodetext[4] == 'm')
235 	    pl_ignore += 5;
236 	}
237       while (pl_index + 2 >= allocated_win_width - 1)
238 	{
239 	  allocated_win_width *= 2;
240 	  printed_line = (char *)xrealloc (printed_line, allocated_win_width);
241 	}
242 
243       /* If this character can be printed without passing the width of
244          the line, then stuff it into the line. */
245       if (replen + pl_index < win->width + pl_ignore)
246         {
247           /* Optimize if possible. */
248           if (replen == 1)
249             {
250               printed_line[pl_index++] = *rep;
251             }
252           else
253             {
254               for (i = 0; i < replen; i++)
255                 printed_line[pl_index++] = rep[i];
256             }
257         }
258       else
259         {
260           DISPLAY_LINE *entry;
261 
262           /* If this character cannot be printed in this line, we have
263              found the end of this line as it would appear on the screen.
264              Carefully print the end of the line, and then compare. */
265           if (*nodetext == '\n' || *nodetext == '\r' || *nodetext == '\t')
266             {
267               printed_line[pl_index] = '\0';
268               rep_carried_over = (char *)NULL;
269             }
270           else
271             {
272               /* The printed representation of this character extends into
273                  the next line.  Remember the offset of the last character
274                  printed out of REP so that we can carry the character over
275                  to the next line. */
276               for (i = 0; pl_index < (win->width + pl_ignore - 1);)
277                 printed_line[pl_index++] = rep[i++];
278 
279               rep_carried_over = rep + i;
280 
281               /* If printing the last character in this window couldn't
282                  possibly cause the screen to scroll, place a backslash
283                  in the rightmost column. */
284               if (1 + line_index + win->first_row < the_screen->height)
285                 {
286                   if (win->flags & W_NoWrap)
287                     printed_line[pl_index++] = '$';
288                   else
289                     printed_line[pl_index++] = '\\';
290                 }
291               printed_line[pl_index] = '\0';
292             }
293 
294           /* We have the exact line as it should appear on the screen.
295              Check to see if this line matches the one already appearing
296              on the screen. */
297           entry = display[line_index + win->first_row];
298 
299           /* If the screen line is inversed, then we have to clear
300              the line from the screen first.  Why, I don't know.
301              (But don't do this if we have no visible entries, as can
302              happen if the window is shrunk very small.)  */
303           if ((entry && entry->inverse)
304 	      /* Need to erase the line if it has escape sequences.  */
305 	      || (raw_escapes_p && strchr (entry->text, '\033') != 0))
306             {
307               terminal_goto_xy (0, line_index + win->first_row);
308               terminal_clear_to_eol ();
309               entry->inverse = 0;
310               entry->text[0] = '\0';
311               entry->textlen = 0;
312             }
313 
314           /* Find the offset where these lines differ. */
315           for (i = 0; i < pl_index; i++)
316             if (printed_line[i] != entry->text[i])
317               break;
318 
319           /* If the lines are not the same length, or if they differed
320              at all, we must do some redrawing. */
321           if ((i != pl_index) || (pl_index != entry->textlen))
322             {
323               /* Move to the proper point on the terminal. */
324               terminal_goto_xy (i, line_index + win->first_row);
325 
326               /* If there is any text to print, print it. */
327               if (i != pl_index)
328                 terminal_put_text (printed_line + i);
329 
330               /* If the printed text didn't extend all the way to the edge
331                  of the window, and text was appearing between here and the
332                  edge of the window, clear from here to the end of the line. */
333               if ((pl_index < win->width + pl_ignore
334 		   && pl_index < entry->textlen)
335 		  || (entry->inverse))
336                 terminal_clear_to_eol ();
337 
338               fflush (stdout);
339 
340               /* Update the display text buffer. */
341 	      if (strlen (printed_line) > (unsigned int) screenwidth)
342 		/* printed_line[] can include more than screenwidth
343 		   characters if we are under -R and there are escape
344 		   sequences in it.  However, entry->text was
345 		   allocated (in display_initialize_display) for
346 		   screenwidth characters only.  */
347 		entry->text = xrealloc (entry->text, strlen (printed_line)+1);
348               strcpy (entry->text + i, printed_line + i);
349               entry->textlen = pl_index;
350 
351               /* Lines showing node text are not in inverse.  Only modelines
352                  have that distinction. */
353               entry->inverse = 0;
354             }
355 
356           /* We have done at least one line.  Increment our screen line
357              index, and check against the bottom of the window. */
358           if (++line_index == win->height)
359             break;
360 
361           /* A line has been displayed, and the screen reflects that state.
362              If there is typeahead pending, then let that typeahead be read
363              now, instead of continuing with the display. */
364           if (info_any_buffered_input_p ())
365             {
366               free (printed_line);
367               display_was_interrupted_p = 1;
368               return;
369             }
370 
371           /* Reset PL_INDEX to the start of the line. */
372           pl_index = 0;
373 	  pl_ignore = 0;	/* this is computed per line */
374 
375           /* If there are characters from REP left to print, stuff them
376              into the buffer now. */
377           if (rep_carried_over)
378             for (; rep[pl_index]; pl_index++)
379               printed_line[pl_index] = rep[pl_index];
380 
381           /* If this window has chosen not to wrap lines, skip to the end
382              of the physical line in the buffer, and start a new line here. */
383           if (pl_index && (win->flags & W_NoWrap))
384             {
385               char *begin;
386 
387               pl_index = 0;
388               printed_line[0] = '\0';
389 
390               begin = nodetext;
391 
392               while ((nodetext < last_node_char) && (*nodetext != '\n'))
393                 nodetext++;
394             }
395         }
396     }
397 
398  done_with_node_display:
399   /* We have reached the end of the node or the end of the window.  If it
400      is the end of the node, then clear the lines of the window from here
401      to the end of the window. */
402   for (; line_index < win->height; line_index++)
403     {
404       DISPLAY_LINE *entry = display[line_index + win->first_row];
405 
406       /* If this line has text on it then make it go away. */
407       if (entry && entry->textlen)
408         {
409           entry->textlen = 0;
410           entry->text[0] = '\0';
411 
412           terminal_goto_xy (0, line_index + win->first_row);
413           terminal_clear_to_eol ();
414         }
415     }
416 
417   /* Finally, if this window has a modeline it might need to be redisplayed.
418      Check the window's modeline against the one in the display, and update
419      if necessary. */
420   if ((win->flags & W_InhibitMode) == 0)
421     {
422       window_make_modeline (win);
423       line_index = win->first_row + win->height;
424 
425       /* This display line must both be in inverse, and have the same
426          contents. */
427       if ((!display[line_index]->inverse) ||
428           (strcmp (display[line_index]->text, win->modeline) != 0))
429         {
430           terminal_goto_xy (0, line_index);
431           terminal_begin_inverse ();
432           terminal_put_text (win->modeline);
433           terminal_end_inverse ();
434           strcpy (display[line_index]->text, win->modeline);
435           display[line_index]->inverse = 1;
436           display[line_index]->textlen = strlen (win->modeline);
437           fflush (stdout);
438         }
439     }
440 
441   /* Okay, this window doesn't need updating anymore. */
442   win->flags &= ~W_UpdateWindow;
443   free (printed_line);
444   fflush (stdout);
445 }
446 
447 /* Scroll the region of the_display starting at START, ending at END, and
448    moving the lines AMOUNT lines.  If AMOUNT is less than zero, the lines
449    are moved up in the screen, otherwise down.  Actually, it is possible
450    for no scrolling to take place in the case that the terminal doesn't
451    support it.  This doesn't matter to us. */
452 void
display_scroll_display(int start,int end,int amount)453 display_scroll_display (int start, int end, int amount)
454 {
455   register int i, last;
456   DISPLAY_LINE *temp;
457 
458   /* If this terminal cannot do scrolling, give up now. */
459   if (!terminal_can_scroll)
460     return;
461 
462   /* If there isn't anything displayed on the screen because it is too
463      small, quit now. */
464   if (!the_display[0])
465     return;
466 
467   /* If there is typeahead pending, then don't actually do any scrolling. */
468   if (info_any_buffered_input_p ())
469     return;
470 
471   /* Do it on the screen. */
472   terminal_scroll_terminal (start, end, amount);
473 
474   /* Now do it in the display buffer so our contents match the screen. */
475   if (amount > 0)
476     {
477       last = end + amount;
478 
479       /* Shift the lines to scroll right into place. */
480       for (i = 0; i < (end - start); i++)
481         {
482           temp = the_display[last - i];
483           the_display[last - i] = the_display[end - i];
484           the_display[end - i] = temp;
485         }
486 
487       /* The lines have been shifted down in the buffer.  Clear all of the
488          lines that were vacated. */
489       for (i = start; i != (start + amount); i++)
490         {
491           the_display[i]->text[0] = '\0';
492           the_display[i]->textlen = 0;
493           the_display[i]->inverse = 0;
494         }
495     }
496 
497   if (amount < 0)
498     {
499       last = start + amount;
500       for (i = 0; i < (end - start); i++)
501         {
502           temp = the_display[last + i];
503           the_display[last + i] = the_display[start + i];
504           the_display[start + i] = temp;
505         }
506 
507       /* The lines have been shifted up in the buffer.  Clear all of the
508          lines that are left over. */
509       for (i = end + amount; i != end; i++)
510         {
511           the_display[i]->text[0] = '\0';
512           the_display[i]->textlen = 0;
513           the_display[i]->inverse = 0;
514         }
515     }
516 }
517 
518 /* Try to scroll lines in WINDOW.  OLD_PAGETOP is the pagetop of WINDOW before
519    having had its line starts recalculated.  OLD_STARTS is the list of line
520    starts that used to appear in this window.  OLD_COUNT is the number of lines
521    that appear in the OLD_STARTS array. */
522 void
display_scroll_line_starts(WINDOW * window,int old_pagetop,char ** old_starts,int old_count)523 display_scroll_line_starts (WINDOW *window, int old_pagetop,
524     char **old_starts, int old_count)
525 {
526   register int i, old, new;     /* Indices into the line starts arrays. */
527   int last_new, last_old;       /* Index of the last visible line. */
528   int old_first, new_first;     /* Index of the first changed line. */
529   int unchanged_at_top = 0;
530   int already_scrolled = 0;
531 
532   /* Locate the first line which was displayed on the old window. */
533   old_first = old_pagetop;
534   new_first = window->pagetop;
535 
536   /* Find the last line currently visible in this window. */
537   last_new = window->pagetop + (window->height - 1);
538   if (last_new > window->line_count)
539     last_new = window->line_count - 1;
540 
541   /* Find the last line which used to be currently visible in this window. */
542   last_old = old_pagetop + (window->height - 1);
543   if (last_old > old_count)
544     last_old = old_count - 1;
545 
546   for (old = old_first, new = new_first;
547        old < last_old && new < last_new;
548        old++, new++)
549     if (old_starts[old] != window->line_starts[new])
550       break;
551     else
552       unchanged_at_top++;
553 
554   /* Loop through the old lines looking for a match in the new lines. */
555   for (old = old_first + unchanged_at_top; old < last_old; old++)
556     {
557       for (new = new_first; new < last_new; new++)
558         if (old_starts[old] == window->line_starts[new])
559           {
560             /* Find the extent of the matching lines. */
561             for (i = 0; (old + i) < last_old; i++)
562               if (old_starts[old + i] != window->line_starts[new + i])
563                 break;
564 
565             /* Scroll these lines if there are enough of them. */
566             {
567               int start, end, amount;
568 
569               start = (window->first_row
570                        + ((old + already_scrolled) - old_pagetop));
571               amount = new - (old + already_scrolled);
572               end = window->first_row + window->height;
573 
574               /* If we are shifting the block of lines down, then the last
575                  AMOUNT lines will become invisible.  Thus, don't bother
576                  scrolling them. */
577               if (amount > 0)
578                 end -= amount;
579 
580               if ((end - start) > 0)
581                 {
582                   display_scroll_display (start, end, amount);
583 
584                   /* Some lines have been scrolled.  Simulate the scrolling
585                      by offsetting the value of the old index. */
586                   old += i;
587                   already_scrolled += amount;
588                 }
589             }
590           }
591     }
592 }
593 
594 /* Move the screen cursor to directly over the current character in WINDOW. */
595 void
display_cursor_at_point(WINDOW * window)596 display_cursor_at_point (WINDOW *window)
597 {
598   int vpos, hpos;
599 
600   vpos = window_line_of_point (window) - window->pagetop + window->first_row;
601   hpos = window_get_cursor_column (window);
602   terminal_goto_xy (hpos, vpos);
603   fflush (stdout);
604 }
605 
606 /* **************************************************************** */
607 /*                                                                  */
608 /*                   Functions Static to this File                  */
609 /*                                                                  */
610 /* **************************************************************** */
611 
612 /* Make a DISPLAY_LINE ** with width and height. */
613 static DISPLAY_LINE **
make_display(int width,int height)614 make_display (int width, int height)
615 {
616   register int i;
617   DISPLAY_LINE **display;
618 
619   display = (DISPLAY_LINE **)xmalloc ((1 + height) * sizeof (DISPLAY_LINE *));
620 
621   for (i = 0; i < height; i++)
622     {
623       display[i] = (DISPLAY_LINE *)xmalloc (sizeof (DISPLAY_LINE));
624       display[i]->text = (char *)xmalloc (1 + width);
625       display[i]->textlen = 0;
626       display[i]->inverse = 0;
627     }
628   display[i] = (DISPLAY_LINE *)NULL;
629   return (display);
630 }
631 
632 /* Free the storage allocated to DISPLAY. */
633 static void
free_display(DISPLAY_LINE ** display)634 free_display (DISPLAY_LINE **display)
635 {
636   register int i;
637   register DISPLAY_LINE *display_line;
638 
639   if (!display)
640     return;
641 
642   for (i = 0; (display_line = display[i]); i++)
643     {
644       free (display_line->text);
645       free (display_line);
646     }
647   free (display);
648 }
649