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