Contents of release-21-2 at 1999-07-06-13.
[chise/xemacs-chise.git.1] / src / indent.c
1 /* Indentation functions.
2    Copyright (C) 1995 Board of Trustees, University of Illinois.
3    Copyright (C) 1985, 1986, 1987, 1988, 1992, 1993, 1994, 1995
4    Free Software Foundation, Inc.
5
6 This file is part of XEmacs.
7
8 XEmacs is free software; you can redistribute it and/or modify it
9 under the terms of the GNU General Public License as published by the
10 Free Software Foundation; either version 2, or (at your option) any
11 later version.
12
13 XEmacs is distributed in the hope that it will be useful, but WITHOUT
14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
16 for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with XEmacs; see the file COPYING.  If not, write to
20 the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21 Boston, MA 02111-1307, USA.  */
22
23 /* This file has been Mule-ized. */
24
25 /* Synched up with: 19.30.  Diverges significantly from FSF. */
26
27
28 #include <config.h>
29 #include "lisp.h"
30
31 #include "buffer.h"
32 #include "device.h"
33 #include "extents.h"
34 #include "faces.h"
35 #include "frame.h"
36 #include "glyphs.h"
37 #include "insdel.h"
38 #ifdef REGION_CACHE_NEEDS_WORK
39 #include "region-cache.h"
40 #endif
41 #include "window.h"
42
43 Lisp_Object Qcoerce;
44
45 /* Indentation can insert tabs if this is non-zero;
46    otherwise always uses spaces */
47 int indent_tabs_mode;
48
49 /* Avoid recalculation by remembering things in these variables. */
50
51 /* Last value returned by current_column.
52
53    Some things set last_known_column_point to -1
54    to mark the memoized value as invalid */
55 static int last_known_column;
56
57 /* Last buffer searched by current_column */
58 static struct buffer *last_known_column_buffer;
59
60 /* Value of point when current_column was called */
61 static Bufpos last_known_column_point;
62
63 /* Value of MODIFF when current_column was called */
64 static int last_known_column_modified;
65
66 static Bufpos
67 last_visible_position (Bufpos pos, struct buffer *buf)
68 {
69   Lisp_Object buffer;
70   Lisp_Object value;
71
72   XSETBUFFER (buffer, buf);
73   value = Fprevious_single_property_change (make_int (pos), Qinvisible,
74                                             buffer, Qnil);
75   if (NILP (value))
76     return 0; /* no visible position found */
77   else
78     /* #### bug bug bug!!! This will return the position of the beginning
79        of an invisible extent; this extent is very likely to be start-closed,
80        and thus the spaces inserted in `indent-to' will go inside the
81        invisible extent.
82
83        Not sure what the correct solution is here.  Rethink indent-to? */
84     return XINT (value);
85 }
86
87 #ifdef REGION_CACHE_NEEDS_WORK
88
89 /* Allocate or free the width run cache, as requested by the current
90    state of current_buffer's cache_long_line_scans variable.  */
91 static void
92 width_run_cache_on_off (struct buffer *buf)
93 {
94   if (NILP (buf->cache_long_line_scans))
95     {
96       /* It should be off.  */
97       if (buf->width_run_cache)
98         {
99           free_region_cache (buf->width_run_cache);
100           buf->width_run_cache = 0;
101           buf->width_table = Qnil;
102         }
103     }
104   else
105     {
106       /* It should be on.  */
107       if (buf->width_run_cache == 0)
108         {
109           buf->width_run_cache = new_region_cache ();
110           recompute_width_table (buf, buffer_display_table ());
111         }
112     }
113 }
114
115 #endif /* REGION_CACHE_NEEDS_WORK */
116 \f
117
118 /* Cancel any recorded value of the horizontal position.  */
119
120 void
121 invalidate_current_column (void)
122 {
123   last_known_column_point = -1;
124 }
125
126 int
127 column_at_point (struct buffer *buf, Bufpos init_pos, int cur_col)
128 {
129   int col;
130   int tab_seen;
131   int tab_width = XINT (buf->tab_width);
132   int post_tab;
133   Bufpos pos = init_pos;
134   Emchar c;
135
136   if (tab_width <= 0 || tab_width > 1000) tab_width = 8;
137   col = tab_seen = post_tab = 0;
138
139   while (1)
140     {
141       if (pos <= BUF_BEGV (buf))
142         break;
143
144       pos--;
145       c = BUF_FETCH_CHAR (buf, pos);
146       if (c == '\t')
147         {
148           if (tab_seen)
149             col = ((col + tab_width) / tab_width) * tab_width;
150
151           post_tab += col;
152           col = 0;
153           tab_seen = 1;
154         }
155       else if (c == '\n' ||
156                (EQ (buf->selective_display, Qt) && c == '\r'))
157         break;
158       else
159         {
160           /* #### This needs updating to handle the new redisplay. */
161           /* #### FSFmacs looks at ctl_arrow, display tables.
162              We need to do similar. */
163 #if 0
164           displayed_glyphs = glyphs_from_bufpos (sel_frame, buf,
165                                                  XWINDOW (selected_window),
166                                                  pos, dp, 0, col, 0, 0, 0);
167           col += (displayed_glyphs->columns
168                   - (displayed_glyphs->begin_columns
169                      + displayed_glyphs->end_columns));
170 #else /* XEmacs */
171 #ifdef MULE
172           col += XCHARSET_COLUMNS (CHAR_CHARSET (c));
173 #else
174           col ++;
175 #endif /* MULE */
176 #endif /* XEmacs */
177         }
178     }
179
180   if (tab_seen)
181     {
182       col = ((col + tab_width) / tab_width) * tab_width;
183       col += post_tab;
184     }
185
186   if (cur_col)
187     {
188       last_known_column_buffer = buf;
189       last_known_column = col;
190       last_known_column_point = init_pos;
191       last_known_column_modified = BUF_MODIFF (buf);
192     }
193
194   return col;
195 }
196
197 int
198 current_column (struct buffer *buf)
199 {
200   if (buf == last_known_column_buffer
201       && BUF_PT (buf) == last_known_column_point
202       && BUF_MODIFF (buf) == last_known_column_modified)
203     return last_known_column;
204
205   return column_at_point (buf, BUF_PT (buf), 1);
206 }
207
208 DEFUN ("current-column", Fcurrent_column, 0, 1, 0, /*
209 Return the horizontal position of point.  Beginning of line is column 0.
210 This is calculated by adding together the widths of all the displayed
211  representations of the character between the start of the previous line
212  and point. (e.g. control characters will have a width of 2 or 4, tabs
213  will have a variable width.)
214 Ignores finite width of frame, which means that this function may return
215  values greater than (frame-width).
216 Whether the line is visible (if `selective-display' is t) has no effect;
217  however, ^M is treated as end of line when `selective-display' is t.
218 If BUFFER is nil, the current buffer is assumed.
219 */
220        (buffer))
221 {
222   return make_int (current_column (decode_buffer (buffer, 0)));
223 }
224
225 \f
226 DEFUN ("indent-to", Findent_to, 1, 3, "NIndent to column: ", /*
227 Indent from point with tabs and spaces until COLUMN is reached.
228 Optional second argument MIN says always do at least MIN spaces
229  even if that goes past COLUMN; by default, MIN is zero.
230 If BUFFER is nil, the current buffer is assumed.
231 */
232        (col, minimum, buffer))
233 {
234   /* This function can GC */
235   int mincol;
236   int fromcol;
237   struct buffer *buf = decode_buffer (buffer, 0);
238   int tab_width = XINT (buf->tab_width);
239   Bufpos opoint = 0;
240
241   CHECK_INT (col);
242   if (NILP (minimum))
243     minimum = Qzero;
244   else
245     CHECK_INT (minimum);
246
247   XSETBUFFER (buffer, buf);
248
249   fromcol = current_column (buf);
250   mincol = fromcol + XINT (minimum);
251   if (mincol < XINT (col)) mincol = XINT (col);
252
253   if (fromcol == mincol)
254     return make_int (mincol);
255
256   if (tab_width <= 0 || tab_width > 1000) tab_width = 8;
257
258   if (!NILP (Fextent_at (make_int (BUF_PT (buf)), buffer, Qinvisible,
259                          Qnil, Qnil)))
260     {
261       Bufpos last_visible = last_visible_position (BUF_PT (buf), buf);
262
263       opoint = BUF_PT (buf);
264       if (last_visible >= BUF_BEGV (buf))
265         BUF_SET_PT (buf, last_visible);
266       else
267         error ("Visible portion of buffer not modifiable");
268     }
269
270   if (indent_tabs_mode)
271     {
272       int n = mincol / tab_width - fromcol / tab_width;
273       if (n != 0)
274         {
275           Finsert_char (make_char ('\t'), make_int (n), Qnil, buffer);
276
277           fromcol = (mincol / tab_width) * tab_width;
278         }
279     }
280
281   Finsert_char (make_char (' '), make_int (mincol - fromcol), Qnil, buffer);
282
283   last_known_column_buffer = buf;
284   last_known_column = mincol;
285   last_known_column_point = BUF_PT (buf);
286   last_known_column_modified = BUF_MODIFF (buf);
287
288   /* Not in FSF: */
289   if (opoint > 0)
290     BUF_SET_PT (buf, opoint);
291
292   return make_int (mincol);
293 }
294
295 int
296 bi_spaces_at_point (struct buffer *b, Bytind bi_pos)
297 {
298   Bytind bi_end = BI_BUF_ZV (b);
299   int col = 0;
300   Emchar c;
301   int tab_width = XINT (b->tab_width);
302
303   if (tab_width <= 0 || tab_width > 1000)
304     tab_width = 8;
305
306   while (bi_pos < bi_end &&
307          (c = BI_BUF_FETCH_CHAR (b, bi_pos),
308           (c == '\t'
309            ? (col += tab_width - col % tab_width)
310            : (c == ' ' ? ++col : 0))))
311     INC_BYTIND (b, bi_pos);
312
313   return col;
314 }
315
316 \f
317 DEFUN ("current-indentation", Fcurrent_indentation, 0, 1, 0, /*
318 Return the indentation of the current line.
319 This is the horizontal position of the character
320 following any initial whitespace.
321 */
322        (buffer))
323 {
324   struct buffer *buf = decode_buffer (buffer, 0);
325   Bufpos pos = find_next_newline (buf, BUF_PT (buf), -1);
326
327   XSETBUFFER (buffer, buf);
328
329   if (!NILP (Fextent_at (make_int (pos), buffer, Qinvisible, Qnil, Qnil)))
330     return Qzero;
331
332   return make_int (bi_spaces_at_point (buf, bufpos_to_bytind (buf, pos)));
333 }
334
335 \f
336 DEFUN ("move-to-column", Fmove_to_column, 1, 3, 0, /*
337 Move point to column COLUMN in the current line.
338 The column of a character is calculated by adding together the widths
339 as displayed of the previous characters in the line.
340 This function ignores line-continuation;
341 there is no upper limit on the column number a character can have
342 and horizontal scrolling has no effect.
343
344 If specified column is within a character, point goes after that character.
345 If it's past end of line, point goes to end of line.
346
347 A value of 'coerce for the second (optional) argument FORCE means if
348 COLUMN is in the middle of a tab character, change it to spaces.
349 Any other non-nil value means the same, plus if the line is too short to
350 reach column COLUMN, then add spaces/tabs to get there.
351
352 Returns the actual column that it moved to.
353 */
354        (column, force, buffer))
355 {
356   /* This function can GC */
357   Bufpos pos;
358   struct buffer *buf = decode_buffer (buffer, 0);
359   int col = current_column (buf);
360   int goal;
361   Bufpos end;
362   int tab_width = XINT (buf->tab_width);
363
364   int prev_col = 0;
365   Emchar c = 0;
366
367   XSETBUFFER (buffer, buf);
368   if (tab_width <= 0 || tab_width > 1000) tab_width = 8;
369   CHECK_NATNUM (column);
370   goal = XINT (column);
371
372  retry:
373   pos = BUF_PT (buf);
374   end = BUF_ZV (buf);
375
376   /* If we're starting past the desired column,
377      back up to beginning of line and scan from there.  */
378   if (col > goal)
379     {
380       pos = find_next_newline (buf, pos, -1);
381       col = 0;
382     }
383
384   while (col < goal && pos < end)
385     {
386       c = BUF_FETCH_CHAR (buf, pos);
387       if (c == '\n')
388         break;
389       if (c == '\r' && EQ (buf->selective_display, Qt))
390         break;
391       if (c == '\t')
392         {
393           prev_col = col;
394           col += tab_width;
395           col = col / tab_width * tab_width;
396         }
397       else
398         {
399           /* #### oh for the days of the complete new redisplay */
400           /* #### FSFmacs looks at ctl_arrow, display tables.
401              We need to do similar. */
402 #if 0
403           displayed_glyphs = glyphs_from_bufpos (selected_frame (),
404                                                  buf,
405                                                  XWINDOW (Fselected_window (Qnil)),
406                                                  pos, dp, 0, col, 0, 0, 0);
407           col += (displayed_glyphs->columns
408                   - (displayed_glyphs->begin_columns
409                      + displayed_glyphs->end_columns));
410 #else /* XEmacs */
411 #ifdef MULE
412           col += XCHARSET_COLUMNS (CHAR_CHARSET (c));
413 #else
414           col ++;
415 #endif /* MULE */
416 #endif /* XEmacs */
417         }
418
419       pos++;
420     }
421
422   BUF_SET_PT (buf, pos);
423
424   /* If a tab char made us overshoot, change it to spaces
425      and scan through it again.  */
426   if (!NILP (force) && col > goal && c == '\t' && prev_col < goal)
427     {
428       buffer_delete_range (buf, BUF_PT (buf) - 1, BUF_PT (buf), 0);
429       Findent_to (make_int (col - 1), Qzero, buffer);
430       buffer_insert_emacs_char (buf, ' ');
431       goto retry;
432     }
433
434   /* If line ends prematurely, add space to the end.  */
435   if (col < goal && !NILP (force) && !EQ (force, Qcoerce))
436     {
437       col = goal;
438       Findent_to (make_int (col), Qzero, buffer);
439     }
440
441   last_known_column_buffer = buf;
442   last_known_column = col;
443   last_known_column_point = BUF_PT (buf);
444   last_known_column_modified = BUF_MODIFF (buf);
445
446   return make_int (col);
447 }
448
449 #if 0 /* #### OK boys, this function needs to be present, I think.
450          It was there before the 19.12 redisplay rewrite. */
451
452 xxDEFUN ("compute-motion", Fcompute_motion, 7, 7, 0, /*
453   "Scan through the current buffer, calculating screen position.
454 Scan the current buffer forward from offset FROM,
455 assuming it is at position FROMPOS--a cons of the form (HPOS . VPOS)--
456 to position TO or position TOPOS--another cons of the form (HPOS . VPOS)--
457 and return the ending buffer position and screen location.
458
459 There are three additional arguments:
460
461 WIDTH is the number of columns available to display text;
462 this affects handling of continuation lines.
463 This is usually the value returned by `window-width', less one (to allow
464 for the continuation glyph).
465
466 OFFSETS is either nil or a cons cell (HSCROLL . TAB-OFFSET).
467 HSCROLL is the number of columns not being displayed at the left
468 margin; this is usually taken from a window's hscroll member.
469 TAB-OFFSET is the number of columns of the first tab that aren't
470 being displayed, perhaps because the line was continued within it.
471 If OFFSETS is nil, HSCROLL and TAB-OFFSET are assumed to be zero.
472
473 WINDOW is the window to operate on.  Currently this is used only to
474 find the display table.  It does not matter what buffer WINDOW displays;
475 `compute-motion' always operates on the current buffer.
476
477 The value is a list of five elements:
478   (POS HPOS VPOS PREVHPOS CONTIN)
479 POS is the buffer position where the scan stopped.
480 VPOS is the vertical position where the scan stopped.
481 HPOS is the horizontal position where the scan stopped.
482
483 PREVHPOS is the horizontal position one character back from POS.
484 CONTIN is t if a line was continued after (or within) the previous character.
485
486 For example, to find the buffer position of column COL of line LINE
487 of a certain window, pass the window's starting location as FROM
488 and the window's upper-left coordinates as FROMPOS.
489 Pass the buffer's (point-max) as TO, to limit the scan to the end of the
490 visible section of the buffer, and pass LINE and COL as TOPOS.
491 */
492          (from, frompos, to, topos, width, offsets, window))
493 {
494   Lisp_Object bufpos, hpos, vpos, prevhpos, contin;
495   struct position *pos;
496   int hscroll, tab_offset;
497   struct window *w = decode_window (window);
498
499   CHECK_INT_COERCE_MARKER (from);
500   CHECK_CONS (frompos);
501   CHECK_INT (XCAR (frompos));
502   CHECK_INT (XCDR (frompos));
503   CHECK_INT_COERCE_MARKER (to);
504   CHECK_CONS (topos);
505   CHECK_INT (XCAR (topos));
506   CHECK_INT (XCDR (topos));
507   CHECK_INT (width);
508   if (!NILP (offsets))
509     {
510       CHECK_CONS (offsets);
511       CHECK_INT (XCAR (offsets));
512       CHECK_INT (XCDR (offsets));
513       hscroll = XINT (XCAR (offsets));
514       tab_offset = XINT (XCDR (offsets));
515     }
516   else
517     hscroll = tab_offset = 0;
518
519   pos = compute_motion (XINT (from), XINT (XCDR (frompos)),
520                         XINT (XCAR (frompos)),
521                         XINT (to), XINT (XCDR (topos)),
522                         XINT (XCAR (topos)),
523                         XINT (width), hscroll, tab_offset, w);
524
525   XSETINT (bufpos, pos->bufpos);
526   XSETINT (hpos, pos->hpos);
527   XSETINT (vpos, pos->vpos);
528   XSETINT (prevhpos, pos->prevhpos);
529
530   return list5 (bufpos, hpos, vpos, prevhpos,
531                 pos->contin ? Qt : Qnil);
532 }
533
534 #endif /* 0 */
535
536 /* Helper for vmotion_1 - compute vertical pixel motion between
537    START and END in the line start cache CACHE.  This just sums
538    the line heights, including both the starting and ending lines.
539 */
540 static int
541 vpix_motion (line_start_cache_dynarr *cache, int start, int end)
542 {
543   int i, vpix;
544
545   assert (start <= end);
546   assert (start >= 0);
547   assert (end < Dynarr_length (cache));
548
549   vpix = 0;
550   for (i = start; i <= end; i++)
551     vpix += Dynarr_atp (cache, i)->height;
552
553   return vpix;
554 }
555
556 /*****************************************************************************
557  vmotion_1
558
559  Given a starting position ORIG, move point VTARGET lines in WINDOW.
560  Returns the new value for point.  If the arg ret_vpos is not nil, it is
561  taken to be a pointer to an int and the number of lines actually moved is
562  returned in it.  If the arg ret_vpix is not nil, it is taken to be a
563  pointer to an int and the vertical pixel height of the motion which
564  took place is returned in it.
565  ****************************************************************************/
566 static Bufpos
567 vmotion_1 (struct window *w, Bufpos orig, int vtarget,
568            int *ret_vpos, int *ret_vpix)
569 {
570   struct buffer *b = XBUFFER (w->buffer);
571   int elt;
572
573   elt = point_in_line_start_cache (w, orig, (vtarget < 0
574                                              ? -vtarget
575                                              : vtarget));
576
577   /* #### This assertion must be true before the if statements are hit
578      but may possibly be wrong after the call to
579      point_in_line_start_cache if orig is outside of the visible
580      region of the buffer.  Handle this. */
581   assert (elt >= 0);
582
583   /* Moving downward. */
584   if (vtarget > 0)
585     {
586       int cur_line = Dynarr_length (w->line_start_cache) - 1 - elt;
587       Bufpos ret_pt;
588
589       if (cur_line > vtarget)
590         cur_line = vtarget;
591
592       /* The traditional FSF behavior is to return the end of buffer
593          position if we couldn't move far enough because we hit it.  */
594       if (cur_line < vtarget)
595         ret_pt = BUF_ZV (b);
596       else
597         ret_pt = Dynarr_atp (w->line_start_cache, cur_line + elt)->start;
598
599       while (ret_pt > BUF_ZV (b) && cur_line > 0)
600         {
601           cur_line--;
602           ret_pt = Dynarr_atp (w->line_start_cache, cur_line + elt)->start;
603         }
604
605       if (ret_vpos) *ret_vpos = cur_line;
606       if (ret_vpix)
607         *ret_vpix = vpix_motion (w->line_start_cache, elt, cur_line + elt);
608       return ret_pt;
609     }
610   else if (vtarget < 0)
611     {
612       if (elt < -vtarget)
613         {
614           if (ret_vpos) *ret_vpos = -elt;
615           if (ret_vpix)
616             *ret_vpix = vpix_motion (w->line_start_cache, 0, elt);
617           /* #### This should be BUF_BEGV (b), right? */
618           return Dynarr_atp (w->line_start_cache, 0)->start;
619         }
620       else
621         {
622           if (ret_vpos) *ret_vpos = vtarget;
623           if (ret_vpix)
624             *ret_vpix = vpix_motion (w->line_start_cache, elt + vtarget, elt);
625           return Dynarr_atp (w->line_start_cache, elt + vtarget)->start;
626         }
627     }
628   else
629     {
630       /* No vertical motion requested so we just return the position
631          of the beginning of the current line. */
632       if (ret_vpos) *ret_vpos = 0;
633       if (ret_vpix)
634         *ret_vpix = vpix_motion (w->line_start_cache, elt, elt);
635
636       return Dynarr_atp (w->line_start_cache, elt)->start;
637     }
638
639   RETURN_NOT_REACHED(0) /* shut up compiler */
640 }
641
642 /*****************************************************************************
643  vmotion
644
645  Given a starting position ORIG, move point VTARGET lines in WINDOW.
646  Returns the new value for point.  If the arg ret_vpos is not nil, it is
647  taken to be a pointer to an int and the number of lines actually moved is
648  returned in it.
649  ****************************************************************************/
650 Bufpos
651 vmotion (struct window *w, Bufpos orig, int vtarget, int *ret_vpos)
652 {
653   return vmotion_1 (w, orig, vtarget, ret_vpos, NULL);
654 }
655
656 /* Helper for Fvertical_motion.
657  */
658 static
659 Lisp_Object vertical_motion_1 (Lisp_Object lines, Lisp_Object window,
660                                int pixels)
661 {
662   Bufpos bufpos;
663   Bufpos orig;
664   int selected;
665   int *vpos, *vpix;
666   int value=0;
667   struct window *w;
668
669   if (NILP (window))
670     window = Fselected_window (Qnil);
671
672   CHECK_LIVE_WINDOW (window);
673   CHECK_INT (lines);
674
675   selected = (EQ (window, Fselected_window (Qnil)));
676
677   w = XWINDOW (window);
678
679   orig = selected ? BUF_PT (XBUFFER (w->buffer))
680                   : marker_position (w->pointm[CURRENT_DISP]);
681
682   vpos = pixels ? NULL   : &value;
683   vpix = pixels ? &value : NULL;
684
685   bufpos = vmotion_1 (w, orig, XINT (lines), vpos, vpix);
686
687   /* Note that the buffer's point is set, not the window's point. */
688   if (selected)
689     BUF_SET_PT (XBUFFER (w->buffer), bufpos);
690   else
691     set_marker_restricted (w->pointm[CURRENT_DISP],
692                            make_int(bufpos),
693                            w->buffer);
694
695   return make_int (value);
696 }
697
698 DEFUN ("vertical-motion", Fvertical_motion, 1, 3, 0, /*
699 Move to start of frame line LINES lines down.
700 If LINES is negative, this is moving up.
701 Optional second argument is WINDOW to move in,
702 the default is the selected window.
703
704 Sets point to position found; this may be start of line
705 or just the start of a continuation line.
706 If optional third argument PIXELS is nil, returns number
707 of lines moved; may be closer to zero than LINES if beginning
708 or end of buffer was reached.  If PIXELS is non-nil, the
709 vertical pixel height of the motion which took place is
710 returned instead of the actual number of lines moved.  A
711 motion of zero lines returns the height of the current line.
712
713 Note that `vertical-motion' sets WINDOW's buffer's point, not
714 WINDOW's point. (This differs from FSF Emacs, which buggily always
715 sets current buffer's point, regardless of WINDOW.)
716 */
717        (lines, window, pixels))
718 {
719   return vertical_motion_1 (lines, window, !NILP (pixels));
720 }
721
722 /*
723  * Like vmotion() but requested and returned movement is in pixels.
724  * HOW specifies the stopping condition.  Positive means move at least
725  * PIXELS.  Negative means at most.  Zero means as close as possible.
726  */
727 Bufpos
728 vmotion_pixels (Lisp_Object window, Bufpos start, int pixels, int how,
729                 int *motion)
730 {
731   struct window *w;
732   Bufpos eobuf, bobuf;
733   int defheight;
734   int needed;
735   int line, next;
736   int remain, abspix, dirn;
737   int elt, nelt;
738   int i;
739   line_start_cache_dynarr *cache;
740   int previous = -1;
741   int lines;
742
743   if (NILP (window))
744     window = Fselected_window (Qnil);
745
746   CHECK_LIVE_WINDOW (window);
747   w = XWINDOW (window);
748
749   eobuf = BUF_ZV (XBUFFER (w->buffer));
750   bobuf = BUF_BEGV (XBUFFER (w->buffer));
751
752   default_face_height_and_width (window, &defheight, NULL);
753
754   /* guess num lines needed in line start cache + a few extra */
755   abspix = abs (pixels);
756   needed = (abspix + defheight-1)/defheight + 3;
757
758   dirn = (pixels >= 0) ? 1 : -1;
759
760   while (1)
761     {
762       elt = point_in_line_start_cache (w, start, needed);
763       assert (elt >= 0); /* in the cache */
764
765       cache = w->line_start_cache;
766       nelt  = Dynarr_length (cache);
767
768       *motion = 0;
769
770       if (pixels == 0)
771         /* No vertical motion requested so we just return the position
772            of the beginning of the current display line. */
773         return Dynarr_atp (cache, elt)->start;
774
775       if ((dirn < 0 && elt == 0      &&
776            Dynarr_atp (cache, elt)->start <= bobuf) ||
777           (dirn > 0 && elt == nelt-1 &&
778            Dynarr_atp (cache, elt)->end   >= eobuf))
779         return Dynarr_atp (cache, elt)->start;
780
781       remain = abspix;
782       for (i = elt; (dirn > 0) ? (i < nelt) : (i > 0); i += dirn)
783         {
784           /* cache line we're considering moving over */
785           int ii = (dirn > 0) ? i : i-1;
786
787           if (remain < 0)
788             return Dynarr_atp (cache, i)->start;
789
790           line = Dynarr_atp (cache, ii)->height;
791           next = remain - line;
792
793           /* is stopping condition satisfied? */
794           if ((how >  0  &&  remain <= 0)  ||       /* at least */
795               (how <  0  &&  next < 0)     ||       /* at most */
796               (how == 0  &&  remain <= abs (next))) /* closest */
797             return Dynarr_atp (cache, i)->start;
798
799           /* moving down and nowhere left to go? */
800           if (dirn > 0 && Dynarr_atp (cache, ii)->end >= eobuf)
801             return Dynarr_atp (cache, ii)->start;
802
803           /* take the step */
804           remain   = next;
805           *motion += dirn * line;
806
807           /* moving up and nowhere left to go? */
808           if (dirn < 0 && Dynarr_atp (cache, ii)->start <= bobuf)
809             return Dynarr_atp (cache, ii)->start;
810         }
811
812       /* get here => need more cache lines.  try again. */
813       assert (abs (*motion) > previous); /* progress? */
814       previous = abs (*motion);
815
816       lines   = (pixels < 0) ? elt : (nelt - elt);
817       needed += (remain*lines + abspix-1)/abspix + 3;
818     }
819
820   RETURN_NOT_REACHED(0) /* shut up compiler */
821 }
822
823 DEFUN ("vertical-motion-pixels", Fvertical_motion_pixels, 1, 3, 0, /*
824 Move to start of frame line PIXELS vertical pixels down.
825 If PIXELS is negative, this is moving up.
826 The actual vertical motion in pixels is returned.
827
828 Optional second argument is WINDOW to move in,
829 the default is the selected window.
830
831 Optional third argument HOW specifies when to stop.  A value
832 less than zero indicates that the motion should be no more
833 than PIXELS.  A value greater than zero indicates that the
834 motion should be at least PIXELS.  Any other value indicates
835 that the motion should be as close as possible to PIXELS.
836 */
837        (pixels, window, how))
838 {
839   Bufpos bufpos;
840   Bufpos orig;
841   int selected;
842   int motion;
843   int howto;
844   struct window *w;
845
846   if (NILP (window))
847     window = Fselected_window (Qnil);
848
849   CHECK_LIVE_WINDOW (window);
850   CHECK_INT (pixels);
851
852   selected = (EQ (window, Fselected_window (Qnil)));
853
854   w = XWINDOW (window);
855
856   orig = selected ? BUF_PT (XBUFFER (w->buffer))
857                   : marker_position (w->pointm[CURRENT_DISP]);
858
859   howto = INTP (how) ? XINT (how) : 0;
860
861   bufpos = vmotion_pixels (window, orig, XINT (pixels), howto, &motion);
862
863   if (selected)
864     BUF_SET_PT (XBUFFER (w->buffer), bufpos);
865   else
866     set_marker_restricted (w->pointm[CURRENT_DISP],
867                            make_int(bufpos),
868                            w->buffer);
869
870   return make_int (motion);
871 }
872
873 \f
874 void
875 syms_of_indent (void)
876 {
877   DEFSUBR (Fcurrent_indentation);
878   DEFSUBR (Findent_to);
879   DEFSUBR (Fcurrent_column);
880   DEFSUBR (Fmove_to_column);
881 #if 0 /* #### */
882   DEFSUBR (Fcompute_motion);
883 #endif
884   DEFSUBR (Fvertical_motion);
885   DEFSUBR (Fvertical_motion_pixels);
886
887   defsymbol (&Qcoerce, "coerce");
888 }
889
890 void
891 vars_of_indent (void)
892 {
893   DEFVAR_BOOL ("indent-tabs-mode", &indent_tabs_mode /*
894 *Indentation can insert tabs if this is non-nil.
895 Setting this variable automatically makes it local to the current buffer.
896 */ );
897   indent_tabs_mode = 1;
898 }