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