46f322fbc658aec014f724fda5f969b8b9055923
[lice.git] / indent.lisp
blob46f322fbc658aec014f724fda5f969b8b9055923
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 (point 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 (point)))
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 ;; FIXME: this assumes each character is 1 column
116 (while (and (< col column)
117 (< pos end))
118 (let ((c (buffer-fetch-char pos-aref buffer)))
119 (when (char= c #\Newline)
120 (return nil))
121 (incf col)
122 (inc-both pos pos-aref buffer)))
123 (set-point pos buffer)
124 (when (and force
125 (< col column))
126 (setf col column)
127 (indent-to col))
128 col))
130 (defcommand indent-relative ((&optional unindented-ok)
131 :raw-prefix)
132 "Space out to under next indent point in previous nonblank line.
133 An indent point is a non-whitespace character following whitespace.
134 The following line shows the indentation points in this line.
135 ^ ^ ^ ^ ^ ^ ^ ^ ^
136 If the previous nonblank line has no indent points beyond the
137 column point starts at, `tab-to-tab-stop' is done instead, unless
138 this command is invoked with a numeric argument, in which case it
139 does nothing.
141 See also `indent-relative-maybe'."
142 ;; FIXME: abbrev mode
143 ;; (if (and abbrev-mode
144 ;; (eq (char-syntax (preceding-char)) :word-constituent))
145 ;; (expand-abbrev))
146 (let ((start-column (current-column))
147 indent)
148 (save-excursion
149 (beginning-of-line)
150 (if (re-search-backward "\\n[^\\n]" :error nil) ;; XXX used to be "^[^\n]"
151 (let ((end (save-excursion (forward-line 1) (point))))
152 (move-to-column start-column)
153 ;; Is start-column inside a tab on this line?
154 (if (> (current-column) start-column)
155 (backward-char 1))
156 (or (looking-at "[ \\t]")
157 unindented-ok
158 (skip-chars-forward (coerce '(#\^ #\Space #\Tab) 'string) end))
159 (skip-whitespace-forward end)
160 (or (= (point) end) (setq indent (current-column))))))
161 (if indent
162 (let ((opoint (point-marker)))
163 (indent-to indent 0)
164 (if (> opoint (point))
165 (goto-char opoint))
166 (set-marker opoint nil))
167 ;; FIXME: implement and uncomment
168 ;; (tab-to-tab-stop)
171 (defvar *indent-region-function* 'lisp-indent-region
172 "Short cut function to indent region using `indent-according-to-mode'.
173 A value of nil means really run `indent-according-to-mode' on each line.")
175 (defcommand indent-region ((start end &optional column)
176 :region-beginning :region-end :raw-prefix)
177 "Indent each nonblank line in the region.
178 A numeric prefix argument specifies a column: indent each line to that column.
180 With no prefix argument, the command chooses one of these methods and
181 indents all the lines with it:
183 1) If `fill-prefix' is non-nil, insert `fill-prefix' at the
184 beginning of each line in the region that does not already begin
185 with it.
186 2) If `indent-region-function' is non-nil, call that function
187 to indent the region.
188 3) Indent each line as specified by the variable `indent-line-function'.
190 Called from a program, START and END specify the region to indent.
191 If the third argument COLUMN is an integer, it specifies the
192 column to indent to; if it is nil, use one of the three methods above."
193 (message "guuh ~s ~s" column *prefix-arg*)
195 (if (null column)
196 (if *fill-prefix*
197 (save-excursion
198 (goto-char end)
199 (setq end (point-marker))
200 (goto-char start)
201 (let ((regexp (regexp-quote *fill-prefix*)))
202 (while (< (point) (marker-position end))
203 (or (looking-at regexp)
204 (and (bolp) (eolp))
205 (insert *fill-prefix*))
206 (forward-line 1))))
207 (if *indent-region-function*
208 (funcall *indent-region-function* start end)
209 (save-excursion
210 (setq end (copy-marker end))
211 (goto-char start)
212 (while (< (point) (marker-position end))
213 (or (and (bolp) (eolp))
214 (funcall *indent-line-function*))
215 (forward-line 1))
216 (set-marker end nil))))
217 (progn
218 (setq column (prefix-numeric-value column))
219 (save-excursion
220 (goto-char end)
221 (setq end (point-marker))
222 (goto-char start)
223 (or (bolp) (forward-line 1))
224 (while (< (point) (marker-position end))
225 (delete-region (point) (progn (skip-whitespace-forward) (point)))
226 (or (eolp)
227 (indent-to column 0))
228 (forward-line 1))
229 (set-marker end nil)))))
231 (defun vertical-motion (lines &optional (window (selected-window)))
232 "Move point to start of the screen line LINES lines down.
233 If LINES is negative, this means moving up.
235 This function is an ordinary cursor motion function
236 which calculates the new position based on how text would be displayed.
237 The new position may be the start of a line,
238 or just the start of a continuation line.
239 The function returns number of screen lines moved over;
240 that usually equals LINES, but may be closer to zero
241 if beginning or end of buffer was reached.
243 The optional second argument WINDOW specifies the window to use for
244 parameters such as width, horizontal scrolling, and so on.
245 The default is to use the selected window's parameters.
247 `vertical-motion' always uses the current buffer,
248 regardless of which buffer is displayed in WINDOW.
249 This is consistent with other cursor motion functions
250 and makes it possible to use `vertical-motion' in any buffer,
251 whether or not it is currently displayed in some window."
252 (declare (ignore lines window))
253 (error "unimplemented")
256 (defun indent-line-to (column)
257 "Indent current line to COLUMN.
258 This function removes or adds spaces and tabs at beginning of line
259 only if necessary. It leaves point at end of indentation."
260 (back-to-indentation)
261 (let ((cur-col (current-column)))
262 (cond ((< cur-col column)
263 (if (>= (- column (* (/ cur-col tab-width) tab-width)) tab-width)
264 (delete-region (point)
265 (progn (skip-chars-backward " ") (point))))
266 (indent-to column))
267 ((> cur-col column) ; too far right (after tab?)
268 (delete-region (progn (move-to-column column t) (point))
269 (progn (back-to-indentation) (point)))))))
271 (defun current-left-margin ()
272 "Return the left margin to use for this line.
273 This is the value of the buffer-local variable `left-margin' plus the value
274 of the `left-margin' text-property at the start of the line."
275 (save-excursion
276 (back-to-indentation)
277 (max 0
278 (+ left-margin (or (get-text-property
279 (if (and (eobp) (not (bobp)))
280 (1- (point)) (point))
281 'left-margin) 0)))))
283 (defcommand move-to-left-margin ((&optional (n 1) (force t))
284 :prefix)
285 "Move to the left margin of the current line.
286 With optional argument, move forward N-1 lines first.
287 The column moved to is the one given by the `current-left-margin' function.
288 If the line's indentation appears to be wrong, and this command is called
289 interactively or with optional argument FORCE, it will be fixed."
290 ;;(interactive (list (prefix-numeric-value current-prefix-arg) t))
291 (check-type n integer)
292 (beginning-of-line n)
293 (skip-chars-forward " \t")
294 (if (minibufferp (current-buffer))
295 (if (save-excursion (beginning-of-line) (bobp))
296 (goto-char (minibuffer-prompt-end))
297 (beginning-of-line))
298 (let ((lm (current-left-margin))
299 (cc (current-column)))
300 (cond ((> cc lm)
301 (if (> (move-to-column lm force) lm)
302 ;; If lm is in a tab and we are not forcing, move before tab
303 (backward-char 1)))
304 ((and force (< cc lm))
305 (indent-to-left-margin))))))
307 ;; This used to be the default indent-line-function,
308 ;; used in Fundamental Mode, Text Mode, etc.
309 (defun indent-to-left-margin ()
310 "Indent current line to the column given by `current-left-margin'."
311 (indent-line-to (current-left-margin)))