display.c revision 1.3
1/* display.c -- How to display Info windows.
2   $Id: display.c,v 1.3 2002/06/10 13:51:03 espie 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
25extern int info_any_buffered_input_p (); /* Found in session.c. */
26
27static void free_display ();
28static DISPLAY_LINE **make_display ();
29
30/* An array of display lines which tell us what is currently visible on
31   the display.  */
32DISPLAY_LINE **the_display = (DISPLAY_LINE **)NULL;
33
34/* Non-zero means do no output. */
35int display_inhibited = 0;
36
37/* Initialize THE_DISPLAY to WIDTH and HEIGHT, with nothing in it. */
38void
39display_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. */
48void
49display_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. */
64int 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. */
68void
69display_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. */
96void
97display_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. */
386void
387display_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. */
457void
458display_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. */
532void
533display_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
544/* **************************************************************** */
545/*                                                                  */
546/*                   Functions Static to this File                  */
547/*                                                                  */
548/* **************************************************************** */
549
550/* Make a DISPLAY_LINE ** with width and height. */
551static DISPLAY_LINE **
552make_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. */
572static void
573free_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}
589