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