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