Initial Commit
[packages] / xemacs-packages / haskell-mode / haskell-decl-scan.el
1 ;;; haskell-decl-scan.el --- Declaration scanning module for Haskell Mode
2
3 ;; Copyright (C) 2004, 2005  Free Software Foundation, Inc.
4 ;; Copyright (C) 1997-1998 Graeme E Moss
5
6 ;; Authors: 1997-1998 Graeme E Moss <gem@cs.york.ac.uk>
7 ;; Keywords: declarations menu files Haskell
8 ;; URL: http://cvs.haskell.org/cgi-bin/cvsweb.cgi/fptools/CONTRIB/haskell-modes/emacs/haskell-decl-scan.el?rev=HEAD
9
10 ;; This file is not part of GNU Emacs.
11
12 ;; This file is free software; you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation; either version 2, or (at your option)
15 ;; any later version.
16
17 ;; This file is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 ;; GNU General Public License for more details.
21
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
24 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25 ;; Boston, MA 02111-1307, USA.
26
27 \f
28 ;;; Commentary:
29
30 ;; Purpose:
31 ;;
32 ;; Top-level declarations are scanned and placed in a menu.  Supports
33 ;; full Latin1 Haskell 1.4 as well as literate scripts.
34 ;;
35 ;;
36 ;; Installation:
37 ;; 
38 ;; To turn declaration scanning on for all Haskell buffers under the
39 ;; Haskell mode of Moss&Thorn, add this to .emacs:
40 ;;
41 ;;    (add-hook haskell-mode-hook 'turn-on-haskell-decl-scan)
42 ;;
43 ;; Otherwise, call `turn-on-haskell-decl-scan'.
44 ;;
45 ;;
46 ;; Customisation:
47 ;;
48 ;; None available so far.
49 ;;
50 ;;
51 ;; History:
52 ;;
53 ;; If you have any problems or suggestions, after consulting the list
54 ;; below, email gem@cs.york.ac.uk quoting the version of the library
55 ;; you are using, the version of Emacs you are using, and a small
56 ;; example of the problem or suggestion.  Note that this library
57 ;; requires a reasonably recent version of Emacs.
58 ;;
59 ;; Uses `imenu' under Emacs, and `func-menu' under XEmacs.
60 ;;
61 ;; Version 1.2:
62 ;;   Added support for LaTeX-style literate scripts.
63 ;;
64 ;; Version 1.1:
65 ;;   Use own syntax table.  Fixed bug for very small buffers.  Use
66 ;;   markers instead of pointers (markers move with the text).
67 ;;
68 ;; Version 1.0:
69 ;;   Brought over from Haskell mode v1.1.
70 ;;
71 ;;
72 ;; Present Limitations/Future Work (contributions are most welcome!):
73 ;;
74 ;; . Unicode is still a mystery...  has anyone used it yet?  We still
75 ;;   support Latin-ISO-8859-1 though (the character set of Haskell 1.3).
76 ;;
77 ;; . Declarations requiring information extending beyond starting line
78 ;;   don't get scanned properly, eg.
79 ;;   > class Eq a =>
80 ;;   >       Test a
81 ;;
82 ;; . Comments placed in the midst of the first few lexemes of a
83 ;;   declaration will cause havoc, eg.
84 ;;   > infixWithComments :: Int -> Int -> Int
85 ;;   > x {-nastyComment-} `infixWithComments` y = x + y
86 ;;   but are not worth worrying about.
87 ;;
88 ;; . Would be nice to scan other top-level declarations such as
89 ;;   methods of a class, datatype field labels...  any more?
90 ;;
91 ;; . Support for GreenCard?
92 ;;
93 ;; . Re-running (literate-)haskell-imenu should not cause the problems
94 ;;   that it does.  The ability to turn off scanning would also be
95 ;;   useful.  (Note that re-running (literate-)haskell-mode seems to
96 ;;   cause no problems.)
97 ;;
98 ;; . Inconsistency: we define the start of a declaration in `imenu' as
99 ;;   the start of the line the declaration starts on, but in
100 ;;   `func-menu' as the start of the name that the declaration is
101 ;;   given (eg. "class Eq a => Ord a ..." starts at "class" in `imenu'
102 ;;   but at "Ord" in `func-menu').  This avoids rescanning of the
103 ;;   buffer by the goto functions of `func-menu' but allows `imenu' to
104 ;;   have the better definition of the start of the declaration (IMO).
105 ;;
106 ;; . `func-menu' cannot cope well with spaces in declaration names.
107 ;;   This is unavoidable in "instance Eq Int" (changing the spaces to
108 ;;   underscores would cause rescans of the buffer).  Note though that
109 ;;   `fume-prompt-function-goto' (usually bound to "C-c g") does cope
110 ;;   with spaces okay.
111 ;;
112 ;; . Would like to extend the goto functions given by `func-menu'
113 ;;   under XEmacs to Emacs.  Would have to implement these
114 ;;   ourselves as `imenu' does not provide them.
115 ;;
116 ;; . `func-menu' uses its own syntax table when grabbing a declaration
117 ;;   name to lookup (why doesn't it use the syntax table of the
118 ;;   buffer?) so some declaration names will not be grabbed correctly,
119 ;;   eg. "fib'" will be grabbed as "fib" since "'" is not a word or
120 ;;   symbol constituent under the syntax table `func-menu' uses.
121
122 ;; All functions/variables start with
123 ;; `(turn-(on/off)-)haskell-decl-scan' or `haskell-ds-'.
124
125 ;; The imenu support is based on code taken from `hugs-mode',
126 ;; thanks go to Chris Van Humbeeck.
127
128 ;; Version.
129
130 ;;; Code:
131
132 (require 'haskell-mode)
133
134 (defconst haskell-decl-scan-version "1.9"
135   "Version number of haskell-decl-scan.")
136 (defun haskell-decl-scan-version ()
137   "Echo the current version of haskell-decl-scan in the minibuffer."
138   (interactive)
139   (message "Using haskell-decl-scan version %s" haskell-decl-scan-version))
140
141 ;;###autoload
142 ;; As `cl' defines macros that `imenu' uses, we must require them at
143 ;; compile time.
144 (eval-when-compile
145   (require 'cl)
146   (condition-case nil
147       (require 'imenu)
148     (error nil)))
149
150 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
151 ;; General declaration scanning functions.
152
153 (defalias 'haskell-ds-match-string
154   (if (fboundp 'match-string-no-properties)
155       'match-string-no-properties
156     (lambda (num)
157       "As `match-string' except that the string is stripped of properties."
158       (format "%s" (match-string num)))))
159
160 (defvar haskell-ds-start-keywords-re
161   (concat "\\(\\<"
162           "class\\|data\\|i\\(mport\\|n\\(fix\\(\\|[lr]\\)\\|stance\\)\\)\\|"
163           "module\\|primitive\\|type\\|newtype"
164           "\\)\\>")
165   "Keywords that may start a declaration.")
166
167 (defvar haskell-ds-syntax-table
168   (let ((table (copy-syntax-table haskell-mode-syntax-table)))
169     (modify-syntax-entry ?\' "w" table)
170     (modify-syntax-entry ?_  "w" table)
171     (modify-syntax-entry ?\\ "_" table)
172     table)
173   "Syntax table used for Haskell declaration scanning.")
174
175
176 (defun haskell-ds-get-variable (prefix)
177   "Assuming point is looking at the regexp PREFIX followed by the
178 start of a declaration (perhaps in the middle of a series of
179 declarations concerning a single variable), if this declaration is a
180 value binding or type signature, return the variable involved, and
181 otherwise returns nil.  Point is not moved in either case."
182   ;; I think I can now handle all declarations bar those with comments
183   ;; nested before the second lexeme.
184   (let ((orig-table (syntax-table))
185         (start (point))
186         par-start
187         name)
188     (set-syntax-table haskell-ds-syntax-table)
189     (re-search-forward (concat "\\=" prefix) (point-max) t)
190     ;; Keyword.
191     (if (looking-at haskell-ds-start-keywords-re)
192         ()
193       (if (looking-at "(\\(\\s_+\\))")
194           ;; Paranthesised symbolic variable.
195           (setq name (haskell-ds-match-string 1))
196         (if (if (looking-at "(")
197                 ;; Skip paranthesised expression.
198                 (progn
199                   (setq par-start t)
200                   (forward-sexp)
201                   ;; Repeating this code and avoiding moving point if
202                   ;; possible speeds things up.
203                   (looking-at "\\(\\)\\s-*\\(\\s_+\\|`\\(\\sw+\\)`\\)"))
204               (looking-at "\\(\\sw+\\)\\s-*\\(\\s_+\\|`\\(\\sw+\\)`\\)"))
205             (let ((match2 (haskell-ds-match-string 2)))
206               ;; Weed out `::', `=' and `|' from potential infix
207               ;; symbolic variable.
208               (if (member match2 '("::" "=" "|"))
209                   (if (not par-start)
210                       ;; Variable identifier.
211                       (setq name (haskell-ds-match-string 1)))
212                 (if (eq (aref match2 0) ?\`)
213                     ;; Infix variable identifier.
214                     (setq name (haskell-ds-match-string 3))
215                   ;; Infix symbolic variable.
216                   (setq name match2)))))
217         ;; Variable identifier.
218         (if (and (not name) (looking-at "\\sw+"))
219             (setq name (haskell-ds-match-string 0)))))
220     ;; Return the result.
221     (goto-char start)
222     (set-syntax-table orig-table)
223     name))
224
225 (defun haskell-ds-move-to-start-regexp (inc regexp)
226   "Move to beginning of line that succeeds/preceeds (INC = 1/-1)
227 current line that starts with REGEXP and is not in `font-lock-comment-face'."
228   ;; Making this defsubst instead of defun appears to have little or
229   ;; no effect on efficiency.  It is probably not called enough to do
230   ;; so.
231   (while (and (= (forward-line inc) 0)
232               (or (not (looking-at regexp))
233                   (eq (get-text-property (point) 'face)
234                       'font-lock-comment-face)))))
235
236 (defvar literate-haskell-ds-line-prefix "> "
237   "The regexp that matches the start of a line of Bird-style literate
238 code for the purposes of `imenu' support.  Current value is \"> \" as we
239 assume top-level declarations start at column 3.  Must not contain the
240 special \"^\" regexp as we may not use the regexp at the start of a
241 regexp string.  Note this is only for `imenu' support.")
242
243 (defvar haskell-ds-start-decl-re "\\(\\sw\\|(\\)"
244   "The regexp that starts a Haskell declaration.")
245
246 (defvar literate-haskell-ds-start-decl-re
247   (concat literate-haskell-ds-line-prefix haskell-ds-start-decl-re)
248   "The regexp that starts a Bird-style literate Haskell declaration.")
249
250 (defun haskell-ds-move-to-decl (direction bird-literate fix)
251   "General function for moving to the start of a declaration, either
252 forwards or backwards from point, with normal or with Bird-style
253 literate scripts.  If DIRECTION is t, then forward, else backward.  If
254 BIRD-LITERATE is t, then treat as Bird-style literate scripts, else
255 normal scripts.  Returns point if point is left at the start of a
256 declaration, and nil otherwise, ie. because point is at the beginning
257 or end of the buffer and no declaration starts there.  If FIX is t,
258 then point does not move if already at the start of a declaration."
259   ;; As `haskell-ds-get-variable' cannot separate an infix variable
260   ;; identifier out of a value binding with non-alphanumeric first
261   ;; argument, this function will treat such value bindings as
262   ;; separate from the declarations surrounding it.
263   (let (;; The variable typed or bound in the current series of
264         ;; declarations.
265         name
266         ;; The variable typed or bound in the new declaration.
267         newname
268         ;; Hack to solve hard problem for Bird-style literate scripts
269         ;; that start with a declaration.  We are in the abyss if
270         ;; point is before start of this declaration.
271         abyss
272         (line-prefix (if bird-literate literate-haskell-ds-line-prefix ""))
273         ;; The regexp to match for the start of a declaration.
274         (start-decl-re (if bird-literate
275                            literate-haskell-ds-start-decl-re
276                          haskell-ds-start-decl-re))
277         (increment (if direction 1 -1))
278         (bound (if direction (point-max) (point-min)))
279         ;; Original syntax table.
280         (orig-table (syntax-table))
281         result)
282     ;; Change syntax table.
283     (set-syntax-table haskell-ds-syntax-table)
284     ;; Move to beginning of line that starts the "current
285     ;; declaration" (dependent on DIRECTION and FIX), and then get
286     ;; the variable typed or bound by this declaration, if any.
287     (let (;; Where point was at call of function.
288           (here (point))
289           ;; Where the declaration on this line (if any) starts.
290           (start (progn
291                    (beginning-of-line)
292                    ;; Checking the face to ensure a declaration starts
293                    ;; here seems to be the only addition to make this
294                    ;; module support LaTeX-style literate scripts.
295                    (if (and (looking-at start-decl-re)
296                             (not (eq (get-text-property (point) 'face)
297                                      'font-lock-comment-face)))
298                        (match-beginning 1)))))
299       (if (and start
300                ;; This complicated boolean determines whether we
301                ;; should include the declaration that starts on the
302                ;; current line as the "current declaration" or not.
303                (or (and (or (and direction (not fix))
304                             (and (not direction) fix))
305                         (>= here start))
306                    (and (or (and direction fix)
307                             (and (not direction) (not fix)))
308                         (> here start))))
309           ;; If so, we are already at start of the current line, so
310           ;; do nothing.
311           ()
312         ;; If point was before start of a declaration on the first
313         ;; line of the buffer (possible for Bird-style literate
314         ;; scripts) then we are in the abyss.
315         (if (and start (bobp))
316             (setq abyss t)
317           ;; Otherwise we move to the start of the first declaration
318           ;; on a line preceeding the current one.
319           (haskell-ds-move-to-start-regexp -1 start-decl-re))))
320     ;; If we are in the abyss, position and return as appropriate.
321     (if abyss
322         (if (not direction)
323             (setq result nil)
324           (re-search-forward (concat "\\=" line-prefix) (point-max) t)
325           (setq result (point)))
326       ;; Get the variable typed or bound by this declaration, if any. 
327       (setq name (haskell-ds-get-variable line-prefix))
328       (if (not name)
329           ;; If no such variable, stop at the start of this
330           ;; declaration if moving backward, or move to the next
331           ;; declaration if moving forward.
332           (if direction
333               (haskell-ds-move-to-start-regexp 1 start-decl-re))
334         ;; If there is a variable, find the first
335         ;; succeeding/preceeding declaration that does not type or
336         ;; bind it.  Check for reaching start/end of buffer.
337         (haskell-ds-move-to-start-regexp increment start-decl-re)
338         (while (and (/= (point) bound)
339                     (and (setq newname (haskell-ds-get-variable line-prefix))
340                          (string= name newname)))
341           (setq name newname)
342           (haskell-ds-move-to-start-regexp increment start-decl-re))
343         ;; If we are going backward, and have either reached a new
344         ;; declaration or the beginning of a buffer that does not
345         ;; start with a declaration, move forward to start of next
346         ;; declaration (which must exist).  Otherwise, we are done.
347         (if (and (not direction)
348                  (or (and (looking-at start-decl-re)
349                           (not (string= name
350                                         ;; Note we must not use
351                                         ;; newname here as this may
352                                         ;; not have been set if we
353                                         ;; have reached the beginning
354                                         ;; of the buffer.
355                                         (haskell-ds-get-variable
356                                          line-prefix))))
357                      (and (not (looking-at start-decl-re))
358                           (bobp))))
359             (haskell-ds-move-to-start-regexp 1 start-decl-re)))
360       ;; Store whether we are at the start of a declaration or not.
361       ;; Used to calculate final result.
362       (let ((at-start-decl (looking-at start-decl-re)))
363         ;; If we are at the beginning of a line, move over
364         ;; line-prefix, if present at point.
365         (if (bolp)
366             (re-search-forward (concat "\\=" line-prefix) (point-max) t))
367         ;; Return point if at the start of a declaration and nil
368         ;; otherwise.
369         (setq result (if at-start-decl (point) nil))))
370     ;; Replace original syntax table.
371     (set-syntax-table orig-table)
372     ;; Return the result.
373     result))
374
375 (defun haskell-ds-bird-p ()
376   (if (boundp 'haskell-literate)
377       (eq haskell-literate 'bird) nil))
378
379 (defun haskell-ds-backward-decl ()
380   "Move point backward to the first character preceeding the current
381 point that starts a top-level declaration.  A series of declarations
382 concerning one variable is treated as one declaration by this
383 function.  So, if point is within a top-level declaration then move it
384 to the start of that declaration.  If point is already at the start of
385 a top-level declaration, then move it to the start of the preceeding
386 declaration.  Returns point if point is left at the start of a
387 declaration, and nil otherwise, ie. because point is at the beginning
388 of the buffer and no declaration starts there."
389   (interactive)
390   (haskell-ds-move-to-decl nil (haskell-ds-bird-p) nil))
391
392 (defun haskell-ds-forward-decl ()
393   "As `haskell-ds-backward-decl' but forward."
394   (interactive)
395   (haskell-ds-move-to-decl t (haskell-ds-bird-p) nil))
396
397 (defun haskell-ds-generic-find-next-decl (bird-literate)
398   "Find the name, position and type of the declaration at or after
399 point.  Returns `((name . (start-position . name-position)) . type)'
400 if one exists and nil otherwise.  The start-position is at the start
401 of the declaration, and the name-position is at the start of the name
402 of the declaration.  The name is a string, the positions are buffer
403 positions and the type is one of the symbols \"variable\", \"datatype\",
404 \"class\", \"import\" and \"instance\"."
405   (let (;; The name, type and name-position of the declaration to
406         ;; return.
407         name
408         type
409         name-pos
410         ;; Buffer positions marking the start and end of the space
411         ;; containing a declaration.
412         start
413         end
414         ;; Original syntax table.
415         (orig-table (syntax-table)))
416     ;; Change to declaration scanning syntax.
417     (set-syntax-table haskell-ds-syntax-table)
418     ;; Stop when we are at the end of the buffer or when a valid
419     ;; declaration is grabbed.
420     (while (not (or (eobp) name))
421       ;; Move forward to next declaration at or after point.
422       (haskell-ds-move-to-decl t bird-literate t)
423       ;; Start and end of search space is currently just the starting
424       ;; line of the declaration.
425       (setq start (point)
426             end   (progn (end-of-line) (point)))
427       (goto-char start)
428       (cond
429        ;; If the start of the top-level declaration does not begin
430        ;; with a starting keyword, then (if legal) must be a type
431        ;; signature or value binding, and the variable concerned is
432        ;; grabbed.
433        ((not (looking-at haskell-ds-start-keywords-re))
434         (setq name (haskell-ds-get-variable ""))
435         (if name
436             (progn
437               (setq type 'variable)
438               (re-search-forward (regexp-quote name) end t)
439               (setq name-pos (match-beginning 0)))))
440        ;; User-defined datatype declaration.
441        ((re-search-forward "\\=\\(data\\|newtype\\|type\\)\\>" end t)
442         (re-search-forward "=>" end t)
443         (if (looking-at "[ \t]*\\(\\sw+\\)")
444             (progn
445               (setq name (haskell-ds-match-string 1))
446               (setq name-pos (match-beginning 1))
447               (setq type 'datatype))))
448        ;; Class declaration.
449        ((re-search-forward "\\=class\\>" end t)
450         (re-search-forward "=>" end t)
451         (if (looking-at "[ \t]*\\(\\sw+\\)")
452             (progn
453               (setq name (haskell-ds-match-string 1))
454               (setq name-pos (match-beginning 1))
455               (setq type 'class))))
456        ;; Import declaration.
457        ((looking-at "import[ \t]+\\(qualified[ \t]+\\)?\\(\\sw+\\)")
458         (setq name (haskell-ds-match-string 2))
459         (setq name-pos (match-beginning 2))
460         (setq type 'import))
461        ;; Instance declaration.
462        ((re-search-forward "\\=instance[ \t]+" end t)
463         (re-search-forward "=>[ \t]+" end t)
464         ;; The instance "title" starts just after the `instance' (and
465         ;; any context) and finishes just before the _first_ `where'
466         ;; if one exists.  This solution is ugly, but I can't find a
467         ;; nicer one---a simple regexp will pick up the last `where',
468         ;; which may be rare but nevertheless...
469         (setq name-pos (point))
470         (setq name (format "%s"
471                            (buffer-substring
472                             (point)
473                             (progn                             
474                               ;; Look for a `where'.
475                               (if (re-search-forward "\\<where\\>" end t)
476                                   ;; Move back to just before the `where'.
477                                   (progn
478                                     (re-search-backward "\\s-where")
479                                     (point))
480                                 ;; No `where' so move to last non-whitespace
481                                 ;; before `end'.
482                                 (progn
483                                   (goto-char end)
484                                   (skip-chars-backward " \t")
485                                   (point)))))))
486         ;; If we did not manage to extract a name, cancel this
487         ;; declaration (eg. when line ends in "=> ").
488         (if (string-match "^[ \t]*$" name) (setq name nil))
489         (setq type 'instance)))
490       ;; Move past start of current declaration.
491       (goto-char end))
492     ;; Replace syntax table.
493     (set-syntax-table orig-table)
494     ;; If we have a valid declaration then return it, otherwise return
495     ;; nil.
496     (if name
497         (cons (cons name (cons (copy-marker start t) (copy-marker name-pos t)))
498               type)
499       nil)))
500
501 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
502 ;; Declaration scanning via `imenu'.
503
504 (defun haskell-ds-create-imenu-index ()
505   "Function for finding `imenu' declarations in Haskell mode.
506 Finds all declarations (classes, variables, imports, instances and
507 datatypes) in a Haskell file for the `imenu' package."
508   ;; Each list has elements of the form `(INDEX-NAME . INDEX-POSITION)'.
509   ;; These lists are nested using `(INDEX-TITLE . INDEX-ALIST)'.
510   (let* ((bird-literate (haskell-ds-bird-p))
511          (index-alist '())
512          (index-class-alist '())   ;; Classes
513          (index-var-alist '())     ;; Variables
514          (index-imp-alist '())     ;; Imports
515          (index-inst-alist '())    ;; Instances
516          (index-type-alist '())    ;; Datatypes
517          ;; Variables for showing progress.
518          (bufname (buffer-name))
519          (divisor-of-progress (max 1 (/ (point-max) 100)))
520          ;; The result we wish to return.
521          result)
522     (goto-char (point-min))
523     ;; Loop forwards from the beginning of the buffer through the
524     ;; starts of the top-level declarations.
525     (while (< (point) (point-max))
526       (message "Scanning declarations in %s... (%3d%%)" bufname
527                (/ (point) divisor-of-progress))
528       ;; Grab the next declaration.
529       (setq result (haskell-ds-generic-find-next-decl bird-literate))
530       (if result
531           ;; If valid, extract the components of the result.
532           (let* ((name-posns (car result))
533                  (name (car name-posns))
534                  (posns (cdr name-posns))
535                  (start-pos (car posns))
536                  (type (cdr result))
537                  ;; Place `(name . start-pos)' in the correct alist.
538                  (alist (cond
539                          ((eq type 'variable) 'index-var-alist)
540                          ((eq type 'datatype) 'index-type-alist)
541                          ((eq type 'class) 'index-class-alist)
542                          ((eq type 'import) 'index-imp-alist)
543                          ((eq type 'instance) 'index-inst-alist))))
544             (set alist (cons (cons name start-pos) (eval alist))))))
545     ;; Now sort all the lists, label them, and place them in one list.
546     (message "Sorting declarations in %s..." bufname)
547     (and index-type-alist
548          (push (cons "Datatypes"
549                      (sort index-type-alist 'haskell-ds-imenu-label-cmp))
550                index-alist))
551     (and index-inst-alist
552          (push (cons "Instances"
553                      (sort index-inst-alist 'haskell-ds-imenu-label-cmp))
554                index-alist))
555     (and index-imp-alist
556          (push (cons "Imports"
557                      (sort index-imp-alist 'haskell-ds-imenu-label-cmp))
558                index-alist))
559     (and index-var-alist
560          (push (cons "Variables"
561                      (sort index-var-alist 'haskell-ds-imenu-label-cmp))
562                index-alist))
563     (and index-class-alist
564          (push (cons "Classes"
565                      (sort index-class-alist 'haskell-ds-imenu-label-cmp))
566                index-alist))
567     (message "Sorting declarations in %s...done" bufname)
568     ;; Return the alist.
569     index-alist))
570
571 (defun haskell-ds-imenu-label-cmp (el1 el2)
572   "Predicate to compare labels in lists produced by
573 `haskell-ds-create-imenu-index'."
574   (string< (car el1) (car el2)))
575
576 (defun haskell-ds-imenu ()
577   "Install `imenu' for Haskell scripts."
578   (setq imenu-create-index-function 'haskell-ds-create-imenu-index)
579   (if (fboundp 'imenu-add-to-menubar)
580       (imenu-add-to-menubar "Declarations")))
581
582 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
583 ;; Declaration scanning via `func-menu'.
584
585 (defun haskell-ds-func-menu-next (buffer)
586   "Non-literate Haskell version of `haskell-ds-generic-func-menu-next'." 
587   (haskell-ds-generic-func-menu-next (haskell-ds-bird-p) buffer)) 
588
589 (defun haskell-ds-generic-func-menu-next (bird-literate buffer)
590   "Return `(name . pos)' of next declaration."
591   (set-buffer buffer)
592   (let ((result (haskell-ds-generic-find-next-decl bird-literate)))
593     (if result
594         (let* ((name-posns (car result))
595                (name (car name-posns))
596                (posns (cdr name-posns))
597                (name-pos (cdr posns))
598                ;;(type (cdr result))
599                )
600           (cons ;(concat
601                  ;; func-menu has problems with spaces, and adding a
602                  ;; qualifying keyword will not allow the "goto fn"
603                  ;; functions to work properly.  Sigh.
604                  ;; (cond
605                  ;;  ((eq type 'variable) "")
606                  ;;  ((eq type 'datatype) "datatype ")
607                  ;;  ((eq type 'class) "class ")
608                  ;;  ((eq type 'import) "import ")
609                  ;;  ((eq type 'instance) "instance "))
610                  name;)
611                 name-pos))
612       nil)))
613
614 (defvar haskell-ds-func-menu-regexp
615   (concat "^" haskell-ds-start-decl-re)
616   "Regexp to match the start of a possible declaration.")
617
618 (defvar literate-haskell-ds-func-menu-regexp
619   (concat "^" literate-haskell-ds-start-decl-re)
620   "As `haskell-ds-func-menu-regexp' but for Bird-style literate scripts.")
621
622 (defun haskell-ds-func-menu ()
623   "Use `func-menu' to establish declaration scanning for Haskell scripts."
624   (require 'func-menu)
625   (set (make-local-variable 'fume-menubar-menu-name) "Declarations")
626   (set (make-local-variable 'fume-function-name-regexp-alist)
627        (if (haskell-ds-bird-p)
628            '((haskell-mode . literate-haskell-ds-func-menu-regexp))
629          '((haskell-mode . haskell-ds-func-menu-regexp))))
630   (set (make-local-variable 'fume-find-function-name-method-alist)
631        '((haskell-mode . haskell-ds-func-menu-next)))
632   (fume-add-menubar-entry)
633   (local-set-key "\C-cl" 'fume-list-functions)
634   (local-set-key "\C-cg" 'fume-prompt-function-goto)
635   (local-set-key [(meta button1)] 'fume-mouse-function-goto))
636
637 ;; Key mappings.
638 (defun haskell-ds-keys ()
639   "Map the keys for forward and backward declaration movement."
640   (local-set-key "\M-\C-e" 'haskell-ds-forward-decl)
641   (local-set-key "\M-\C-a" 'haskell-ds-backward-decl))
642
643 ;; The main functions to turn on declaration scanning.
644 (defun turn-on-haskell-decl-scan ()
645   "Turn on declaration scanning for Haskell mode.  Top-level
646 declarations are scanned and listed in the menu item \"Declarations\".
647 Selecting an item from this menu will take point to the start of the
648 declaration.
649
650 \\[haskell-ds-forward-decl] and \\[haskell-ds-backward-decl] move forward and backward to the start of a declaration.
651
652 Under XEmacs, the following keys are also defined:
653
654 \\[fume-list-functions] lists the declarations of the current buffer,
655 \\[fume-prompt-function-goto] prompts for a declaration to move to, and
656 \\[fume-mouse-function-goto] moves to the declaration whose name is at point.
657
658 This may link with `haskell-doc' (only for Emacs currently).
659
660 For non-literate and LaTeX-style literate scripts, we assume the
661 common convention that top-level declarations start at the first
662 column.  For Bird-style literate scripts, we assume the common
663 convention that top-level declarations start at the third column,
664 ie. after \"> \".
665
666 Anything in `font-lock-comment-face' is not considered for a
667 declaration.  Therefore, using Haskell font locking with comments
668 coloured in `font-lock-comment-face' improves declaration scanning.
669
670 To turn on declaration scanning for all Haskell buffers, add this to
671 .emacs:
672
673   (add-hook 'haskell-mode-hook 'turn-on-haskell-decl-scan)
674
675 To turn declaration scanning on for the current buffer, call
676 `turn-on-haskell-decl-scan'.
677
678 Literate Haskell scripts are supported: If the value of
679 `haskell-literate' (automatically set by the Haskell mode of
680 Moss&Thorn) is 'bird, a Bird-style literate script is assumed.  If it
681 is nil or 'latex, a non-literate or LaTeX-style literate script is
682 assumed, respectively.
683
684 Invokes `haskell-decl-scan-hook' if not nil.
685
686 Use `haskell-decl-scan-version' to find out what version this is."
687   (interactive)
688   (haskell-ds-keys)
689   (if (fboundp 'imenu)
690       (haskell-ds-imenu)
691     (haskell-ds-func-menu))
692   (run-hooks 'haskell-decl-scan-hook))
693
694 ;; Provide ourselves:
695
696 (provide 'haskell-decl-scan)
697
698 ;;; haskell-decl-scan.el ends here