[lice @ more rearranging. define-key modifications to accomodate bindings.lisp. this...
[lice.git] / src / indent.lisp
blobf6ce2e993f32e91e7575d4a2cb743ab6e04b2595
1 ;;; Indentation functions
3 (in-package "LICE")
5 (define-buffer-local indent-tabs-mode t
6 "*Indentation can insert tabs if this is non-nil.
7 Setting this variable automatically makes it local to the current buffer.")
9 (define-buffer-local *indent-line-function* 'indent-relative
10 "Function to indent the current line.
11 This function will be called with no arguments.
12 If it is called somewhere where auto-indentation cannot be done
13 \(f.ex. inside a string), the function should simply return `noindent'.
14 Setting this function is all you need to make TAB indent appropriately.
15 Don't rebind TAB unless you really need to.")
17 (defun position-indentation (pos-aref buffer)
18 ;; XXX: just cheap out on this puppy
19 (let ((column 0))
20 (loop
21 (inc-aref pos-aref buffer)
22 (when (>= pos-aref (zv-aref buffer))
23 (return column))
25 (let ((c (buffer-fetch-char pos-aref buffer)))
26 (cond ((char= c #\Space)
27 (incf column))
28 ((char= c #\Tab)
29 ;; FIXME: tab width
30 (incf column))
32 ;; categories?
33 (return column)))))))
35 (defun current-indentation (&aux (buffer (current-buffer)))
36 "Return the indentation of the current line.
37 This is the horizontal position of the character
38 following any initial whitespace."
39 (let ((pt (buffer-scan-newline buffer (pt buffer) (begv buffer) -1)))
40 (position-indentation (buffer-char-to-aref buffer pt) buffer)))
42 ;; (defun current-column ()
43 ;; "Return the current column that the current buffer's point is on."
44 ;; (let ((bol (buffer-beginning-of-line)))
45 ;; (- (point) bol)))
47 (defun current-column (&aux (buffer (current-buffer)))
48 "Return the horizontal position of point. Beginning of line is column 0.
49 This is calculated by adding together the widths of all the displayed
50 representations of the character between the start of the previous line
51 and point (eg. control characters will have a width of 2 or 4, tabs
52 will have a variable width).
53 Ignores finite width of frame, which means that this function may return
54 values greater than (frame-width).
55 Whether the line is visible (if `selective-display' is t) has no effect;
56 however, ^M is treated as end of line when `selective-display' is t.
57 Text that has an invisible property is considered as having width 0, unless
58 `buffer-invisibility-spec' specifies that it is replaced by an ellipsis."
59 (let ((pos-aref (buffer-char-to-aref buffer (pt buffer)))
60 (column 0)
62 (loop
63 (dec-aref pos-aref buffer)
64 (when (< pos-aref (begv-aref buffer))
65 (return column))
66 (setf c (buffer-fetch-char pos-aref buffer))
67 (cond ((char= c #\Newline)
68 (return column))
69 ((char= c #\Tab)
70 ;; TODO: tabs are > 1 column
71 (incf column))
72 ;; FIXME: what about control chars?
73 (t (incf column))))))
75 (defun indent-to (column &optional (minimum 0))
76 "Indent from point with tabs and spaces until COLUMN is reached.
77 Optional second argument MINIMUM says always do at least MINIMUM spaces
78 even if that goes past COLUMN; by default, MINIMUM is zero."
79 (check-type column number)
80 (check-type minimum number)
81 (let* ((fromcol (current-column))
82 (mincol (+ fromcol minimum)))
83 (when (< mincol column)
84 (setf mincol column))
85 (when (= fromcol mincol)
86 (return-from indent-to mincol))
88 ;; TODO: use tabs to indent
89 (insert-char #\Space column t)
91 ;; TODO: cache the last known column
92 mincol))
95 (defcommand move-to-column ((column &optional force)
96 :prefix)
97 "Move point to column COLUMN in the current line.
98 Interactively, COLUMN is the value of prefix numeric argument.
99 The column of a character is calculated by adding together the widths
100 as displayed of the previous characters in the line.
101 This function ignores line-continuation;
102 there is no upper limit on the column number a character can have
103 and horizontal scrolling has no effect.
105 If specified column is within a character, point goes after that character.
106 If it's past end of line, point goes to end of line.
108 Optional second argument FORCE non-nil means if COLUMN is in the
109 middle of a tab character, change it to spaces.
110 In addition, if FORCE is t, and the line is too short to reach
111 COLUMN, add spaces/tabs to get there.
113 The return value is the current column."
114 (let* ((buffer (current-buffer))
115 (col (current-column))
116 (pos (point buffer))
117 (pos-aref (buffer-char-to-aref buffer pos))
118 (end (zv buffer)))
119 ;; If we're starting past the desired column, back up to beginning
120 ;; of line and scan from there.
121 (when (> col column)
122 (setf end pos
123 pos (buffer-beginning-of-line)
124 pos-aref (buffer-char-to-aref buffer pos)
125 col 0))
126 ;; FIXME: this assumes each character is 1 column
127 (while (and (< col column)
128 (< pos end))
129 (let ((c (buffer-fetch-char pos-aref buffer)))
130 (when (char= c #\Newline)
131 (return nil))
132 (incf col)
133 (inc-both pos pos-aref buffer)))
134 (set-point pos buffer)
135 (when (and force
136 (< col column))
137 (setf col column)
138 (indent-to col))
139 col))
141 (defcommand indent-relative ((&optional unindented-ok)
142 :raw-prefix)
143 "Space out to under next indent point in previous nonblank line.
144 An indent point is a non-whitespace character following whitespace.
145 The following line shows the indentation points in this line.
146 ^ ^ ^ ^ ^ ^ ^ ^ ^
147 If the previous nonblank line has no indent points beyond the
148 column point starts at, `tab-to-tab-stop' is done instead, unless
149 this command is invoked with a numeric argument, in which case it
150 does nothing.
152 See also `indent-relative-maybe'."
153 ;; FIXME: abbrev mode
154 ;; (if (and abbrev-mode
155 ;; (eq (char-syntax (preceding-char)) :word-constituent))
156 ;; (expand-abbrev))
157 (let ((start-column (current-column))
158 indent)
159 (save-excursion
160 (beginning-of-line)
161 (if (re-search-backward "\\n[^\\n]" :error nil) ;; XXX used to be "^[^\n]"
162 (let ((end (save-excursion (forward-line 1) (point))))
163 (move-to-column start-column)
164 ;; Is start-column inside a tab on this line?
165 (if (> (current-column) start-column)
166 (backward-char 1))
167 (or (looking-at "[ \\t]")
168 unindented-ok
169 (skip-chars-forward (coerce '(#\^ #\Space #\Tab) 'string) end))
170 (skip-whitespace-forward end)
171 (or (= (point) end) (setq indent (current-column))))))
172 (if indent
173 (let ((opoint (point-marker)))
174 (indent-to indent 0)
175 (if (> opoint (point))
176 (goto-char opoint))
177 (set-marker opoint nil))
178 ;; FIXME: implement and uncomment
179 ;; (tab-to-tab-stop)
182 (defvar *indent-region-function* 'lisp-indent-region
183 "Short cut function to indent region using `indent-according-to-mode'.
184 A value of nil means really run `indent-according-to-mode' on each line.")
186 (defcommand indent-region ((start end &optional column)
187 :region-beginning :region-end :raw-prefix)
188 "Indent each nonblank line in the region.
189 A numeric prefix argument specifies a column: indent each line to that column.
191 With no prefix argument, the command chooses one of these methods and
192 indents all the lines with it:
194 1) If `fill-prefix' is non-nil, insert `fill-prefix' at the
195 beginning of each line in the region that does not already begin
196 with it.
197 2) If `indent-region-function' is non-nil, call that function
198 to indent the region.
199 3) Indent each line as specified by the variable `indent-line-function'.
201 Called from a program, START and END specify the region to indent.
202 If the third argument COLUMN is an integer, it specifies the
203 column to indent to; if it is nil, use one of the three methods above."
204 (message "guuh ~s ~s" column *prefix-arg*)
206 (if (null column)
207 (if *fill-prefix*
208 (save-excursion
209 (goto-char end)
210 (setq end (point-marker))
211 (goto-char start)
212 (let ((regexp (regexp-quote *fill-prefix*)))
213 (while (< (point) (marker-position end))
214 (or (looking-at regexp)
215 (and (bolp) (eolp))
216 (insert *fill-prefix*))
217 (forward-line 1))))
218 (if *indent-region-function*
219 (funcall *indent-region-function* start end)
220 (save-excursion
221 (setq end (copy-marker end))
222 (goto-char start)
223 (while (< (point) (marker-position end))
224 (or (and (bolp) (eolp))
225 (funcall *indent-line-function*))
226 (forward-line 1))
227 (set-marker end nil))))
228 (progn
229 (setq column (prefix-numeric-value column))
230 (save-excursion
231 (goto-char end)
232 (setq end (point-marker))
233 (goto-char start)
234 (or (bolp) (forward-line 1))
235 (while (< (point) (marker-position end))
236 (delete-region (point) (progn (skip-whitespace-forward) (point)))
237 (or (eolp)
238 (indent-to column 0))
239 (forward-line 1))
240 (set-marker end nil)))))
242 (defun vertical-motion (lines &optional (window (selected-window)))
243 "Move point to start of the screen line LINES lines down.
244 If LINES is negative, this means moving up.
246 This function is an ordinary cursor motion function
247 which calculates the new position based on how text would be displayed.
248 The new position may be the start of a line,
249 or just the start of a continuation line.
250 The function returns number of screen lines moved over;
251 that usually equals LINES, but may be closer to zero
252 if beginning or end of buffer was reached.
254 The optional second argument WINDOW specifies the window to use for
255 parameters such as width, horizontal scrolling, and so on.
256 The default is to use the selected window's parameters.
258 `vertical-motion' always uses the current buffer,
259 regardless of which buffer is displayed in WINDOW.
260 This is consistent with other cursor motion functions
261 and makes it possible to use `vertical-motion' in any buffer,
262 whether or not it is currently displayed in some window."
263 (declare (ignore lines window))
264 ;; FIXME: its cheap but it works, for now. It all assumes there
265 ;; aren't pictures or variable width fonts, etc.
266 (let* ((total lines)
267 (old-pt (pt))
268 (win (selected-window))
269 (width (window-width win nil))
270 (buf (current-buffer)))
271 ;; go to the beginning of the line
272 (decf old-pt (mod (current-column) width))
273 (while (and (< old-pt (zv))
274 (> lines 0))
275 (setf old-pt (1+ (buffer-scan-newline buf old-pt (+ old-pt width) 1)))
276 (decf lines))
277 (while (and (> old-pt (begv))
278 (< lines 0))
279 (setf old-pt (buffer-scan-newline buf old-pt (- old-pt width) -2))
280 ;; go past the newline except at the beginning of the buffer
281 (unless (= old-pt (begv))
282 (incf old-pt))
283 (incf lines))
284 (set-point (max (begv) (min (zv) old-pt)))
285 (- total lines)))
287 (defun indent-line-to (column)
288 "Indent current line to COLUMN.
289 This function removes or adds spaces and tabs at beginning of line
290 only if necessary. It leaves point at end of indentation."
291 (back-to-indentation)
292 (let ((cur-col (current-column)))
293 (cond ((< cur-col column)
294 (if (>= (- column (* (/ cur-col tab-width) tab-width)) tab-width)
295 (delete-region (point)
296 (progn (skip-chars-backward " ") (point))))
297 (indent-to column))
298 ((> cur-col column) ; too far right (after tab?)
299 (delete-region (progn (move-to-column column t) (point))
300 (progn (back-to-indentation) (point)))))))
302 (defun current-left-margin ()
303 "Return the left margin to use for this line.
304 This is the value of the buffer-local variable `left-margin' plus the value
305 of the `left-margin' text-property at the start of the line."
306 (save-excursion
307 (back-to-indentation)
308 (max 0
309 (+ left-margin (or (get-text-property
310 (if (and (eobp) (not (bobp)))
311 (1- (point)) (point))
312 'left-margin) 0)))))
314 (defcommand move-to-left-margin ((&optional (n 1) (force t))
315 :prefix)
316 "Move to the left margin of the current line.
317 With optional argument, move forward N-1 lines first.
318 The column moved to is the one given by the `current-left-margin' function.
319 If the line's indentation appears to be wrong, and this command is called
320 interactively or with optional argument FORCE, it will be fixed."
321 ;;(interactive (list (prefix-numeric-value current-prefix-arg) t))
322 (check-type n integer)
323 (beginning-of-line n)
324 (skip-chars-forward " \t")
325 (if (minibufferp (current-buffer))
326 (if (save-excursion (beginning-of-line) (bobp))
327 (goto-char (minibuffer-prompt-end))
328 (beginning-of-line))
329 (let ((lm (current-left-margin))
330 (cc (current-column)))
331 (cond ((> cc lm)
332 (if (> (move-to-column lm force) lm)
333 ;; If lm is in a tab and we are not forcing, move before tab
334 (backward-char 1)))
335 ((and force (< cc lm))
336 (indent-to-left-margin))))))
338 ;; This used to be the default indent-line-function,
339 ;; used in Fundamental Mode, Text Mode, etc.
340 (defun indent-to-left-margin ()
341 "Indent current line to the column given by `current-left-margin'."
342 (indent-line-to (current-left-margin)))
344 (defcommand beginning-of-line-text ((&optional n)
345 :prefix)
346 "Move to the beginning of the text on this line.
347 With optional argument, move forward N-1 lines first.
348 From the beginning of the line, moves past the left-margin indentation, the
349 fill-prefix, and any indentation used for centering or right-justifying the
350 line, but does not move past any whitespace that was explicitly inserted
351 \(such as a tab used to indent the first line of a paragraph)."
352 (beginning-of-line n)
353 (skip-chars-forward " \t")
354 ;; Skip over fill-prefix.
355 (if (and *fill-prefix*
356 (not (string-equal *fill-prefix* "")))
357 (if (equal *fill-prefix*
358 (buffer-substring
359 (point) (min (point-max) (+ (length *fill-prefix*) (point)))))
360 (forward-char (length *fill-prefix*)))
361 (if (and adaptive-fill-mode adaptive-fill-regexp
362 (looking-at adaptive-fill-regexp))
363 (goto-char (match-end 0))))
364 ;; Skip centering or flushright indentation
365 (if (memq (current-justification) '(center right))
366 (skip-chars-forward " \t")))