Initial Commit
[packages] / xemacs-packages / ilisp / ilisp-ext.el
1 ;;; -*- Mode: Emacs-Lisp -*-
2
3 ;;; ilisp-ext.el --
4 ;;;
5 ;;; This file is part of ILISP.
6 ;;; Please refer to the file COPYING for copyrights and licensing
7 ;;; information.
8 ;;; Please refer to the file ACKNOWLEGDEMENTS for an (incomplete) list
9 ;;; of present and past contributors.
10 ;;;
11 ;;; $Id: ilisp-ext.el,v 1.3 2001-07-02 09:40:46 youngs Exp $
12
13 ;;; Lisp mode extensions from the ILISP package.
14 ;;; Copyright (C) 1990, 1991, 1992 Chris McConnell, ccm@cs.cmu.edu.
15
16 ;;; This file may become part of GNU Emacs.
17
18 ;;; GNU Emacs is distributed in the hope that it will be useful,
19 ;;; but WITHOUT ANY WARRANTY.  No author or distributor
20 ;;; accepts responsibility to anyone for the consequences of using it
21 ;;; or for whether it serves any particular purpose or works at all,
22 ;;; unless he says so in writing.  Refer to the GNU Emacs General Public
23 ;;; License for full details.
24
25 ;;; Everyone is granted permission to copy, modify and redistribute
26 ;;; GNU Emacs, but only under the conditions described in the
27 ;;; GNU Emacs General Public License.   A copy of this license is
28 ;;; supposed to have been given to you along with GNU Emacs so you
29 ;;; can know your rights and responsibilities.  It should be in a
30 ;;; file named COPYING.  Among other things, the copyright notice
31 ;;; and this notice must be preserved on all copies.
32
33 ;;; When loaded this file adds new functionality to emacs lisp mode
34 ;;; and lisp mode. 
35 ;;; 
36 ;;; Default bindings:
37 ;;;
38 ;;; M-x find-unbalanced-lisp find unbalanced parens in the current
39 ;;; buffer.  With a prefix in the current region. 
40 ;;;
41 ;;; ] Close all open parentheses back to the start of the containing
42 ;;; sexp, or to a previous left bracket which will be converted to a
43 ;;; left paren.
44 ;;;
45 ;;; M-q Reindent comments or strings in paragraph chunks or reindent
46 ;;; the containing sexp.
47 ;;;
48 ;;; M-x comment-region-lisp inserts prefix copies of the comment-start
49 ;;; character before lines in the region and the comment-end character
50 ;;; at the end of each line.  If called with a negative prefix, that
51 ;;; many copies are removed.
52 ;;;
53 ;;; C-M-r repositions the first line of the current defun to the top
54 ;;; of the current window.
55 ;;;
56 ;;; C-M-l switches the current window to the previously seen buffer.
57 ;;;
58 ;;; EXAMPLE .emacs:
59 ;;;
60 ;;; (setq ilisp-ext-load-hook 
61 ;;;   '(lambda () (define-key global-map "\C-\M-l" 'previous-buffer-lisp)))
62 ;;; (require 'ilisp-ext)
63
64 ;;;%Syntax
65 ;;; This makes it so that .'s are treated as normal characters so that
66 ;;; 3.141 gets treated as a single lisp token.  This does cause dotted
67 ;;; pairs to be treated weird though.
68 (modify-syntax-entry ?. "_" lisp-mode-syntax-table)
69
70 ;;; Brackets match
71 (modify-syntax-entry ?\[ "(]" lisp-mode-syntax-table)
72 (modify-syntax-entry ?\] ")[" lisp-mode-syntax-table)
73
74
75
76 ;;;%Superbrackets
77 (defun close-all-lisp (arg)
78   "Unless you are in a string, insert right parentheses as necessary
79 to balance unmatched left parentheses back to the start of the current
80 defun or to a previous left bracket which is then replaced with a left
81 parentheses.  If there are too many right parentheses, remove them
82 unless there is text after the extra right parentheses.  If called
83 with a prefix, the entire expression will be closed and all open left
84 brackets will be replaced with left parentheses."
85   (interactive "P")
86   (let* ((point (point))
87          (begin (lisp-defun-begin))
88          (end (lisp-end-defun-text))
89          inserted
90          (closed nil))
91     (goto-char point)
92     (if (or (car (cdr (cdr (lisp-in-string begin end))))
93             (save-excursion (beginning-of-line)
94                             (looking-at "[ \t]*;")))
95         (insert "]")
96         (if (= begin end)
97             (error "No sexp to close.")
98             (save-restriction
99               (narrow-to-region begin end)
100               (if (< point begin) 
101                   (setq point begin)
102                   (if (> point end)
103                       (setq point end)))
104               ;; Add parens at point until either the defun is closed, or we
105               ;; hit a square bracket.
106               (goto-char point)
107               (insert ?\))              ;So we have an sexp
108               (while (progn
109                        (setq inserted (point))
110                        (condition-case () 
111                            (progn (backward-sexp)
112                                   (or arg 
113                                       (not (eq (char-after (point)) ?\[))))
114                          (error (setq closed t) nil)))
115                 ;; With an arg replace all left brackets
116                 (if (and arg (= (char-after (point)) ?\[))
117                     (progn
118                       (delete-char 1)
119                       (insert ?\()
120                       (backward-char)))
121                 (forward-sexp)
122                 (insert ?\)))
123               (if (< (point) point)
124                   ;; We are at a left bracket
125                   (let ((left (point)))
126                     (delete-char 1)
127                     (insert ?\()
128                     (backward-char)
129                     (forward-sexp))
130                   ;; There was not an open left bracket so close at end
131                   (delete-region point inserted)
132                   (goto-char begin)
133                   (if (condition-case () (progn
134                                            (forward-sexp)
135                                            (<= (point) end))
136                         (error nil))
137                       ;; Delete extra right parens
138                       (let ((point (point)))
139                         (skip-chars-forward " \t)\n")
140                         (if (or (bolp) (eobp))
141                             (progn
142                               (skip-chars-backward " \t\n")
143                               (delete-region point (point)))
144                             (error
145                              "There is text after the last right parentheses.")))
146                       ;; Insert parens at end changing any left brackets
147                       (goto-char end)
148                       (while 
149                           (progn
150                             (insert ?\))
151                             (save-excursion
152                               (condition-case ()
153                                   (progn (backward-sexp)
154                                          (if (= (char-after (point)) ?\[)
155                                              (progn
156                                                (delete-char 1)
157                                                (insert ?\()
158                                                (backward-char)))
159                                          (> (point) begin))
160                                 (error (delete-backward-char 1)
161                                        nil))))))))))))
162
163 ;;;%Reindentation
164
165 ;;;
166 (defun reindent-lisp ()
167   "Indents code depending partially on context (comments or strings).
168 If in a comment, indent the comment paragraph bounded by
169 non-comments, blank lines or empty comment lines.  If in a string,
170 indent the paragraph bounded by string delimiters or blank lines.
171 Otherwise go to the containing defun, close it and reindent the code
172 block."
173   (interactive)
174   (let ((region (lisp-in-string))
175         (comment (concat "[ \t]*" comment-start "+[ \t]*")))
176     (set-marker lisp-fill-marker (point))
177     (back-to-indentation)
178     (cond (region
179            (or (= (char-after (point)) ?\")
180                (and (< (point) (car region)) (goto-char (car region)))
181                (re-search-backward "^$" (car region) 'end))
182            (let ((begin (point))
183                  (end (car (cdr region)))
184                  (fill-prefix nil))
185              (forward-char)
186              (re-search-forward "^$" end 'end)
187              (if (= (point) end)
188                  (progn (skip-chars-forward "^\n")
189                         (if (not (eobp)) (forward-char))))
190              (fill-region-as-paragraph begin (point))))
191           ((looking-at comment)
192            (let ((fill-prefix
193                   (buffer-substring
194                    (progn (beginning-of-line) (point))
195                    (match-end 0))))
196              (while (and (not (bobp)) (lisp-in-comment comment))
197                (forward-line -1))
198              (if (not (bobp)) (forward-line 1))
199              (let ((begin (point)))
200                (while (and (lisp-in-comment comment) (not (eobp)))
201                  (replace-match fill-prefix)
202                  (forward-line 1))
203                (if (not (eobp))
204                    (progn (forward-line -1)
205                           (end-of-line)
206                           (forward-char 1)))
207                (fill-region-as-paragraph begin (point)))))
208           (t
209            (goto-char lisp-fill-marker)
210            (close-all-lisp 1)
211            (lisp-defun-begin)
212            (indent-sexp-ilisp)))
213   (goto-char lisp-fill-marker)
214   (set-marker lisp-fill-marker nil)
215   (message "Done")))
216
217 ;;;%Comment region
218 (defun comment-region-lisp (start end prefix)
219   "If prefix is positive, insert prefix copies of comment-start at the
220 start and comment-end at the end of each line in region.  If prefix is
221 negative, remove all comment-start and comment-end strings from the
222 region."
223   (interactive "r\np")
224   (save-excursion
225     (goto-char end)
226     (if (and (not (= start end)) (bolp)) (setq end (1- end)))
227     (goto-char end)
228     (beginning-of-line)
229     (set-marker ilisp-comment-marker (point))
230     (untabify start end)
231     (goto-char start)
232     (beginning-of-line)
233     (let* ((count 1)
234            (comment comment-start)
235            (comment-end (if (not (equal comment-end "")) comment-end)))
236       (if (> prefix 0)
237           (progn
238             (while (< count prefix)
239               (setq comment (concat comment-start comment)
240                     count (1+ count)))
241             (while (<= (point) ilisp-comment-marker)
242               (beginning-of-line)
243               (insert comment)
244               (if comment-end (progn (end-of-line) (insert comment-end)))
245               (forward-line 1)))
246           (setq comment (concat comment "+"))
247           (while (<= (point) ilisp-comment-marker)
248             (back-to-indentation)
249             (if (looking-at comment) (replace-match ""))
250             (if comment-end
251                 (progn
252                   (re-search-backward comment-end)
253                   (replace-match "")))
254             (forward-line 1)))
255       (set-marker ilisp-comment-marker nil))))
256
257 ;;;%Movement
258 ;;; beginning-of-defun-lisp and end-of-defun-lisp are overloaded by ilisp.el
259 (defun beginning-of-defun-lisp (&optional stay)
260   "Go to the next left paren that starts at the left margin."
261   (interactive)
262   (beginning-of-defun))
263
264 ;;;
265 (defun end-of-defun-lisp ()
266   "Go to the next left paren that starts at the left margin."
267   (interactive)
268   (let ((point (point)))
269     (beginning-of-line)
270     (re-search-forward "^[ \t\n]*[^; \t\n]" nil t)
271     (back-to-indentation)
272     (if (not (bolp)) (beginning-of-defun-lisp t))
273     (lisp-end-defun-text t)
274     (if (= point (point))               ;Already at end so move to next end
275         (lisp-skip (point-max))
276         (if (not (or (eobp)
277                      (= (char-after (point)) ?\n)))
278             (lisp-end-defun-text t)))))
279
280 ;;;%%Reposition-window
281 (defun count-screen-lines-lisp (start end)
282   "Return the number of screen lines between start and end."
283   (save-excursion
284     (save-restriction
285       (narrow-to-region start end)
286       (goto-char (point-min))
287       (vertical-motion (- (point-max) (point-min))))))
288
289 ;;;
290 (defun count-screen-lines-signed-lisp (start end)
291   "Return number of screen lines between START and END; returns a negative
292 number if END precedes START."
293   (interactive "r")
294   (let ((lines (count-screen-lines-lisp start end)))
295     (if (< start end) lines (- lines))))
296
297 ;;; This was written by Michael D. Ernst
298 (defun reposition-window-lisp (&optional arg)
299   "Make the current definition and/or comment visible, move it to the
300 top of the window, or toggle the visibility of comments that precede
301 it.  Leaves point unchanged unless supplied with prefix ARG.  If the
302 definition is fully onscreen, it is moved to the top of the window.
303 If it is partly offscreen, the window is scrolled to get the
304 definition \(or as much as will fit) onscreen, unless point is in a
305 comment which is also partly offscreen, in which case the scrolling
306 attempts to get as much of the comment onscreen as possible.
307 Initially reposition-window attempts to make both the definition and
308 preceding comments visible.  Further invocations toggle the visibility
309 of the comment lines.  If ARG is non-nil, point may move in order to
310 make the whole defun visible \(if only part could otherwise be made
311 so), to make the defun line visible \(if point is in code and it could
312 not be made so, or if only comments, including the first comment line,
313 are visible), or to make the first comment line visible \(if point is
314 in a comment)."
315   (interactive "P")
316   (let* ((here (point))
317          ;; change this name once I've gotten rid of references to ht.
318          ;; this is actually the number of the last screen line
319          (ht (- (window-height (selected-window)) 2))
320          (line (count-screen-lines-lisp (window-start) (point)))
321          (comment-height
322           ;; The max deals with the case of cursor between defuns.
323           (max 0
324                (count-screen-lines-signed-lisp
325                 ;; the beginning of the preceding comment
326                 (save-excursion
327                   (if (not (and (bolp) (eq (char-after (point)) ?\()))
328                       (beginning-of-defun-lisp))
329                   (beginning-of-defun-lisp)
330                   (end-of-defun-lisp)
331                   ;; Skip whitespace, newlines, and form feeds.
332                   (re-search-forward "[^\\s \n\014]")
333                   (backward-char 1)
334                   (point))
335                 here)))
336          (defun-height 
337              (count-screen-lines-signed-lisp
338               (save-excursion
339                (end-of-defun-lisp)      ;associate comment with next defun 
340                (beginning-of-defun-lisp)
341                (point))
342               here))
343          ;; This must be positive, so don't use the signed version.
344          (defun-depth
345              (count-screen-lines-lisp
346               here
347               (save-excursion (end-of-defun-lisp) (point))))
348          (defun-line-onscreen-p
349              (and (<= defun-height line) (<= (- line defun-height) ht))))
350     (cond ((or (= comment-height line)
351                (and (= line ht)
352                     (> comment-height line)
353                     ;; if defun line offscreen, we should be in case 4
354                     defun-line-onscreen-p))
355            ;; Either first comment line is at top of screen or (point at
356            ;; bottom of screen, defun line onscreen, and first comment line
357            ;; off top of screen).  That is, it looks like we just did
358            ;; recenter-definition, trying to fit as much of the comment
359            ;; onscreen as possible.  Put defun line at top of screen; that
360            ;; is, show as much code, and as few comments, as possible.
361            (if (and arg (> defun-depth (1+ ht)))
362                ;; Can't fit whole defun onscreen without moving point.
363                (progn (end-of-defun-lisp) (beginning-of-defun-lisp)
364                       (recenter 0))
365                (recenter (max defun-height 0))))
366           ((or (= defun-height line)
367                (= line 0)
368                (and (< line comment-height)
369                     (< defun-height 0)))
370            ;; Defun line or cursor at top of screen, OR cursor in comment
371            ;; whose first line is offscreen.
372            ;; Avoid moving definition up even if defun runs offscreen;
373            ;; we care more about getting the comment onscreen.
374            (cond ((= line ht)
375                   ;; cursor on last screen line (and so in a comment)
376                   (if arg (progn (end-of-defun-lisp) 
377                                  (beginning-of-defun-lisp)))
378                   (recenter 0))
379                  ;; This condition, copied from case 4, may not be quite right
380                  ((and arg (< ht comment-height))
381                   ;; Can't get first comment line onscreen.
382                   ;; Go there and try again.
383                   (forward-line (- comment-height))
384                   (beginning-of-line)
385                   ;; was (reposition-window)
386                   (recenter 0))
387                  (t
388                   (recenter (min ht comment-height))))
389            ;; (recenter (min ht comment-height))
390            )
391           ((and (> (+ line defun-depth -1) ht)
392                 defun-line-onscreen-p)
393            ;; Defun runs off the bottom of the screen and the defun
394            ;; line is onscreen.  Move the defun up.
395            (recenter (max 0 (1+ (- ht defun-depth)) defun-height)))
396           (t
397            ;; If on the bottom line and comment start is offscreen
398            ;; then just move all comments offscreen, or at least as
399            ;; far as they'll go.  Try to get as much of the comments
400            ;; onscreen as possible.
401            (if (and arg (< ht comment-height))
402                ;; Can't get defun line onscreen; go there and try again.
403                (progn (forward-line (- defun-height))
404                       (beginning-of-line)
405                       (reposition-window-lisp))
406                (recenter (min ht comment-height)))))))
407
408 ;;;
409 (defun previous-buffer-lisp (n)
410   "Switch to Nth previously selected buffer.  N defaults to the number
411 of windows plus 1.  That is, no argument switches to the most recently
412 selected buffer that is not visible.  If N is 1, repeated calls will
413 cycle through all buffers; -1 cycles the other way.  If N is greater
414 than 1, the first N buffers on the buffer list are rotated."
415   (interactive "P")
416   (if (not n)
417       (switch-to-buffer nil)
418       (let ((buffer-list (buffer-list)))
419         (setq n (prefix-numeric-value n))
420         (cond ((= n 1)
421                (bury-buffer (current-buffer))
422                (setq n 2))
423               ((< n 0)
424                (setq buffer-list (nreverse buffer-list)
425                      n (- n)))
426               (t nil))
427         (while (and (> n 1) buffer-list)
428           (setq n (1- n)
429                 buffer-list (cdr buffer-list))
430           (while (eq (elt (buffer-name (car buffer-list)) 0) ? )
431             (setq buffer-list (cdr buffer-list))))
432         (if buffer-list
433             (switch-to-buffer (car buffer-list))
434             (error "There aren't that many buffers")))))
435
436 ;;;%Bindings
437 (define-key emacs-lisp-mode-map "\M-q"    'reindent-lisp)
438 (define-key emacs-lisp-mode-map "\M-\C-a" 'beginning-of-defun-lisp)
439 (define-key emacs-lisp-mode-map "\M-\C-e" 'end-of-defun-lisp)
440 (unless ilisp-*use-fsf-compliant-keybindings*
441   ;; FSF Emacs 20 has `reposition-window' bound to C-M-l
442   ;; and `isearch-backward-regexp' bound to C-M-r
443   (define-key emacs-lisp-mode-map "\C-\M-r" 'reposition-window-lisp))
444 (when ilisp-bindings-*bind-right-bracket-p*
445   (define-key emacs-lisp-mode-map "]"       'close-all-lisp))
446
447 (define-key lisp-mode-map       "\M-q"    'reindent-lisp)
448 (when ilisp-bindings-*bind-right-bracket-p*
449   (define-key lisp-mode-map       "]"       'close-all-lisp))
450 (unless ilisp-*use-fsf-compliant-keybindings*
451   ;; FSF Emacs 20 has `reposition-window' bound to C-M-l
452   ;; and `isearch-backward-regexp' bound to C-M-r
453   (define-key lisp-mode-map       "\C-\M-r" 'reposition-window-lisp)
454   (define-key global-map          "\M-\C-l" 'previous-buffer-lisp))
455
456 ;;;
457 (run-hooks 'ilisp-ext-load-hook)
458 (provide 'ilisp-ext)
459
460 ;;; end of file -- ilisp-ext.el --