comparison lisp/emacs-lisp/smie.el @ 110161:eb977e012180

Provide blink-matching support to SMIE. * lisp/emacs-lisp/smie.el (smie-bnf-closer-alist): New function. (smie-blink-matching-triggers, smie-blink-matching-inners): New vars. (smie-blink-matching-check, smie-blink-matching-open): New functions.
author Stefan Monnier <monnier@iro.umontreal.ca>
date Fri, 03 Sep 2010 13:18:45 +0200
parents 5cab4c4229ff
children 06323f4d421b
comparison
equal deleted inserted replaced
110160:c32f6ba217a2 110161:eb977e012180
72 72
73 (defvar comment-continue) 73 (defvar comment-continue)
74 (declare-function comment-string-strip "newcomment" (str beforep afterp)) 74 (declare-function comment-string-strip "newcomment" (str beforep afterp))
75 75
76 ;;; Building precedence level tables from BNF specs. 76 ;;; Building precedence level tables from BNF specs.
77
78 ;; We have 4 different representations of a "grammar":
79 ;; - a BNF table, which is a list of BNF rules of the form
80 ;; (NONTERM RHS1 ... RHSn) where each RHS is a list of terminals (tokens)
81 ;; or nonterminals. Any element in these lists which does not appear as
82 ;; the `car' of a BNF rule is taken to be a terminal.
83 ;; - A list of precedences (key word "precs"), is a list, sorted
84 ;; from lowest to highest precedence, of precedence classes that
85 ;; have the form (ASSOCIATIVITY TERMINAL1 .. TERMINALn), where
86 ;; ASSOCIATIVITY can be `assoc', `left', `right' or `nonassoc'.
87 ;; - a 2 dimensional precedence table (key word "prec2"), is a 2D
88 ;; table recording the precedence relation (can be `<', `=', `>', or
89 ;; nil) between each pair of tokens.
90 ;; - a precedence-level table (key word "levels"), while is a alist
91 ;; giving for each token its left and right precedence level (a
92 ;; number or nil). This is used in `smie-op-levels'.
93 ;; The prec2 tables are only intermediate data structures: the source
94 ;; code normally provides a mix of BNF and precs tables, and then
95 ;; turns them into a levels table, which is what's used by the rest of
96 ;; the SMIE code.
77 97
78 (defun smie-set-prec2tab (table x y val &optional override) 98 (defun smie-set-prec2tab (table x y val &optional override)
79 (assert (and x y)) 99 (assert (and x y))
80 (let* ((key (cons x y)) 100 (let* ((key (cons x y))
81 (old (gethash key table))) 101 (old (gethash key table)))
204 '= override))) 224 '= override)))
205 (t (smie-set-prec2tab prec2 (car rhs) (cadr rhs) '= override))) 225 (t (smie-set-prec2tab prec2 (car rhs) (cadr rhs) '= override)))
206 (setq rhs (cdr rhs))))) 226 (setq rhs (cdr rhs)))))
207 prec2)) 227 prec2))
208 228
229 ;; (defun smie-prec2-closer-alist (prec2 include-inners)
230 ;; "Build a closer-alist from a PREC2 table.
231 ;; The return value is in the same form as `smie-closer-alist'.
232 ;; INCLUDE-INNERS if non-nil means that inner keywords will be included
233 ;; in the table, e.g. the table will include things like (\"if\" . \"else\")."
234 ;; (let* ((non-openers '())
235 ;; (non-closers '())
236 ;; ;; For each keyword, this gives the matching openers, if any.
237 ;; (openers (make-hash-table :test 'equal))
238 ;; (closers '())
239 ;; (done nil))
240 ;; ;; First, find the non-openers and non-closers.
241 ;; (maphash (lambda (k v)
242 ;; (unless (or (eq v '<) (member (cdr k) non-openers))
243 ;; (push (cdr k) non-openers))
244 ;; (unless (or (eq v '>) (member (car k) non-closers))
245 ;; (push (car k) non-closers)))
246 ;; prec2)
247 ;; ;; Then find the openers and closers.
248 ;; (maphash (lambda (k _)
249 ;; (unless (member (car k) non-openers)
250 ;; (puthash (car k) (list (car k)) openers))
251 ;; (unless (or (member (cdr k) non-closers)
252 ;; (member (cdr k) closers))
253 ;; (push (cdr k) closers)))
254 ;; prec2)
255 ;; ;; Then collect the matching elements.
256 ;; (while (not done)
257 ;; (setq done t)
258 ;; (maphash (lambda (k v)
259 ;; (when (eq v '=)
260 ;; (let ((aopeners (gethash (car k) openers))
261 ;; (dopeners (gethash (cdr k) openers))
262 ;; (new nil))
263 ;; (dolist (o aopeners)
264 ;; (unless (member o dopeners)
265 ;; (setq new t)
266 ;; (push o dopeners)))
267 ;; (when new
268 ;; (setq done nil)
269 ;; (puthash (cdr k) dopeners openers)))))
270 ;; prec2))
271 ;; ;; Finally, dump the resulting table.
272 ;; (let ((alist '()))
273 ;; (maphash (lambda (k v)
274 ;; (when (or include-inners (member k closers))
275 ;; (dolist (opener v)
276 ;; (unless (equal opener k)
277 ;; (push (cons opener k) alist)))))
278 ;; openers)
279 ;; alist)))
280
281 (defun smie-bnf-closer-alist (bnf &optional no-inners)
282 ;; We can also build this closer-alist table from a prec2 table,
283 ;; but it takes more work, and the order is unpredictable, which
284 ;; is a problem for smie-close-block.
285 ;; More convenient would be to build it from a levels table since we
286 ;; always have this table (contrary to the BNF), but it has all the
287 ;; disadvantages of the prec2 case plus the disadvantage that the levels
288 ;; table has lost some info which would result in extra invalid pairs.
289 "Build a closer-alist from a BNF table.
290 The return value is in the same form as `smie-closer-alist'.
291 NO-INNERS if non-nil means that inner keywords will be excluded
292 from the table, e.g. the table will not include things like (\"if\" . \"else\")."
293 (let ((nts (mapcar #'car bnf)) ;non terminals.
294 (alist '()))
295 (dolist (nt bnf)
296 (dolist (rhs (cdr nt))
297 (unless (or (< (length rhs) 2) (member (car rhs) nts))
298 (if no-inners
299 (let ((last (car (last rhs))))
300 (unless (member last nts)
301 (pushnew (cons (car rhs) last) alist :test #'equal)))
302 ;; Reverse so that the "real" closer gets there first,
303 ;; which is important for smie-close-block.
304 (dolist (term (reverse (cdr rhs)))
305 (unless (member term nts)
306 (pushnew (cons (car rhs) term) alist :test #'equal)))))))
307 (nreverse alist)))
308
309
209 (defun smie-prec2-levels (prec2) 310 (defun smie-prec2-levels (prec2)
210 ;; FIXME: Rather than only return an alist of precedence levels, we should 311 ;; FIXME: Rather than only return an alist of precedence levels, we should
211 ;; also extract other useful data from it: 312 ;; also extract other useful data from it:
212 ;; - matching sets of block openers&closers (which can otherwise become 313 ;; - matching sets of block openers&closers (which can otherwise become
213 ;; collapsed into a single equivalence class in smie-op-levels) for 314 ;; collapsed into a single equivalence class in smie-op-levels) for
221 "Take a 2D precedence table and turn it into an alist of precedence levels. 322 "Take a 2D precedence table and turn it into an alist of precedence levels.
222 PREC2 is a table as returned by `smie-precs-precedence-table' or 323 PREC2 is a table as returned by `smie-precs-precedence-table' or
223 `smie-bnf-precedence-table'." 324 `smie-bnf-precedence-table'."
224 ;; For each operator, we create two "variables" (corresponding to 325 ;; For each operator, we create two "variables" (corresponding to
225 ;; the left and right precedence level), which are represented by 326 ;; the left and right precedence level), which are represented by
226 ;; cons cells. Those are the vary cons cells that appear in the 327 ;; cons cells. Those are the very cons cells that appear in the
227 ;; final `table'. The value of each "variable" is kept in the `car'. 328 ;; final `table'. The value of each "variable" is kept in the `car'.
228 (let ((table ()) 329 (let ((table ())
229 (csts ()) 330 (csts ())
230 (eqs ()) 331 (eqs ())
231 tmp x y) 332 tmp x y)
593 (goto-char start) 694 (goto-char start)
594 (signal 'scan-error 695 (signal 'scan-error
595 (list "Containing expression ends prematurely" 696 (list "Containing expression ends prematurely"
596 pos end)))) 697 pos end))))
597 (t))))))) 698 (t)))))))
699
700 (defvar smie-blink-matching-triggers '(?\s ?\n)
701 "Chars which might trigger `blink-matching-open'.
702 These can include the final chars of end-tokens, or chars that are
703 typically inserted right after an end token.
704 I.e. a good choice can be:
705 (delete-dups
706 (mapcar (lambda (kw) (aref (cdr kw) (1- (length (cdr kw)))))
707 smie-closer-alist))")
708
709 (defcustom smie-blink-matching-inners t
710 "Whether SMIE should blink to matching opener for inner keywords.
711 If non-nil, it will blink not only for \"begin..end\" but also for \"if...else\"."
712 :type 'boolean)
713
714 (defun smie-blink-matching-check (start end)
715 (save-excursion
716 (goto-char end)
717 (let ((ender (funcall smie-backward-token-function)))
718 (cond
719 ((not (and ender (rassoc ender smie-closer-alist)))
720 ;; This not is one of the begin..end we know how to check.
721 (blink-matching-check-mismatch start end))
722 ((not start) t)
723 (t
724 (goto-char start)
725 (let ((starter (funcall smie-forward-token-function)))
726 (not (member (cons starter ender) smie-closer-alist))))))))
727
728 (defun smie-blink-matching-open ()
729 "Blink the matching opener when applicable.
730 This uses SMIE's tables and is expected to be placed on `post-self-insert-hook'."
731 (when (and blink-matching-paren
732 smie-closer-alist ; Optimization.
733 (eq (char-before) last-command-event) ; Sanity check.
734 (memq last-command-event smie-blink-matching-triggers)
735 (save-excursion
736 ;; FIXME: Here we assume that closers all end
737 ;; with a word-syntax char.
738 (unless (eq ?\w (char-syntax last-command-event))
739 (forward-char -1))
740 (and (looking-at "\\>")
741 (not (nth 8 (syntax-ppss))))))
742 (save-excursion
743 (let ((pos (point))
744 (token (funcall smie-backward-token-function)))
745 (if (= 1 (length token))
746 ;; The trigger char is itself a token but is not
747 ;; one of the closers (e.g. ?\; in Octave mode),
748 ;; so go back to the previous token
749 (setq token (save-excursion
750 (funcall smie-backward-token-function)))
751 (goto-char pos))
752 ;; Here we assume that smie-backward-token-function
753 ;; returns a token that is a string and whose content
754 ;; match the buffer's representation of this token.
755 (when (and (> (length token) 1) (stringp token)
756 (memq (aref token (1- (length token)))
757 smie-blink-matching-triggers)
758 (not (eq (aref token (1- (length token)))
759 last-command-event)))
760 ;; Token ends with a trigger char, so don't blink for
761 ;; anything else than this trigger char, lest we'd blink
762 ;; both when inserting the trigger char and when inserting a
763 ;; subsequent SPC.
764 (setq token nil))
765 (when (and (rassoc token smie-closer-alist)
766 (or smie-blink-matching-inners
767 (null (nth 2 (assoc token smie-op-levels)))))
768 ;; The major mode might set blink-matching-check-function
769 ;; buffer-locally so that interactive calls to
770 ;; blink-matching-open work right, but let's not presume
771 ;; that's the case.
772 (let ((blink-matching-check-function #'smie-blink-matching-check))
773 (blink-matching-open)))))))
598 774
599 ;;; The indentation engine. 775 ;;; The indentation engine.
600 776
601 (defcustom smie-indent-basic 4 777 (defcustom smie-indent-basic 4
602 "Basic amount of indentation." 778 "Basic amount of indentation."