89
89
;; that it does. The ability to turn off scanning would also be
90
90
;; useful. (Note that re-running (literate-)haskell-mode seems to
91
91
;; cause no problems.)
93
;; . Inconsistency: we define the start of a declaration in `imenu' as
94
;; the start of the line the declaration starts on, but in
95
;; `func-menu' as the start of the name that the declaration is
96
;; given (eg. "class Eq a => Ord a ..." starts at "class" in `imenu'
97
;; but at "Ord" in `func-menu'). This avoids rescanning of the
98
;; buffer by the goto functions of `func-menu' but allows `imenu' to
99
;; have the better definition of the start of the declaration (IMO).
101
;; . `func-menu' cannot cope well with spaces in declaration names.
102
;; This is unavoidable in "instance Eq Int" (changing the spaces to
103
;; underscores would cause rescans of the buffer). Note though that
104
;; `fume-prompt-function-goto' (usually bound to "C-c g") does cope
107
;; . Would like to extend the goto functions given by `func-menu'
108
;; under XEmacs to Emacs. Would have to implement these
109
;; ourselves as `imenu' does not provide them.
111
;; . `func-menu' uses its own syntax table when grabbing a declaration
112
;; name to lookup (why doesn't it use the syntax table of the
113
;; buffer?) so some declaration names will not be grabbed correctly,
114
;; eg. "fib'" will be grabbed as "fib" since "'" is not a word or
115
;; symbol constituent under the syntax table `func-menu' uses.
117
93
;; All functions/variables start with
118
94
;; `(turn-(on/off)-)haskell-decl-scan' or `haskell-ds-'.
127
103
(require 'haskell-mode)
128
104
(require 'syntax)
129
105
(with-no-warnings (require 'cl))
132
;; As `cl' defines macros that `imenu' uses, we must require them at
138
;; It makes a big difference if we don't copy the syntax table here,
139
;; as Emacs 21 does, but Emacs 22 doesn't.
140
(unless (eq (syntax-table)
141
(with-syntax-table (syntax-table) (syntax-table)))
142
(defmacro with-syntax-table (table &rest body)
143
"Evaluate BODY with syntax table of current buffer set to a copy of TABLE.
144
The syntax table of the current buffer is saved, BODY is evaluated, and the
145
saved table is restored, even in case of an abnormal exit.
146
Value is what BODY returns."
147
(let ((old-table (make-symbol "table"))
148
(old-buffer (make-symbol "buffer")))
149
`(let ((,old-table (syntax-table))
150
(,old-buffer (current-buffer)))
153
(set-syntax-table ,table)
156
(set-buffer ,old-buffer)
157
(set-syntax-table ,old-table))))))))
108
(defgroup haskell-decl-scan nil
109
"Haskell declaration scanning (`imenu' support)."
110
:link '(custom-manual "(haskell-mode)haskell-decl-scan-mode")
112
:prefix "haskell-decl-scan-")
114
(defcustom haskell-decl-scan-bindings-as-variables nil
115
"Whether to put top-level value bindings into a \"Variables\" category."
116
:group 'haskell-decl-scan
119
(defcustom haskell-decl-scan-add-to-menubar t
120
"Whether to add a \"Declarations\" menu entry to menu bar."
121
:group 'haskell-decl-scan
159
124
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
160
125
;; General declaration scanning functions.
162
(defalias 'haskell-ds-match-string
163
(if (fboundp 'match-string-no-properties)
164
'match-string-no-properties
166
"As `match-string' except that the string is stripped of properties."
167
(format "%s" (match-string num)))))
169
127
(defvar haskell-ds-start-keywords-re
171
"class\\|data\\|i\\(mport\\|n\\(fix\\(\\|[lr]\\)\\|stance\\)\\)\\|"
172
"module\\|primitive\\|type\\|newtype"
129
"class\\|data\\|i\\(mport\\|n\\(fix\\(\\|[lr]\\)\\|stance\\)\\)\\|"
130
"module\\|primitive\\|type\\|newtype"
174
132
"Keywords that may start a declaration.")
176
134
(defvar haskell-ds-syntax-table
271
229
;; argument, this function will treat such value bindings as
272
230
;; separate from the declarations surrounding it.
273
231
(let ( ;; The variable typed or bound in the current series of
276
;; The variable typed or bound in the new declaration.
278
;; Hack to solve hard problem for Bird-style literate scripts
279
;; that start with a declaration. We are in the abyss if
280
;; point is before start of this declaration.
282
(line-prefix (if bird-literate literate-haskell-ds-line-prefix ""))
283
;; The regexp to match for the start of a declaration.
284
(start-decl-re (if bird-literate
285
literate-haskell-ds-start-decl-re
286
haskell-ds-start-decl-re))
287
(increment (if direction 1 -1))
288
(bound (if direction (point-max) (point-min))))
234
;; The variable typed or bound in the new declaration.
236
;; Hack to solve hard problem for Bird-style literate scripts
237
;; that start with a declaration. We are in the abyss if
238
;; point is before start of this declaration.
240
(line-prefix (if bird-literate literate-haskell-ds-line-prefix ""))
241
;; The regexp to match for the start of a declaration.
242
(start-decl-re (if bird-literate
243
literate-haskell-ds-start-decl-re
244
haskell-ds-start-decl-re))
245
(increment (if direction 1 -1))
246
(bound (if direction (point-max) (point-min))))
289
247
;; Change syntax table.
290
248
(with-syntax-table haskell-ds-syntax-table
291
249
;; move to beginning of line that starts the "current
405
363
of the declaration. The name is a string, the positions are buffer
406
364
positions and the type is one of the symbols \"variable\", \"datatype\",
407
365
\"class\", \"import\" and \"instance\"."
408
(let (;; The name, type and name-position of the declaration to
413
;; Buffer positions marking the start and end of the space
414
;; containing a declaration.
366
(let ( ;; The name, type and name-position of the declaration to
371
;; Buffer positions marking the start and end of the space
372
;; containing a declaration.
417
375
;; Change to declaration scanning syntax.
418
376
(with-syntax-table haskell-ds-syntax-table
419
;; Stop when we are at the end of the buffer or when a valid
420
;; declaration is grabbed.
421
(while (not (or (eobp) name))
422
;; Move forward to next declaration at or after point.
423
(haskell-ds-move-to-decl t bird-literate t)
424
;; Start and end of search space is currently just the starting
425
;; line of the declaration.
427
end (line-end-position))
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
433
((not (looking-at haskell-ds-start-keywords-re))
434
(setq name (haskell-ds-get-variable ""))
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+\\)")
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+\\)")
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))
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"
474
;; Look for a `where'.
475
(if (re-search-forward "\\<where\\>" end t)
476
;; Move back to just before the `where'.
478
(re-search-backward "\\s-where")
480
;; No `where' so move to last non-whitespace
484
(skip-chars-backward " \t")
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.
492
;; If we have a valid declaration then return it, otherwise return
495
(cons (cons name (cons (copy-marker start t) (copy-marker name-pos t)))
377
;; Stop when we are at the end of the buffer or when a valid
378
;; declaration is grabbed.
379
(while (not (or (eobp) name))
380
;; Move forward to next declaration at or after point.
381
(haskell-ds-move-to-decl t bird-literate t)
382
;; Start and end of search space is currently just the starting
383
;; line of the declaration.
385
end (line-end-position))
387
;; If the start of the top-level declaration does not begin
388
;; with a starting keyword, then (if legal) must be a type
389
;; signature or value binding, and the variable concerned is
391
((not (looking-at haskell-ds-start-keywords-re))
392
(setq name (haskell-ds-get-variable ""))
395
(setq type 'variable)
396
(re-search-forward (regexp-quote name) end t)
397
(setq name-pos (match-beginning 0)))))
398
;; User-defined datatype declaration.
399
((re-search-forward "\\=\\(data\\|newtype\\|type\\)\\>" end t)
400
(re-search-forward "=>" end t)
401
(if (looking-at "[ \t]*\\(\\sw+\\)")
403
(setq name (match-string-no-properties 1))
404
(setq name-pos (match-beginning 1))
405
(setq type 'datatype))))
406
;; Class declaration.
407
((re-search-forward "\\=class\\>" end t)
408
(re-search-forward "=>" end t)
409
(if (looking-at "[ \t]*\\(\\sw+\\)")
411
(setq name (match-string-no-properties 1))
412
(setq name-pos (match-beginning 1))
413
(setq type 'class))))
414
;; Import declaration.
415
((looking-at "import[ \t]+\\(?:safe[\t ]+\\)?\\(?:qualified[ \t]+\\)?\\(?:\"[^\"]*\"[\t ]+\\)?\\(\\(?:\\sw\\|.\\)+\\)")
416
(setq name (match-string-no-properties 1))
417
(setq name-pos (match-beginning 1))
419
;; Instance declaration.
420
((re-search-forward "\\=instance[ \t]+" end t)
421
(re-search-forward "=>[ \t]+" end t)
422
;; The instance "title" starts just after the `instance' (and
423
;; any context) and finishes just before the _first_ `where'
424
;; if one exists. This solution is ugly, but I can't find a
425
;; nicer one---a simple regexp will pick up the last `where',
426
;; which may be rare but nevertheless...
427
(setq name-pos (point))
428
(setq name (buffer-substring-no-properties
431
;; Look for a `where'.
432
(if (re-search-forward "\\<where\\>" end t)
433
;; Move back to just before the `where'.
435
(re-search-backward "\\s-where")
437
;; No `where' so move to last non-whitespace
441
(skip-chars-backward " \t")
443
;; If we did not manage to extract a name, cancel this
444
;; declaration (eg. when line ends in "=> ").
445
(if (string-match "^[ \t]*$" name) (setq name nil))
446
(setq type 'instance)))
447
;; Move past start of current declaration.
449
;; If we have a valid declaration then return it, otherwise return
452
(cons (cons name (cons (copy-marker start t) (copy-marker name-pos t)))
499
456
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
500
457
;; Declaration scanning via `imenu'.
507
464
;; Each list has elements of the form `(INDEX-NAME . INDEX-POSITION)'.
508
465
;; These lists are nested using `(INDEX-TITLE . INDEX-ALIST)'.
509
466
(let* ((bird-literate (haskell-ds-bird-p))
511
(index-class-alist '()) ;; Classes
512
(index-var-alist '()) ;; Variables
513
(index-imp-alist '()) ;; Imports
514
(index-inst-alist '()) ;; Instances
515
(index-type-alist '()) ;; Datatypes
516
;; Variables for showing progress.
517
(bufname (buffer-name))
518
(divisor-of-progress (max 1 (/ (buffer-size) 100)))
519
;; The result we wish to return.
468
(index-class-alist '()) ;; Classes
469
(index-var-alist '()) ;; Variables
470
(index-imp-alist '()) ;; Imports
471
(index-inst-alist '()) ;; Instances
472
(index-type-alist '()) ;; Datatypes
473
;; Variables for showing progress.
474
(bufname (buffer-name))
475
(divisor-of-progress (max 1 (/ (buffer-size) 100)))
476
;; The result we wish to return.
521
478
(goto-char (point-min))
522
479
;; Loop forwards from the beginning of the buffer through the
523
480
;; starts of the top-level declarations.
524
481
(while (< (point) (point-max))
525
482
(message "Scanning declarations in %s... (%3d%%)" bufname
526
(/ (- (point) (point-min)) divisor-of-progress))
483
(/ (- (point) (point-min)) divisor-of-progress))
527
484
;; Grab the next declaration.
528
485
(setq result (haskell-ds-generic-find-next-decl bird-literate))
530
;; If valid, extract the components of the result.
531
(let* ((name-posns (car result))
532
(name (car name-posns))
533
(posns (cdr name-posns))
534
(start-pos (car posns))
536
;; Place `(name . start-pos)' in the correct alist.
487
;; If valid, extract the components of the result.
488
(let* ((name-posns (car result))
489
(name (car name-posns))
490
(posns (cdr name-posns))
491
(start-pos (car posns))
493
;; Place `(name . start-pos)' in the correct alist.
538
495
'((variable . index-var-alist)
539
496
(datatype . index-type-alist)
540
497
(class . index-class-alist)
541
498
(import . index-imp-alist)
542
499
(instance . index-inst-alist))))))
543
(set sym (cons (cons name start-pos) (symbol-value sym))))))
500
(set sym (cons (cons name start-pos) (symbol-value sym))))))
544
501
;; Now sort all the lists, label them, and place them in one list.
545
502
(message "Sorting declarations in %s..." bufname)
546
(and index-type-alist
547
(push (cons "Datatypes"
548
(sort index-type-alist 'haskell-ds-imenu-label-cmp))
550
(and index-inst-alist
551
(push (cons "Instances"
552
(sort index-inst-alist 'haskell-ds-imenu-label-cmp))
555
(push (cons "Imports"
556
(sort index-imp-alist 'haskell-ds-imenu-label-cmp))
559
(push (cons "Variables"
560
(sort index-var-alist 'haskell-ds-imenu-label-cmp))
562
(and index-class-alist
563
(push (cons "Classes"
564
(sort index-class-alist 'haskell-ds-imenu-label-cmp))
503
(when index-type-alist
504
(push (cons "Datatypes"
505
(sort index-type-alist 'haskell-ds-imenu-label-cmp))
507
(when index-inst-alist
508
(push (cons "Instances"
509
(sort index-inst-alist 'haskell-ds-imenu-label-cmp))
511
(when index-imp-alist
512
(push (cons "Imports"
513
(sort index-imp-alist 'haskell-ds-imenu-label-cmp))
515
(when index-class-alist
516
(push (cons "Classes"
517
(sort index-class-alist 'haskell-ds-imenu-label-cmp))
519
(when index-var-alist
520
(if haskell-decl-scan-bindings-as-variables
521
(push (cons "Variables"
522
(sort index-var-alist 'haskell-ds-imenu-label-cmp))
524
(setq index-alist (append index-alist
525
(sort index-var-alist 'haskell-ds-imenu-label-cmp)))))
566
526
(message "Sorting declarations in %s...done" bufname)
567
527
;; Return the alist.
574
534
(defun haskell-ds-imenu ()
575
535
"Install `imenu' for Haskell scripts."
576
536
(setq imenu-create-index-function 'haskell-ds-create-imenu-index)
577
(if (fboundp 'imenu-add-to-menubar)
578
(imenu-add-to-menubar "Declarations")))
580
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
581
;; Declaration scanning via `func-menu'.
583
(defun haskell-ds-func-menu-next (buffer)
584
"Non-literate Haskell version of `haskell-ds-generic-func-menu-next'."
585
(haskell-ds-generic-func-menu-next (haskell-ds-bird-p) buffer))
587
(defun haskell-ds-generic-func-menu-next (bird-literate buffer)
588
"Return `(name . pos)' of next declaration."
590
(let ((result (haskell-ds-generic-find-next-decl bird-literate)))
592
(let* ((name-posns (car result))
593
(name (car name-posns))
594
(posns (cdr name-posns))
595
(name-pos (cdr posns))
596
;;(type (cdr result))
599
;; func-menu has problems with spaces, and adding a
600
;; qualifying keyword will not allow the "goto fn"
601
;; functions to work properly. Sigh.
603
;; ((eq type 'variable) "")
604
;; ((eq type 'datatype) "datatype ")
605
;; ((eq type 'class) "class ")
606
;; ((eq type 'import) "import ")
607
;; ((eq type 'instance) "instance "))
612
(defvar haskell-ds-func-menu-regexp
613
(concat "^" haskell-ds-start-decl-re)
614
"Regexp to match the start of a possible declaration.")
616
(defvar literate-haskell-ds-func-menu-regexp
617
(concat "^" literate-haskell-ds-start-decl-re)
618
"As `haskell-ds-func-menu-regexp' but for Bird-style literate scripts.")
620
(declare-function fume-add-menubar-entry "ext:func-menu")
621
(defvar fume-menubar-menu-name)
622
(defvar fume-function-name-regexp-alist)
623
(defvar fume-find-function-name-method-alist)
625
(defun haskell-ds-func-menu ()
626
"Use `func-menu' to establish declaration scanning for Haskell scripts."
628
(set (make-local-variable 'fume-menubar-menu-name) "Declarations")
629
(set (make-local-variable 'fume-function-name-regexp-alist)
630
(if (haskell-ds-bird-p)
631
'((haskell-mode . literate-haskell-ds-func-menu-regexp))
632
'((haskell-mode . haskell-ds-func-menu-regexp))))
633
(set (make-local-variable 'fume-find-function-name-method-alist)
634
'((haskell-mode . haskell-ds-func-menu-next)))
635
(fume-add-menubar-entry)
636
(local-set-key "\C-cl" 'fume-list-functions)
637
(local-set-key "\C-cg" 'fume-prompt-function-goto)
638
(local-set-key [(meta button1)] 'fume-mouse-function-goto))
537
(when haskell-decl-scan-add-to-menubar
538
(imenu-add-to-menubar "Declarations")))
640
540
;; The main functions to turn on declaration scanning.
642
542
(defun turn-on-haskell-decl-scan ()
644
543
"Unconditionally activate `haskell-decl-scan-mode'."
645
(haskell-decl-scan-mode 1))
647
(defvar haskell-decl-scan-mode nil)
648
(make-variable-buffer-local 'haskell-decl-scan-mode)
545
(haskell-decl-scan-mode))
651
(defun haskell-decl-scan-mode (&optional arg)
652
"Minor mode for declaration scanning for Haskell mode.
653
Top-level declarations are scanned and listed in the menu item \"Declarations\".
654
Selecting an item from this menu will take point to the start of the
657
\\[haskell-ds-forward-decl] and \\[haskell-ds-backward-decl] move forward and backward to the start of a declaration.
659
Under XEmacs, the following keys are also defined:
661
\\[fume-list-functions] lists the declarations of the current buffer,
662
\\[fume-prompt-function-goto] prompts for a declaration to move to, and
663
\\[fume-mouse-function-goto] moves to the declaration whose name is at point.
665
This may link with `haskell-doc' (only for Emacs currently).
548
(define-minor-mode haskell-decl-scan-mode
549
"Toggle Haskell declaration scanning minor mode on or off.
550
With a prefix argument ARG, enable minor mode if ARG is
551
positive, and disable it otherwise. If called from Lisp, enable
552
the mode if ARG is omitted or nil, and toggle it if ARG is `toggle'.
554
See also info node `(haskell-mode)haskell-decl-scan-mode' for
555
more details about this minor mode.
557
Top-level declarations are scanned and listed in the menu item
558
\"Declarations\" (if enabled via option
559
`haskell-decl-scan-add-to-menubar'). Selecting an item from this
560
menu will take point to the start of the declaration.
562
\\[beginning-of-defun] and \\[end-of-defun] move forward and backward to the start of a declaration.
564
This may link with `haskell-doc-mode'.
667
566
For non-literate and LaTeX-style literate scripts, we assume the
668
567
common convention that top-level declarations start at the first
674
573
declaration. Therefore, using Haskell font locking with comments
675
574
coloured in `font-lock-comment-face' improves declaration scanning.
677
To turn on declaration scanning for all Haskell buffers, add this to
680
(add-hook 'haskell-mode-hook 'turn-on-haskell-decl-scan)
682
To turn declaration scanning on for the current buffer, call
683
`turn-on-haskell-decl-scan'.
685
576
Literate Haskell scripts are supported: If the value of
686
`haskell-literate' (automatically set by the Haskell mode of
687
Moss&Thorn) is `bird', a Bird-style literate script is assumed. If it
688
is nil or `tex', a non-literate or LaTeX-style literate script is
577
`haskell-literate' (set automatically by `literate-haskell-mode')
578
is `bird', a Bird-style literate script is assumed. If it is nil
579
or `tex', a non-literate or LaTeX-style literate script is
689
580
assumed, respectively.
691
Invokes `haskell-decl-scan-mode-hook'."
693
(if (boundp 'beginning-of-defun-function)
694
(if haskell-decl-scan-mode
696
(set (make-local-variable 'beginning-of-defun-function)
697
'haskell-ds-backward-decl)
698
(set (make-local-variable 'end-of-defun-function)
699
'haskell-ds-forward-decl))
700
(kill-local-variable 'beginning-of-defun-function)
701
(kill-local-variable 'end-of-defun-function))
702
(local-set-key "\M-\C-e"
703
(if haskell-decl-scan-mode 'haskell-ds-forward-decl))
704
(local-set-key "\M-\C-a"
705
(if haskell-decl-scan-mode 'haskell-ds-backward-decl)))
706
(if haskell-decl-scan-mode
709
(haskell-ds-func-menu))
710
;; How can we cleanly remove that menus?
711
(local-set-key [menu-bar index] nil))
712
(run-hooks 'haskell-decl-scan-mode-hook))
582
Invokes `haskell-decl-scan-mode-hook' on activation."
583
:group 'haskell-decl-scan
585
(kill-local-variable 'beginning-of-defun-function)
586
(kill-local-variable 'end-of-defun-function)
587
(kill-local-variable 'imenu-create-index-function)
588
(unless haskell-decl-scan-mode
589
;; How can we cleanly remove the "Declarations" menu?
590
(when haskell-decl-scan-add-to-menubar
591
(local-set-key [menu-bar index] nil)))
593
(when haskell-decl-scan-mode
594
(set (make-local-variable 'beginning-of-defun-function)
595
'haskell-ds-backward-decl)
596
(set (make-local-variable 'end-of-defun-function)
597
'haskell-ds-forward-decl)
714
601
;; Provide ourselves: