[lice @ massive rearrangement to get rid of compiler warnings and mimic the file...
[lice.git] / indent.lisp
blob691747138d9dc54befd6a085098c4625548deee3
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 (error "unimplemented")
263 (defun indent-line-to (column)
264 "Indent current line to COLUMN.
265 This function removes or adds spaces and tabs at beginning of line
266 only if necessary. It leaves point at end of indentation."
267 (back-to-indentation)
268 (let ((cur-col (current-column)))
269 (cond ((< cur-col column)
270 (if (>= (- column (* (/ cur-col tab-width) tab-width)) tab-width)
271 (delete-region (point)
272 (progn (skip-chars-backward " ") (point))))
273 (indent-to column))
274 ((> cur-col column) ; too far right (after tab?)
275 (delete-region (progn (move-to-column column t) (point))
276 (progn (back-to-indentation) (point)))))))
278 (defun current-left-margin ()
279 "Return the left margin to use for this line.
280 This is the value of the buffer-local variable `left-margin' plus the value
281 of the `left-margin' text-property at the start of the line."
282 (save-excursion
283 (back-to-indentation)
284 (max 0
285 (+ left-margin (or (get-text-property
286 (if (and (eobp) (not (bobp)))
287 (1- (point)) (point))
288 'left-margin) 0)))))
290 (defcommand move-to-left-margin ((&optional (n 1) (force t))
291 :prefix)
292 "Move to the left margin of the current line.
293 With optional argument, move forward N-1 lines first.
294 The column moved to is the one given by the `current-left-margin' function.
295 If the line's indentation appears to be wrong, and this command is called
296 interactively or with optional argument FORCE, it will be fixed."
297 ;;(interactive (list (prefix-numeric-value current-prefix-arg) t))
298 (check-type n integer)
299 (beginning-of-line n)
300 (skip-chars-forward " \t")
301 (if (minibufferp (current-buffer))
302 (if (save-excursion (beginning-of-line) (bobp))
303 (goto-char (minibuffer-prompt-end))
304 (beginning-of-line))
305 (let ((lm (current-left-margin))
306 (cc (current-column)))
307 (cond ((> cc lm)
308 (if (> (move-to-column lm force) lm)
309 ;; If lm is in a tab and we are not forcing, move before tab
310 (backward-char 1)))
311 ((and force (< cc lm))
312 (indent-to-left-margin))))))
314 ;; This used to be the default indent-line-function,
315 ;; used in Fundamental Mode, Text Mode, etc.
316 (defun indent-to-left-margin ()
317 "Indent current line to the column given by `current-left-margin'."
318 (indent-line-to (current-left-margin)))