diff options
Diffstat (limited to 'lisp')
| -rw-r--r-- | lisp/ChangeLog | 5 | ||||
| -rw-r--r-- | lisp/emacs-lisp/smie.el | 178 |
2 files changed, 182 insertions, 1 deletions
diff --git a/lisp/ChangeLog b/lisp/ChangeLog index dd698a0ef40..09424462f21 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog | |||
| @@ -1,5 +1,10 @@ | |||
| 1 | 2010-09-03 Stefan Monnier <monnier@iro.umontreal.ca> | 1 | 2010-09-03 Stefan Monnier <monnier@iro.umontreal.ca> |
| 2 | 2 | ||
| 3 | Provide blink-matching support to SMIE. | ||
| 4 | * emacs-lisp/smie.el (smie-bnf-closer-alist): New function. | ||
| 5 | (smie-blink-matching-triggers, smie-blink-matching-inners): New vars. | ||
| 6 | (smie-blink-matching-check, smie-blink-matching-open): New functions. | ||
| 7 | |||
| 3 | * simple.el (newline): Fix last change to properly remove itself from | 8 | * simple.el (newline): Fix last change to properly remove itself from |
| 4 | the hook. | 9 | the hook. |
| 5 | 10 | ||
diff --git a/lisp/emacs-lisp/smie.el b/lisp/emacs-lisp/smie.el index e8b5c2448e8..c6df851b0e5 100644 --- a/lisp/emacs-lisp/smie.el +++ b/lisp/emacs-lisp/smie.el | |||
| @@ -75,6 +75,26 @@ | |||
| 75 | 75 | ||
| 76 | ;;; Building precedence level tables from BNF specs. | 76 | ;;; Building precedence level tables from BNF specs. |
| 77 | 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. | ||
| 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)) |
| @@ -206,6 +226,87 @@ one of those elements share the same precedence level and associativity." | |||
| 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: |
| @@ -223,7 +324,7 @@ 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 ()) |
| @@ -596,6 +697,81 @@ This command assumes point is not in a string or comment." | |||
| 596 | pos end)))) | 697 | pos end)))) |
| 597 | (t))))))) | 698 | (t))))))) |
| 598 | 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))))))) | ||
| 774 | |||
| 599 | ;;; The indentation engine. | 775 | ;;; The indentation engine. |
| 600 | 776 | ||
| 601 | (defcustom smie-indent-basic 4 | 777 | (defcustom smie-indent-basic 4 |