(root)/
texinfo-7.1/
info/
window.c
       1  /* window.c -- windows in Info.
       2  
       3     Copyright 1993-2023 Free Software Foundation, Inc.
       4  
       5     This program is free software: you can redistribute it and/or modify
       6     it under the terms of the GNU General Public License as published by
       7     the Free Software Foundation, either version 3 of the License, or
       8     (at your option) any later version.
       9  
      10     This program is distributed in the hope that it will be useful,
      11     but WITHOUT ANY WARRANTY; without even the implied warranty of
      12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13     GNU General Public License for more details.
      14  
      15     You should have received a copy of the GNU General Public License
      16     along with this program.  If not, see <http://www.gnu.org/licenses/>.
      17  
      18     Originally written by Brian Fox.  */
      19  
      20  #include "info.h"
      21  #include "session.h"
      22  #include "display.h"
      23  #include "scan.h"
      24  #include "util.h"
      25  #include "doc.h"
      26  #include "tag.h"
      27  #include "variables.h"
      28  
      29  /* The window which describes the screen. */
      30  WINDOW *the_screen = NULL;
      31  
      32  /* The window which describes the echo area. */
      33  WINDOW *the_echo_area = NULL;
      34  
      35  /* The list of windows in Info. */
      36  WINDOW *windows = NULL;
      37  
      38  /* Pointer to the active window in WINDOW_LIST. */
      39  WINDOW *active_window = NULL;
      40  
      41  /* The size of the echo area in Info.  It never changes, irregardless of the
      42     size of the screen. */
      43  #define ECHO_AREA_HEIGHT 1
      44  
      45  /* Show malformed multibyte sequences */
      46  int show_malformed_multibyte_p = 0;
      47  
      48  /* Initalize the window system by creating THE_SCREEN and THE_ECHO_AREA.
      49     Create the first window ever.
      50     You pass the dimensions of the total screen size. */
      51  void
      52  window_initialize_windows (int width, int height)
      53  {
      54    the_screen = xzalloc (sizeof (WINDOW));
      55    the_echo_area = xzalloc (sizeof (WINDOW));
      56    windows = xzalloc (sizeof (WINDOW));
      57    active_window = windows;
      58  
      59    /* The active and echo_area windows are visible.
      60       The echo_area is permanent.
      61       The screen is permanent. */
      62    active_window->flags = W_WindowVisible;
      63    the_echo_area->flags = W_WindowIsPerm | W_InhibitMode | W_WindowVisible;
      64    the_screen->flags    = W_WindowIsPerm;
      65  
      66    /* The height of the echo area never changes.  It is statically set right
      67       here, and it must be at least 1 line for display.  The size of the
      68       initial window cannot be the same size as the screen, since the screen
      69       includes the echo area.  So, we make the height of the initial window
      70       equal to the screen's displayable region minus the height of the echo
      71       area. */
      72    the_echo_area->height = ECHO_AREA_HEIGHT;
      73    active_window->height = the_screen->height - 1 - the_echo_area->height;
      74    window_new_screen_size (width, height);
      75  }
      76  
      77  /* Given that the size of the screen has changed to WIDTH and HEIGHT
      78     from whatever it was before (found in the_screen->height, ->width),
      79     change the size (and possibly location) of each window in the screen.
      80     If a window would become too small, call the function DELETER on it,
      81     after deleting the window from our chain of windows.  If DELETER is NULL,
      82     nothing extra is done.  The last window can never be deleted, but it can
      83     become invisible. */
      84  void
      85  window_new_screen_size (int width, int height)
      86  {
      87    register WINDOW *win, *first_win;
      88    int delta_height, delta_each, delta_leftover;
      89    int numwins;
      90  
      91    /* If no change, do nothing. */
      92    if (width == the_screen->width && height == the_screen->height)
      93      return;
      94  
      95    /* The screen has changed height and width. */
      96    delta_height = height - the_screen->height;
      97    the_screen->height = height;
      98    the_screen->width = width;
      99  
     100    /* Set the start of the echo area. */
     101    the_echo_area->first_row = height - the_echo_area->height;
     102    the_echo_area->width = width;
     103  
     104    /* Count number of windows. */
     105    numwins = 0;
     106    for (win = windows; win; win = win->next)
     107      numwins++;
     108  
     109    if (numwins == 0)
     110      return; /* There is nothing to do. */
     111  
     112    /* Divide the change in height among the available windows. */
     113    delta_each = delta_height / numwins;
     114    delta_leftover = delta_height - (delta_each * numwins);
     115  
     116    /* See if some windows will need to be deleted.  This is the case if
     117       the screen is getting smaller, and the available space divided by
     118       the number of windows is less than WINDOW_MIN_SIZE.  In that case,
     119       delete some windows and try again until there is either enough
     120       space to divy up among the windows, or until there is only one
     121       window left. */
     122    while (height - 1 <= WINDOW_MIN_SIZE * numwins)
     123      {
     124        /* If only one window left, give up. */
     125        if (!windows->next)
     126          {
     127            /* Keep track of the height so that when the screen gets bigger
     128               again, it can be resized properly.  The -2 is for the window
     129               information bar and the echo area. */
     130            windows->height = height - 2;
     131            windows->width = width;
     132            free (windows->modeline);
     133            windows->modeline = xmalloc (1 + width);
     134            return;
     135          }
     136  
     137        /* If we have some temporary windows, delete one of them. */
     138        for (win = windows; win; win = win->next)
     139          if (win->flags & W_TempWindow)
     140            break;
     141  
     142        /* Otherwise, delete the first window, and try again. */
     143        if (!win)
     144          win = windows;
     145  
     146        forget_window_and_nodes (win);
     147        window_delete_window (win);
     148        numwins--;
     149      }
     150  
     151    /* Alternate which window we start resizing at, to resize all
     152       windows evenly. */
     153      {
     154        int first_win_num = the_screen->height % numwins;
     155        int i;
     156        first_win = windows;
     157        for (i = 0; i < first_win_num; i++)
     158          first_win = first_win->next;
     159      }
     160  
     161    /* Change the height of each window in the chain by delta_each.  Change
     162       the height of the last window in the chain by delta_each and by the
     163       leftover amount of change.  Change the width of each window to be
     164       WIDTH. */
     165    win = first_win;
     166    do
     167      {
     168        if ((win->width != width) && ((win->flags & W_InhibitMode) == 0))
     169          {
     170            win->width = width;
     171            free (win->modeline);
     172            win->modeline = xmalloc (1 + width);
     173          }
     174  
     175        /* Don't resize a window to be smaller than one line. */
     176        if (win->height + delta_each >= 1)
     177          win->height += delta_each;
     178        else
     179          delta_leftover += delta_each;
     180  
     181        /* Try to use up the extra space. */
     182        if (delta_leftover != 0 && win->height + delta_leftover >= 1)
     183          {
     184            win->height += delta_leftover;
     185            delta_leftover = 0;
     186          }
     187        /* Go to next window, wrapping round to the start. */
     188        win = win->next;
     189        if (!win)
     190          win = windows;
     191      }
     192    while (win != first_win);
     193  
     194    for (win = windows; win; win = win->next)
     195      {
     196        /* If this is not the first window in the chain, set the
     197           first row of it by adding one to the location of the
     198           previous window's modeline. */
     199        if (win->prev)
     200          win->first_row = (win->prev->first_row + win->prev->height) + 1;
     201  
     202        if (win->node)
     203          {
     204            free (win->line_starts);
     205            free (win->log_line_no);
     206            calculate_line_starts (win);
     207          }
     208  
     209        win->flags |= W_UpdateWindow;
     210      }
     211  
     212    /* If the screen got smaller, check over the windows just shrunk to
     213       keep them within bounds.  Some of the windows may have gotten smaller
     214       than WINDOW_MIN_HEIGHT in which case some of the other windows are
     215       larger than the available display space in the screen.  Because of our
     216       intial test above, we know that there is enough space for all of the
     217       windows. */
     218    if ((delta_each < 0) && ((windows->height != 0) && windows->next))
     219      {
     220        int avail;
     221  
     222        avail = the_screen->height - (numwins + the_echo_area->height);
     223        win = windows;
     224  
     225        while (win)
     226          {
     227            if ((win->height < WINDOW_MIN_HEIGHT) ||
     228                (win->height > avail))
     229              {
     230                WINDOW *lastwin = NULL;
     231  
     232                /* Split the space among the available windows. */
     233                delta_each = avail / numwins;
     234                delta_leftover = avail - (delta_each * numwins);
     235  
     236                for (win = windows; win; win = win->next)
     237                  {
     238                    lastwin = win;
     239                    if (win->prev)
     240                      win->first_row =
     241                        (win->prev->first_row + win->prev->height) + 1;
     242                    win->height = delta_each;
     243                  }
     244  
     245                /* Give the leftover space (if any) to the last window. */
     246                lastwin->height += delta_leftover;
     247                break;
     248              }
     249            else
     250              win = win->next;
     251          }
     252      }
     253  
     254    /* Make sure point is in displayed part of active window. */
     255    window_adjust_pagetop (active_window);
     256  
     257    /* One more loop.  If any heights or widths have become negative,
     258       set them to zero.  This can apparently happen with resizing down to
     259       very small sizes.  Sadly, it is not apparent to me where in the
     260       above calculations it goes wrong.  */
     261    for (win = windows; win; win = win->next)
     262      {
     263        if (win->height < 0)
     264          win->height = 0;
     265  
     266        if (win->width < 0)
     267          win->width = 0;
     268      }
     269  }
     270  
     271  /* Make a new window by splitting an existing one. If the window could
     272     not be made return a null pointer.  The active window is not changed .*/
     273  WINDOW *
     274  window_make_window (void)
     275  {
     276    WINDOW *window;
     277  
     278    /* If there isn't enough room to make another window, return now. */
     279    if ((active_window->height / 2) < WINDOW_MIN_SIZE)
     280      return NULL;
     281  
     282    /* Make and initialize the new window.
     283       The fudging about with -1 and +1 is because the following window in the
     284       chain cannot start at window->height, since that is where the modeline
     285       for the previous window is displayed.  The inverse adjustment is made
     286       in window_delete_window (). */
     287    window = xzalloc (sizeof (WINDOW));
     288    window->width = the_screen->width;
     289    window->height = (active_window->height / 2) - 1;
     290    window->first_row = active_window->first_row +
     291      (active_window->height - window->height);
     292    window->goal_column = -1;
     293    memset (&window->line_map, 0, sizeof (window->line_map));
     294    window->modeline = xmalloc (1 + window->width);
     295    window->line_starts = NULL;
     296    window->flags = W_UpdateWindow | W_WindowVisible;
     297  
     298    /* Adjust the height of the old active window. */
     299    active_window->height -= (window->height + 1);
     300    active_window->flags |= W_UpdateWindow;
     301  
     302    window_make_modeline (active_window);
     303  
     304    /* This window is just after the active one.  Which window is active is
     305       not changed. */
     306    window->prev = active_window;
     307    window->next = active_window->next;
     308    active_window->next = window;
     309    if (window->next)
     310      window->next->prev = window;
     311    return window;
     312  }
     313  
     314  /* These useful macros make it possible to read the code in
     315     window_change_window_height (). */
     316  #define grow_me_shrinking_next(me, next, diff) \
     317    do { \
     318      me->height += diff; \
     319      next->height -= diff; \
     320      next->first_row += diff; \
     321    } while (0)
     322  
     323  #define grow_me_shrinking_prev(me, prev, diff) \
     324    do { \
     325      me->height += diff; \
     326      prev->height -= diff; \
     327      me->first_row -=diff; \
     328    } while (0)
     329  
     330  #define shrink_me_growing_next(me, next, diff) \
     331    do { \
     332      me->height -= diff; \
     333      next->height += diff; \
     334      next->first_row -= diff; \
     335    } while (0)
     336  
     337  #define shrink_me_growing_prev(me, prev, diff) \
     338    do { \
     339      me->height -= diff; \
     340      prev->height += diff; \
     341      me->first_row += diff; \
     342    } while (0)
     343  
     344  /* Change the height of WINDOW by AMOUNT.  This also automagically adjusts
     345     the previous and next windows in the chain.  If there is only one user
     346     window, then no change takes place. */
     347  void
     348  window_change_window_height (WINDOW *window, int amount)
     349  {
     350    register WINDOW *win, *prev, *next;
     351  
     352    /* If there is only one window, or if the amount of change is zero,
     353       return immediately. */
     354    if (!windows->next || amount == 0)
     355      return;
     356  
     357    /* Find this window in our chain. */
     358    for (win = windows; win; win = win->next)
     359      if (win == window)
     360        break;
     361  
     362    /* If the window is isolated (i.e., doesn't appear in our window list,
     363       then quit now. */
     364    if (!win)
     365      return;
     366  
     367    /* Change the height of this window by AMOUNT, if that is possible.
     368       It can be impossible if there isn't enough available room on the
     369       screen, or if the resultant window would be too small. */
     370  
     371      prev = window->prev;
     372      next = window->next;
     373  
     374    /* WINDOW decreasing in size? */
     375    if (amount < 0)
     376      {
     377        int abs_amount = -amount; /* It is easier to deal with this way. */
     378  
     379        /* If the resultant window would be too small, stop here. */
     380        if ((window->height - abs_amount) < WINDOW_MIN_HEIGHT)
     381          return;
     382  
     383        /* If we have two neighboring windows, choose the smaller one to get
     384           larger. */
     385        if (next && prev)
     386          {
     387            if (prev->height < next->height)
     388              shrink_me_growing_prev (window, prev, abs_amount);
     389            else
     390              shrink_me_growing_next (window, next, abs_amount);
     391          }
     392        else if (next)
     393          shrink_me_growing_next (window, next, abs_amount);
     394        else
     395          shrink_me_growing_prev (window, prev, abs_amount);
     396      }
     397  
     398    /* WINDOW increasing in size? */
     399    if (amount > 0)
     400      {
     401        int total_avail, next_avail = 0, prev_avail = 0;
     402  
     403        if (next)
     404          next_avail = next->height - WINDOW_MIN_SIZE;
     405  
     406        if (prev)
     407          prev_avail = prev->height - WINDOW_MIN_SIZE;
     408  
     409        total_avail = next_avail + prev_avail;
     410  
     411        /* If there isn't enough space available to grow this window, give up. */
     412        if (amount > total_avail)
     413          return;
     414  
     415        /* If there aren't two neighboring windows, or if one of the neighbors
     416           is larger than the other one by at least AMOUNT, grow that one. */
     417        if (next_avail - amount >= prev_avail)
     418          grow_me_shrinking_next (window, next, amount);
     419        else if (prev_avail - amount >= next_avail)
     420          grow_me_shrinking_prev (window, prev, amount);
     421        else
     422          {
     423            int change;
     424  
     425            /* This window has two neighbors.  They both must be shrunk in to
     426               make enough space for WINDOW to grow.  Make them both the same
     427               size. */
     428            if (prev_avail > next_avail)
     429              {
     430                change = prev_avail - next_avail;
     431                grow_me_shrinking_prev (window, prev, change);
     432                amount -= change;
     433              }
     434            else
     435              {
     436                change = next_avail - prev_avail;
     437                grow_me_shrinking_next (window, next, change);
     438                amount -= change;
     439              }
     440  
     441            /* Both neighbors are the same size.  Split the difference in
     442               AMOUNT between them. */
     443            while (amount)
     444              {
     445                window->height++;
     446                amount--;
     447  
     448                /* Odd numbers grow next, even grow prev. */
     449                if (amount & 1)
     450                  {
     451                    prev->height--;
     452                    window->first_row--;
     453                  }
     454                else
     455                  {
     456                    next->height--;
     457                    next->first_row++;
     458                  }
     459              }
     460          }
     461      }
     462    if (prev)
     463      prev->flags |= W_UpdateWindow;
     464  
     465    if (next)
     466      next->flags |= W_UpdateWindow;
     467  
     468    window->flags |= W_UpdateWindow;
     469  }
     470  
     471  /* Tile all of the windows currently displayed in the global variable
     472     WINDOWS.  If argument STYLE is TILE_INTERNALS, tile windows displaying
     473     internal nodes as well, otherwise do not change the height of such
     474     windows. */
     475  void
     476  window_tile_windows (int style)
     477  {
     478    WINDOW *win, *last_adjusted;
     479    int numwins, avail, per_win_height, leftover;
     480    int do_internals;
     481  
     482    numwins = avail = 0;
     483    do_internals = (style == TILE_INTERNALS);
     484  
     485    for (win = windows; win; win = win->next)
     486      if (do_internals || !win->node ||
     487          (win->node->flags & N_IsInternal) == 0)
     488        {
     489          avail += win->height;
     490          numwins++;
     491        }
     492  
     493    if (numwins <= 1 || !the_screen->height)
     494      return;
     495  
     496    /* Find the size for each window.  Divide the size of the usable portion
     497       of the screen by the number of windows. */
     498    per_win_height = avail / numwins;
     499    leftover = avail - (per_win_height * numwins);
     500  
     501    last_adjusted = NULL;
     502    for (win = windows; win; win = win->next)
     503      {
     504        if (do_internals || !win->node ||
     505            (win->node->flags & N_IsInternal) == 0)
     506          {
     507            last_adjusted = win;
     508            win->height = per_win_height;
     509          }
     510      }
     511  
     512    if (last_adjusted)
     513      last_adjusted->height += leftover;
     514  
     515    /* Readjust the first_row of every window in the chain. */
     516    for (win = windows; win; win = win->next)
     517      {
     518        if (win->prev)
     519          win->first_row = win->prev->first_row + win->prev->height + 1;
     520  
     521        window_adjust_pagetop (win);
     522        win->flags |= W_UpdateWindow;
     523      }
     524  }
     525  
     526  /* Toggle the state of line wrapping in WINDOW.  This can do a bit of fancy
     527     redisplay. */
     528  void
     529  window_toggle_wrap (WINDOW *window)
     530  {
     531    if (window->flags & W_NoWrap)
     532      window->flags &= ~W_NoWrap;
     533    else
     534      window->flags |= W_NoWrap;
     535  
     536    if (window != the_echo_area)
     537      {
     538        long *old_starts;
     539        long *old_xlat;
     540        int old_lines, old_pagetop;
     541  
     542        old_starts = window->line_starts;
     543        old_xlat = window->log_line_no;
     544        old_lines = window->line_count;
     545        old_pagetop = window->pagetop;
     546  
     547        calculate_line_starts (window);
     548  
     549        /* Make sure that point appears within this window. */
     550        window_adjust_pagetop (window);
     551  
     552        /* If the pagetop hasn't changed maybe we can do some scrolling now
     553           to speed up the display.  Many of the line starts will be the same,
     554           so scrolling here is a very good optimization.*/
     555        if (old_pagetop == window->pagetop)
     556          display_scroll_line_starts (window, old_pagetop,
     557                                      old_starts, old_lines);
     558        free (old_starts);
     559        free (old_xlat);
     560      }
     561    window->flags |= W_UpdateWindow;
     562  }
     563  
     564  /* Set WINDOW to display NODE. */
     565  void
     566  window_set_node_of_window (WINDOW *window, NODE *node)
     567  {
     568    window->node = node;
     569    window->pagetop = 0;
     570    window->point = 0;
     571  
     572    free (window->line_starts);
     573    free (window->log_line_no);
     574    calculate_line_starts (window);
     575    window_compute_line_map (window);
     576  
     577    /* Clear displayed search matches if any. */
     578    free_matches (&window->matches);
     579  
     580    window->flags |= W_UpdateWindow;
     581    if (node)
     582      {
     583        /* The display_pos member is nonzero if we're displaying an anchor.  */
     584        window->point = node ? node->display_pos : 0;
     585        window_adjust_pagetop (window);
     586      }
     587    window_make_modeline (window);
     588  }
     589  
     590  /* Delete WINDOW from the list of known windows.  If this window was the
     591     active window, make the next window in the chain be the active window.
     592     If the active window is the next or previous window, choose that window
     593     as the recipient of the extra space.  Otherwise, prefer the next window.
     594     Be aware that info_delete_window_internal (in session.c) should be called
     595     instead if you need to remove the window from the info_windows list. */
     596  void
     597  window_delete_window (WINDOW *window)
     598  {
     599    WINDOW *next, *prev, *window_to_fix;
     600  
     601    next = window->next;
     602    prev = window->prev;
     603  
     604    /* You cannot delete the only window or a permanent window. */
     605    if ((!next && !prev) || (window->flags & W_WindowIsPerm))
     606      return;
     607  
     608    if (next)
     609      next->prev = prev;
     610  
     611    if (!prev)
     612      windows = next;
     613    else
     614      prev->next = next;
     615  
     616    free (window->line_starts);
     617    free (window->log_line_no);
     618    free (window->line_map.map);
     619    free (window->modeline);
     620    free_matches (&window->matches);
     621    free (window->search_string);
     622  
     623    if (window == active_window)
     624      {
     625        WINDOW *new_active = 0;
     626  
     627        /* If there isn't a next window, then there must be a previous one,
     628           since we cannot delete the last window.  If there is a next window,
     629           prefer to use that as the active window.  Try to find an important
     630           window to select, e.g. not a footnotes window. */
     631        if (next)
     632          {
     633            new_active = next;
     634            while ((new_active->flags & W_TempWindow) && new_active->next)
     635              new_active = new_active->next;
     636          }
     637  
     638        if ((!new_active || new_active->flags & W_TempWindow) && prev)
     639          {
     640            new_active = prev;
     641            while ((new_active->flags & W_TempWindow) && new_active->prev)
     642              new_active = new_active->prev;
     643          }
     644        active_window = new_active;
     645      }
     646  
     647    if (next && active_window == next)
     648      window_to_fix = next;
     649    else if (prev && active_window == prev)
     650      window_to_fix = prev;
     651    else if (next)
     652      window_to_fix = next;
     653    else if (prev)
     654      window_to_fix = prev;
     655    else
     656      window_to_fix = windows;
     657      
     658    if (window_to_fix->first_row > window->first_row)
     659      {
     660        int diff;
     661  
     662        /* Try to adjust the visible part of the node so that as little
     663           text as possible has to move. */
     664        diff = window_to_fix->first_row - window->first_row;
     665        window_to_fix->first_row = window->first_row;
     666  
     667        window_to_fix->pagetop -= diff;
     668        if (window_to_fix->pagetop < 0)
     669          window_to_fix->pagetop = 0;
     670      }
     671  
     672    /* The `+ 1' is to offset the difference between the first_row locations.
     673       See the code in window_make_window (). */
     674    window_to_fix->height += window->height + 1;
     675    window_to_fix->flags |= W_UpdateWindow;
     676  
     677    free (window);
     678  }
     679  
     680  /* For every window in CHAIN, set the flags member to have FLAG set. */
     681  void
     682  window_mark_chain (WINDOW *chain, int flag)
     683  {
     684    register WINDOW *win;
     685  
     686    for (win = chain; win; win = win->next)
     687      win->flags |= flag;
     688  }
     689  
     690  /* For every window in CHAIN, clear the flags member of FLAG. */
     691  void
     692  window_unmark_chain (WINDOW *chain, int flag)
     693  {
     694    register WINDOW *win;
     695  
     696    for (win = chain; win; win = win->next)
     697      win->flags &= ~flag;
     698  }
     699  
     700  /* Return the number of first physical line corresponding to the logical
     701     line LN.
     702  
     703     A logical line can occupy one or more physical lines of output.  It
     704     occupies more than one physical line if its width is greater than the
     705     window width and the flag W_NoWrap is not set for that window.
     706   */
     707  long
     708  window_log_to_phys_line (WINDOW *window, long ln)
     709  {
     710    size_t i;
     711    
     712    if (ln > window->line_count)
     713      return 0;
     714    for (i = ln; i < window->line_count && window->log_line_no[i] < ln; i++)
     715      ;
     716    return i;
     717  }
     718  
     719  /* Change the pagetop of WINDOW to DESIRED_TOP, perhaps scrolling the screen
     720     to do so.  WINDOW->pagetop should be the currently displayed pagetop. */
     721  void
     722  set_window_pagetop (WINDOW *window, int desired_top)
     723  {
     724    int point_line, old_pagetop;
     725  
     726    if (desired_top < 0)
     727      desired_top = 0;
     728    else if (desired_top > window->line_count)
     729      desired_top = window->line_count - 1;
     730  
     731    if (window->pagetop == desired_top)
     732      return;
     733  
     734    old_pagetop = window->pagetop;
     735    window->pagetop = desired_top;
     736  
     737    /* Make sure that point appears in this window. */
     738    point_line = window_line_of_point (window);
     739    if (point_line < window->pagetop)
     740      {
     741        window->point = window->line_starts[window->pagetop];
     742        window->goal_column = 0;
     743      }
     744    else if (point_line >= window->pagetop + window->height)
     745      {
     746        long bottom = window->pagetop + window->height - 1;
     747        window->point = window->line_starts[bottom];
     748        window->goal_column = 0;
     749      }
     750  
     751    window->flags |= W_UpdateWindow;
     752  
     753    /* Find out which direction to scroll, and scroll the window in that
     754       direction.  Do this only if there would be a savings in redisplay
     755       time.  This is true if the amount to scroll is less than the height
     756       of the window, and if the number of lines scrolled would be greater
     757       than 10 % of the window's height.
     758  
     759       To prevent status line blinking when keeping up or down key,
     760       scrolling is disabled if the amount to scroll is 1. */
     761    if (old_pagetop < desired_top)
     762      {
     763        int start, end, amount;
     764  
     765        amount = desired_top - old_pagetop;
     766  
     767        if (amount == 1 ||
     768            (amount >= window->height) ||
     769            (((window->height - amount) * 10) < window->height))
     770          return;
     771  
     772        start = window->first_row;
     773        end = window->height + window->first_row;
     774  
     775        display_scroll_display (start, end, -amount);
     776      }
     777    else
     778      {
     779        int start, end, amount;
     780  
     781        amount = old_pagetop - desired_top;
     782  
     783        if (amount == 1 ||
     784            (amount >= window->height) ||
     785            (((window->height - amount) * 10) < window->height))
     786          return;
     787  
     788        start = window->first_row;
     789        end = window->first_row + window->height;
     790        display_scroll_display (start, end, amount);
     791      }
     792  }
     793  
     794  /* Adjust the pagetop of WINDOW such that the cursor point will be visible. */
     795  void
     796  window_adjust_pagetop (WINDOW *window)
     797  {
     798    register int line;
     799  
     800    if (!window->node)
     801      return;
     802  
     803    line = window_line_of_point (window);
     804  
     805    /* If this line appears in the current displayable page, do nothing.
     806       Otherwise, adjust the top of the page to make this line visible. */
     807    if (line < window->pagetop
     808        || line - window->pagetop > window->height - 1)
     809      {
     810        int new_pagetop = line - ((window->height - 1) / 2);
     811  
     812        if (new_pagetop < 0)
     813          new_pagetop = 0;
     814        set_window_pagetop (window, new_pagetop);
     815      }
     816  }
     817  
     818  /* Return the index of the line containing point. */
     819  int
     820  window_line_of_point (WINDOW *window)
     821  {
     822    register int i, start = 0;
     823  
     824    if (!window->line_starts)
     825      calculate_line_starts (window);
     826  
     827    /* Check if point is past the pagetop for this window, and if so, start 
     828       searching forward from there. */
     829    if (window->pagetop > -1 && window->pagetop < window->line_count
     830        && window->line_starts[window->pagetop] <= window->point)
     831      start = window->pagetop;
     832  
     833    for (i = start; i < window->line_count; i++)
     834      {
     835        if (window->line_starts[i] > window->point)
     836          break;
     837      }
     838  
     839    if (i > 0)
     840      return i - 1;
     841    else
     842      return 0; /* Shouldn't happen */
     843  }
     844  
     845  /* Get and return the printed column offset of the cursor in this window. */
     846  int
     847  window_get_cursor_column (WINDOW *window)
     848  {
     849    return window_point_to_column (window, window->point, &window->point);
     850  }
     851  
     852  /* Create a modeline for WINDOW, and store it in window->modeline. */
     853  void
     854  window_make_modeline (WINDOW *window)
     855  {
     856    register int i;
     857    char *modeline;
     858    char location_indicator[4];
     859    int lines_remaining;
     860  
     861    /* Only make modelines for those windows which have one. */
     862    if (window->flags & W_InhibitMode)
     863      return;
     864  
     865    /* Find the number of lines actually displayed in this window. */
     866    lines_remaining = window->line_count - window->pagetop;
     867  
     868    if (window->pagetop == 0)
     869      {
     870        if (lines_remaining <= window->height)
     871          strcpy (location_indicator, "All");
     872        else
     873          strcpy (location_indicator, "Top");
     874      }
     875    else
     876      {
     877        if (lines_remaining <= window->height)
     878          strcpy (location_indicator, "Bot");
     879        else
     880          {
     881            float pt, lc;
     882            int percentage;
     883  
     884            pt = (float)window->pagetop;
     885            lc = (float)(window->line_count - window->height);
     886  
     887            percentage = 100 * (pt / lc);
     888  
     889            sprintf (location_indicator, "%2d%%", percentage);
     890          }
     891      }
     892  
     893    /* Calculate the maximum size of the information to stick in MODELINE. */
     894    {
     895      int modeline_len = 0;
     896      char *nodename = "*no node*";
     897      NODE *node = window->node;
     898      char *name;
     899      int dot;
     900  
     901      if (node && node->nodename)
     902        nodename = node->nodename;
     903  
     904      name = filename_non_directory (node->fullpath);
     905  
     906      /* 10 for the decimal representation of the number of lines in this
     907         node, and the remainder of the text that can appear in the line. */
     908      modeline_len += 10 + strlen (_("-----Info: (), lines ----, "));
     909      modeline_len += 3; /* strlen (location_indicator) */
     910      modeline_len += strlen (name);
     911      if (nodename)
     912        modeline_len += strlen (nodename);
     913      if (modeline_len < window->width)
     914        modeline_len = window->width;
     915  
     916      modeline = xcalloc (1, 1 + modeline_len);
     917  
     918      sprintf (modeline + strlen (modeline), "-----Info: ");
     919  
     920      /* Omit any extension like ".info.gz" from file name. */
     921      dot = strcspn (name, ".");
     922  
     923      if (name && strcmp ("", name))
     924        {
     925          sprintf (modeline + strlen (modeline), "(");
     926          strncpy (modeline + strlen (modeline), name, dot);
     927          sprintf (modeline + strlen (modeline), ")");
     928        }
     929      sprintf (modeline + strlen (modeline),
     930               "%s, %ld lines --%s",
     931               nodename, window->line_count, location_indicator);
     932  
     933      i = strlen (modeline);
     934  
     935      if (i >= window->width)
     936        modeline[window->width] = '\0';
     937      else
     938        {
     939          while (i < window->width)
     940            modeline[i++] = '-';
     941          modeline[i] = '\0';
     942        }
     943  
     944      strcpy (window->modeline, modeline);
     945      free (modeline);
     946    }
     947  }
     948  
     949  /* Make WINDOW start displaying at PERCENT percentage of its node. */
     950  void
     951  window_goto_percentage (WINDOW *window, int percent)
     952  {
     953    int desired_line;
     954  
     955    if (!percent)
     956      desired_line = 0;
     957    else
     958      desired_line =
     959        (int) ((float)window->line_count * ((float)percent / 100.0));
     960  
     961    window->pagetop = desired_line;
     962    window->point =
     963      window->line_starts[window->pagetop];
     964    window->flags |= W_UpdateWindow;
     965    window_make_modeline (window);
     966  }
     967  
     968  
     969  /* A place to buffer echo area messages. */
     970  static NODE *echo_area_node = NULL;
     971  
     972  /* Make the node of the_echo_area be an empty one. */
     973  void
     974  free_echo_area (void)
     975  {
     976    if (echo_area_node)
     977      {
     978        free (echo_area_node->contents);
     979        free (echo_area_node);
     980      }
     981  
     982    echo_area_node = NULL;
     983    window_set_node_of_window (the_echo_area, echo_area_node);
     984  }
     985    
     986  /* Clear the echo area, removing any message that is already present.
     987     The echo area is cleared immediately. */
     988  void
     989  window_clear_echo_area (void)
     990  {
     991    free_echo_area ();
     992    display_update_one_window (the_echo_area);
     993  }
     994  
     995  void
     996  vwindow_message_in_echo_area (const char *format, va_list ap)
     997  {
     998    free_echo_area ();
     999    echo_area_node = build_message_node (format, ap);
    1000    window_set_node_of_window (the_echo_area, echo_area_node);
    1001    display_update_one_window (the_echo_area);
    1002  }
    1003  
    1004  /* Make a message appear in the echo area, built from FORMAT, ARG1 and ARG2.
    1005     The arguments are treated similar to printf () arguments, but not all of
    1006     printf () hair is present.  The message appears immediately.  If there was
    1007     already a message appearing in the echo area, it is removed. */
    1008  void
    1009  window_message_in_echo_area (const char *format, ...)
    1010  {
    1011    va_list ap;
    1012    
    1013    va_start (ap, format);
    1014    vwindow_message_in_echo_area (format, ap);
    1015    va_end (ap);
    1016  }
    1017  
    1018  /* Place a temporary message in the echo area built from FORMAT, ARG1
    1019     and ARG2.  The message appears immediately, but does not destroy
    1020     any existing message.  A future call to unmessage_in_echo_area ()
    1021     restores the old contents. */
    1022  static NODE **old_echo_area_nodes = NULL;
    1023  static size_t old_echo_area_nodes_index = 0;
    1024  static size_t old_echo_area_nodes_slots = 0;
    1025  
    1026  void
    1027  message_in_echo_area (const char *format, ...)
    1028  {
    1029    va_list ap;
    1030    
    1031    if (echo_area_node)
    1032      {
    1033        add_pointer_to_array (echo_area_node, old_echo_area_nodes_index,
    1034                              old_echo_area_nodes, old_echo_area_nodes_slots,
    1035                              4);
    1036      }
    1037    echo_area_node = NULL;
    1038    va_start (ap, format);
    1039    vwindow_message_in_echo_area (format, ap);
    1040    va_end (ap);
    1041  }
    1042  
    1043  void
    1044  unmessage_in_echo_area (void)
    1045  {
    1046    free_echo_area ();
    1047  
    1048    if (old_echo_area_nodes_index)
    1049      echo_area_node = old_echo_area_nodes[--old_echo_area_nodes_index];
    1050  
    1051    window_set_node_of_window (the_echo_area, echo_area_node);
    1052    display_update_one_window (the_echo_area);
    1053  }
    1054  
    1055  
    1056  /* Build a new node which has FORMAT printed with ARG1 and ARG2 as the
    1057     contents. */
    1058  NODE *
    1059  build_message_node (const char *format, va_list ap)
    1060  {
    1061    struct text_buffer msg;
    1062  
    1063    text_buffer_init (&msg);
    1064    text_buffer_vprintf (&msg, format, ap);
    1065  
    1066    return text_buffer_to_node (&msg);
    1067  }
    1068  
    1069  NODE *
    1070  format_message_node (const char *format, ...)
    1071  {
    1072    NODE *node;
    1073    va_list ap;
    1074    
    1075    va_start (ap, format);
    1076    node = build_message_node (format, ap);
    1077    va_end (ap);
    1078    return node;
    1079  }
    1080  
    1081  NODE *
    1082  text_buffer_to_node (struct text_buffer *tb)
    1083  {
    1084    NODE *node;
    1085  
    1086    node = info_create_node ();
    1087  
    1088    /* Make sure that this buffer ends with a newline. */
    1089    text_buffer_add_char (tb, '\n');
    1090    node->nodelen = text_buffer_off (tb);
    1091    text_buffer_add_char (tb, '\0');
    1092  
    1093    node->contents = text_buffer_base (tb);
    1094    node->flags |= N_IsInternal;
    1095    return node;
    1096  }
    1097  
    1098  /* Used by calculate_line_starts to record line starts in the
    1099     win->LINE_COUNT and win->LOG_LINE_NO arrays. */
    1100  static void
    1101  collect_line_starts (WINDOW *win, long ll_num, long pl_start)
    1102  {
    1103    add_element_to_array (pl_start, win->line_count,
    1104                          win->line_starts, win->line_slots, 2);
    1105  
    1106    /* We cannot do add_element_to_array for this, as this would lead
    1107       to incrementing cp->win->line_count twice. */
    1108    win->log_line_no = xrealloc (win->log_line_no,
    1109                                 win->line_slots * sizeof (long));
    1110    win->log_line_no[win->line_count - 1] = ll_num;
    1111  }
    1112  
    1113  #define NO_NODELINE 0
    1114  #define PRINT_NODELINE 1
    1115  #define NODELINE_POINTERS_ONLY 2
    1116  int nodeline_print = 2;
    1117  
    1118  /* Calculate a list of line starts for the node belonging to WINDOW.  The
    1119     line starts are offsets within WINDOW->node->contents.
    1120  
    1121     Note that this function must agree with what display_update_one_window
    1122     in display.c does. */
    1123  void
    1124  calculate_line_starts (WINDOW *win)
    1125  {
    1126    long pl_chars = 0;     /* Number of characters in line so far. */
    1127    long pl_start;         /* Offset of start of current physical line. */
    1128    long ll_num = 0;       /* Number of logical lines */
    1129    mbi_iterator_t iter;
    1130  
    1131    /* Width of character carried over from one physical line to the next.  */
    1132    size_t carried_over_chars = 0;
    1133  
    1134    win->line_starts = NULL;
    1135    win->log_line_no = NULL;
    1136    win->line_count = 0;
    1137    win->line_slots = 0;
    1138  
    1139    if (!win->node)
    1140      return;
    1141  
    1142    pl_start = 0;
    1143    if (nodeline_print != PRINT_NODELINE
    1144        && !memcmp (win->node->contents, "File:", strlen ("File:")))
    1145      {
    1146        char *s = strchr (win->node->contents, '\n');
    1147        if (s && nodeline_print == NO_NODELINE)
    1148          {
    1149            pl_start = s - win->node->contents + 1;
    1150          }
    1151        else if (s && nodeline_print == NODELINE_POINTERS_ONLY)
    1152          {
    1153            char *s2;
    1154            char saved = *s;
    1155            *s = '\0';
    1156            s2 = strstr (win->node->contents, "Next: ");
    1157            if (!s2)
    1158              s2 = strstr (win->node->contents, "Prev: ");
    1159            if (!s2)
    1160              s2 = strstr (win->node->contents, "Up: ");
    1161            if (s2)
    1162              pl_start = s2 - win->node->contents;
    1163            *s = saved;
    1164          }
    1165      }
    1166  
    1167    for (mbi_init (iter,
    1168                   win->node->contents + pl_start,
    1169                   win->node->nodelen - pl_start);
    1170         mbi_avail (iter);
    1171         mbi_advance (iter))
    1172      {
    1173        size_t pchars = 0; /* Screen columns for this character. */
    1174        size_t pbytes = 0; /* Not used. */
    1175        int delim = 0;
    1176  
    1177        /* Set pchars. */
    1178        (void) printed_representation (&iter, &delim, pl_chars,
    1179                                       &pchars, &pbytes);
    1180  
    1181        /* If this character can be printed without passing the width of
    1182           the line, then include it in the line. */
    1183        if (!delim && pl_chars + pchars < win->width)
    1184          {
    1185            pl_chars += pchars;
    1186            continue;
    1187          }
    1188  
    1189        /* If this character cannot be printed in this line, we have
    1190           found the end of this line as it would appear on the screen. */
    1191  
    1192        carried_over_chars = delim ? 0 : pchars;
    1193  
    1194        collect_line_starts (win, ll_num, pl_start);
    1195  
    1196        if (delim == '\r' || delim == '\n')
    1197          ++ll_num;
    1198  
    1199        /* Start a new physical line at next character, unless a character
    1200           was carried over, in which case start there. */
    1201        pl_start = mbi_cur_ptr (iter) - win->node->contents;
    1202        if (carried_over_chars == 0)
    1203          pl_start += mb_len (mbi_cur (iter));
    1204        pl_chars = 0;
    1205  
    1206        /* If there is a character carried over, count it now.  Expected to be 
    1207           "short", i.e. a representation like "^A". */
    1208        if (carried_over_chars != 0)
    1209          {
    1210            pl_chars = carried_over_chars;
    1211      
    1212            /* If this window has chosen not to wrap lines, skip to the end
    1213               of the logical line in the buffer, and start a new line here. */
    1214            if (win->flags & W_NoWrap)
    1215              {
    1216                for (; mbi_avail (iter); mbi_advance (iter))
    1217                  if (mb_len (mbi_cur (iter)) == 1
    1218                      && *mbi_cur_ptr (iter) == '\n')
    1219                    break;
    1220  
    1221                pl_chars = 0;
    1222                pl_start = mbi_cur_ptr (iter) + mb_len (mbi_cur (iter))
    1223                           - win->node->contents;
    1224              }
    1225          }
    1226      }
    1227  
    1228    if (pl_chars)
    1229      collect_line_starts (win, ll_num++, pl_start);
    1230  
    1231    /* Have one line start at the end of the node. */
    1232    collect_line_starts (win, ll_num, mbi_cur_ptr (iter) - win->node->contents);
    1233    win->line_count--;
    1234  
    1235    /* Finally, initialize the line map for the current line. */
    1236    window_line_map_init (win);
    1237  }
    1238  
    1239  
    1240  static void
    1241  line_map_init (LINE_MAP *map, NODE *node, int line)
    1242  {
    1243    map->node = node;
    1244    map->nline = line;
    1245    map->used = 0;
    1246  }
    1247  
    1248  static void
    1249  line_map_add (LINE_MAP *map, long pos)
    1250  {
    1251    if (map->used == map->size)
    1252      {
    1253        if (map->size == 0)				       
    1254  	map->size = 80; /* Initial allocation */	       
    1255        map->map = x2nrealloc (map->map,
    1256  			     &map->size,
    1257  			     sizeof (map->map[0]));
    1258      }
    1259  
    1260    map->map[map->used++] = pos;
    1261  }
    1262  
    1263  /* Initialize (clear) WIN's line map. */
    1264  void
    1265  window_line_map_init (WINDOW *win)
    1266  {
    1267    win->line_map.used = 0;
    1268  }
    1269  
    1270  /* Compute the line map for the current line in WIN. */
    1271  void
    1272  window_compute_line_map (WINDOW *win)
    1273  {
    1274    int line = window_line_of_point (win);
    1275    mbi_iterator_t iter;
    1276    int delim = 0;
    1277    char *endp;
    1278    const char *cur_ptr;
    1279  
    1280    if (win->line_map.node == win->node && win->line_map.nline == line
    1281        && win->line_map.used)
    1282      return;
    1283    line_map_init (&win->line_map, win->node, line);
    1284    if (!win->node)
    1285      return;
    1286  
    1287    if (line + 1 < win->line_count)
    1288      endp = win->node->contents + win->line_starts[line + 1];
    1289    else
    1290      endp = win->node->contents + win->node->nodelen;
    1291    
    1292    for (mbi_init (iter,
    1293  		 win->node->contents + win->line_starts[line], 
    1294  		 win->node->nodelen - win->line_starts[line]);
    1295         !delim && mbi_avail (iter);
    1296         mbi_advance (iter))
    1297      {
    1298        size_t pchars, pbytes;
    1299        cur_ptr = mbi_cur_ptr (iter);
    1300  
    1301        if (cur_ptr >= endp)
    1302  	break;
    1303        
    1304        /* Set pchars */
    1305        (void) printed_representation (&iter, &delim, win->line_map.used,
    1306                                       &pchars, &pbytes);
    1307  
    1308        while (pchars--)
    1309          line_map_add (&win->line_map, cur_ptr - win->node->contents);
    1310      }
    1311  }
    1312  
    1313  /* Translate the value of POINT into a column number.  If NP is given
    1314     store there the value of point corresponding to the beginning of a
    1315     multibyte character in this column.  If the character at POINT spans 
    1316     multiple columns (e.g. a tab), return the leftmost column it occupies. */
    1317  int
    1318  window_point_to_column (WINDOW *win, long point, long *np)
    1319  {
    1320    int i;
    1321    
    1322    window_compute_line_map (win);
    1323    if (!win->line_map.map || point < win->line_map.map[0])
    1324      return 0;
    1325    for (i = 0; i < win->line_map.used; i++)
    1326      if (win->line_map.map[i] >= point)
    1327        break;
    1328    if (np)
    1329      *np = win->line_map.map[i];
    1330    return i;
    1331  }
    1332