From c4d17d50ea180c405fc58f9ffe693b6c633571ac Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Fri, 29 Oct 2010 15:20:28 -0400 Subject: [PATCH] SMIE: change indent rules format, improve smie-setup. * lisp/emacs-lisp/smie.el (smie-precs-precedence-table) (smie-merge-prec2s, smie-bnf-precedence-table, smie-prec2-levels): Mark them pure so the tables gets built at compile time. (smie-bnf-precedence-table): Store the closer-alist in the table. (smie-prec2-levels): Preserve the closer-alist. (smie-blink-matching-open): Be more forgiving in case of indentation. (smie-hanging-p): Rename from smie-indent--hanging-p. (smie-bolp): Rename from smie-indent--bolp. (smie--parent, smie--after): New dynamic vars. (smie-parent-p, smie-next-p, smie-prev-p): New funs. (smie-indent-rules): Remove. (smie-indent--offset-rule): Remove fun. (smie-rules-function): New var. (smie-indent--rule): New fun. (smie-indent--offset, smie-indent-keyword, smie-indent-after-keyword) (smie-indent-exps): Use it. (smie-setup): Setup paren blinking; add keyword args for token functions; extract closer-alist from op-levels. (smie-indent-debug-log): Remove var. (smie-indent-debug): Remove fun. * lisp/progmodes/prolog.el (prolog-smie-indent-rules): Remove. (prolog-smie-rules): New fun to replace it. (prolog-mode-variables): Simplify. * lisp/progmodes/octave-mod.el (octave-smie-closer-alist): Remove, now that it's setup automatically. (octave-smie-indent-rules): Remove. (octave-smie-rules): New fun to replace it. (octave-mode): Simplify. --- etc/NEWS | 2 + lisp/ChangeLog | 32 ++++ lisp/emacs-lisp/smie.el | 339 ++++++++++++++++++----------------- lisp/progmodes/octave-mod.el | 55 ++---- lisp/progmodes/prolog.el | 23 ++- 5 files changed, 235 insertions(+), 216 deletions(-) diff --git a/etc/NEWS b/etc/NEWS index ff2c2101f1b..4e14402216e 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -587,6 +587,8 @@ Notifications API. It requires D-Bus for communication. * Incompatible Lisp Changes in Emacs 24.1 +** Remove obsolete name `e' (use `float-e' instead). + ** A backquote not followed by a space is now always treated as new-style. ** Test for special mode-class was moved from view-file to view-buffer. diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 4842d677fb4..0fb702e7f66 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog @@ -1,3 +1,35 @@ +2010-10-29 Stefan Monnier + + SMIE: change indent rules format, improve smie-setup. + * emacs-lisp/smie.el (smie-precs-precedence-table) + (smie-merge-prec2s, smie-bnf-precedence-table, smie-prec2-levels): + Mark them pure so the tables gets built at compile time. + (smie-bnf-precedence-table): Store the closer-alist in the table. + (smie-prec2-levels): Preserve the closer-alist. + (smie-blink-matching-open): Be more forgiving in case of indentation. + (smie-hanging-p): Rename from smie-indent--hanging-p. + (smie-bolp): Rename from smie-indent--bolp. + (smie--parent, smie--after): New dynamic vars. + (smie-parent-p, smie-next-p, smie-prev-p): New funs. + (smie-indent-rules): Remove. + (smie-indent--offset-rule): Remove fun. + (smie-rules-function): New var. + (smie-indent--rule): New fun. + (smie-indent--offset, smie-indent-keyword, smie-indent-after-keyword) + (smie-indent-exps): Use it. + (smie-setup): Setup paren blinking; add keyword args for token + functions; extract closer-alist from op-levels. + (smie-indent-debug-log): Remove var. + (smie-indent-debug): Remove fun. + * progmodes/prolog.el (prolog-smie-indent-rules): Remove. + (prolog-smie-rules): New fun to replace it. + (prolog-mode-variables): Simplify. + * progmodes/octave-mod.el (octave-smie-closer-alist): Remove, now that + it's setup automatically. + (octave-smie-indent-rules): Remove. + (octave-smie-rules): New fun to replace it. + (octave-mode): Simplify. + 2010-10-29 Glenn Morris * files.el (temporary-file-directory): Remove (already defined in C). diff --git a/lisp/emacs-lisp/smie.el b/lisp/emacs-lisp/smie.el index 4f5b2046150..6cd434ff790 100644 --- a/lisp/emacs-lisp/smie.el +++ b/lisp/emacs-lisp/smie.el @@ -109,6 +109,7 @@ (display-warning 'smie (format "Conflict: %s %s/%s %s" x old val y))) (puthash key val table)))) +(put 'smie-precs-precedence-table 'pure t) (defun smie-precs-precedence-table (precs) "Compute a 2D precedence table from a list of precedences. PRECS should be a list, sorted by precedence (e.g. \"+\" will @@ -132,6 +133,7 @@ one of those elements share the same precedence level and associativity." (smie-set-prec2tab prec2-table other-op op op1))))))) prec2-table)) +(put 'smie-merge-prec2s 'pure t) (defun smie-merge-prec2s (&rest tables) (if (null (cdr tables)) (car tables) @@ -147,6 +149,7 @@ one of those elements share the same precedence level and associativity." table)) prec2))) +(put 'smie-bnf-precedence-table 'pure t) (defun smie-bnf-precedence-table (bnf &rest precs) (let ((nts (mapcar 'car bnf)) ;Non-terminals (first-ops-table ()) @@ -233,6 +236,7 @@ one of those elements share the same precedence level and associativity." ;; Keep track of which tokens are openers/closer, so they can get a nil ;; precedence in smie-prec2-levels. (puthash :smie-open/close-alist (smie-bnf-classify bnf) prec2) + (puthash :smie-closer-alist (smie-bnf-closer-alist bnf) prec2) prec2)) ;; (defun smie-prec2-closer-alist (prec2 include-inners) @@ -377,6 +381,7 @@ CSTS is a list of pairs representing arcs in a graph." (append names (list (car names))) " < "))) +(put 'smie-prec2-levels 'pure t) (defun smie-prec2-levels (prec2) ;; FIXME: Rather than only return an alist of precedence levels, we should ;; also extract other useful data from it: @@ -479,6 +484,8 @@ PREC2 is a table as returned by `smie-precs-precedence-table' or (eq 'closer (cdr (assoc (car x) classification-table)))) (setf (nth 2 x) i) (incf i))))) ;See other (incf i) above. + (let ((ca (gethash :smie-closer-alist prec2))) + (when ca (push (cons :smie-closer-alist ca) table))) table)) ;;; Parsing using a precedence level table. @@ -803,14 +810,22 @@ If non-nil, it will blink not only for \"begin..end\" but also for \"if...else\" (defun smie-blink-matching-open () "Blink the matching opener when applicable. This uses SMIE's tables and is expected to be placed on `post-self-insert-hook'." + (let ((pos (point)) ;Position after the close token. + token) (when (and blink-matching-paren smie-closer-alist ; Optimization. - (eq (char-before) last-command-event) ; Sanity check. + (or (eq (char-before) last-command-event) ;; Sanity check. + (save-excursion + (or (progn (skip-chars-backward " \t") + (setq pos (point)) + (eq (char-before) last-command-event)) + (progn (skip-chars-backward " \n\t") + (setq pos (point)) + (eq (char-before) last-command-event))))) (memq last-command-event smie-blink-matching-triggers) (not (nth 8 (syntax-ppss)))) (save-excursion - (let ((pos (point)) - (token (funcall smie-backward-token-function))) + (setq token (funcall smie-backward-token-function)) (when (and (eq (point) (1- pos)) (= 1 (length token)) (not (rassoc token smie-closer-alist))) @@ -818,17 +833,20 @@ This uses SMIE's tables and is expected to be placed on `post-self-insert-hook'. ;; closers (e.g. ?\; in Octave mode), so go back to the ;; previous token. (setq pos (point)) - (setq token (save-excursion - (funcall smie-backward-token-function)))) + (setq token (funcall smie-backward-token-function))) (when (rassoc token smie-closer-alist) ;; We're after a close token. Let's still make sure we ;; didn't skip a comment to find that token. (funcall smie-forward-token-function) (when (and (save-excursion - ;; Trigger can be SPC, or reindent. - (skip-chars-forward " \n\t") + ;; Skip the trigger char, if applicable. + (if (eq (char-after) last-command-event) + (forward-char 1)) + (if (eq ?\n last-command-event) + ;; Skip any auto-indentation, if applicable. + (skip-chars-forward " \t")) (>= (point) pos)) - ;; If token ends with a trigger char, so don't blink for + ;; If token ends with a trigger char, don't blink for ;; anything else than this trigger char, lest we'd blink ;; both when inserting the trigger char and when ;; inserting a subsequent trigger char like SPC. @@ -850,34 +868,25 @@ This uses SMIE's tables and is expected to be placed on `post-self-insert-hook'. "Basic amount of indentation." :type 'integer) -(defvar smie-indent-rules 'unset - ;; TODO: For SML, we need more rule formats, so as to handle - ;; structure Foo = - ;; Bar (toto) - ;; and - ;; structure Foo = - ;; struct ... end - ;; I.e. the indentation after "=" depends on the parent ("structure") - ;; as well as on the following token ("struct"). - "Rules of the following form. -\((:before . TOK) . OFFSET-RULES) how to indent TOK itself. -\(TOK . OFFSET-RULES) how to indent right after TOK. -\(list-intro . TOKENS) declare TOKENS as being followed by what may look like - a funcall but is just a sequence of expressions. -\(t . OFFSET) basic indentation step. -\(args . OFFSET) indentation of arguments. -\((T1 . T2) OFFSET) like ((:before . T2) (:parent T1 OFFSET)). - -OFFSET-RULES is a list of elements which can each either be: - -\(:hanging . OFFSET-RULES) if TOK is hanging, use OFFSET-RULES. -\(:parent PARENT . OFFSET-RULES) if TOK's parent is PARENT, use OFFSET-RULES. -\(:next TOKEN . OFFSET-RULES) if TOK is followed by TOKEN, use OFFSET-RULES. -\(:prev TOKEN . OFFSET-RULES) if TOK is preceded by TOKEN, use -\(:bolp . OFFSET-RULES) If TOK is first on a line, use OFFSET-RULES. -OFFSET the offset to use. - -PARENT can be either the name of the parent or a list of such names. +(defvar smie-rules-function 'ignore + "Function providing the indentation rules. +It takes two arguments METHOD and ARG where the meaning of ARG +and the expected return value depends on METHOD. +METHOD can be: +- :after, in which case ARG is a token and the function should return the + OFFSET to use for indentation after ARG. +- :before, in which case ARG is a token and the function should return the + OFFSET to use to indent ARG itself. +- :elem, in which case the function should return either: + - the offset to use to indent function arguments (ARG = `arg') + - the basic indentation step (ARG = `basic'). +- :list-intro, in which case ARG is a token and the function should return + non-nil if TOKEN is followed by a list of expressions (not separated by any + token) rather than an expression. + +When ARG is a token, the function is called with point just before that token. +A return value of nil always means to fallback on the default behavior, so the +function should return nil for arguments it does not expect. OFFSET can be of the form: `point' align with the token. @@ -886,91 +895,69 @@ NUMBER offset by NUMBER. \(+ OFFSETS...) use the sum of OFFSETS. VARIABLE use the value of VARIABLE as offset. -The precise meaning of `point' depends on various details: it can -either mean the position of the token we're indenting, or the -position of its parent, or the position right after its parent. - -A nil offset for indentation after an opening token defaults -to `smie-indent-basic'.") +This function will often use some of the following functions designed +specifically for it: +`smie-bolp', `smie-hanging-p', `smie-parent-p', `smie-next-p', `smie-prev-p'.") -(defun smie-indent--hanging-p () - ;; A hanging keyword is one that's at the end of a line except it's not at - ;; the beginning of a line. - (and (save-excursion +(defun smie-hanging-p () + "Return non-nil if the current token is \"hanging\". +A hanging keyword is one that's at the end of a line except it's not at +the beginning of a line." + (and (not (smie-bolp)) + (save-excursion (when (zerop (length (funcall smie-forward-token-function))) ;; Could be an open-paren. (forward-char 1)) (skip-chars-forward " \t") - (eolp)) - (not (smie-indent--bolp)))) + (eolp)))) -(defun smie-indent--bolp () +(defun smie-bolp () + "Return non-nil if the current token is the first on the line." (save-excursion (skip-chars-backward " \t") (bolp))) +(defvar smie--parent) (defvar smie--after) ;Dynamically scoped. + +(defun smie-parent-p (&rest parents) + "Return non-nil if the current token's parent is among PARENTS. +Only meaningful when called from within `smie-rules-function'." + (member (nth 2 (or smie--parent + (save-excursion + (let* ((pos (point)) + (tok (funcall smie-forward-token-function))) + (unless (cadr (assoc tok smie-op-levels)) + (goto-char pos)) + (setq smie--parent + (smie-backward-sexp 'halfsexp)))))) + parents)) + +(defun smie-next-p (&rest tokens) + "Return non-nil if the next token is among TOKENS. +Only meaningful when called from within `smie-rules-function'." + (let ((next + (save-excursion + (unless smie--after + (smie-indent-forward-token) (setq smie--after (point))) + (goto-char smie--after) + (smie-indent-forward-token)))) + (member (car next) tokens))) + +(defun smie-prev-p (&rest tokens) + "Return non-nil if the previous token is among TOKENS." + (let ((prev (save-excursion + (smie-indent-backward-token)))) + (member (car prev) tokens))) + + (defun smie-indent--offset (elem) - (or (cdr (assq elem smie-indent-rules)) - (cdr (assq t smie-indent-rules)) + (or (funcall smie-rules-function :elem elem) + (if (not (eq elem 'basic)) + (funcall smie-rules-function :elem 'basic)) smie-indent-basic)) -(defvar smie-indent-debug-log) - -(defun smie-indent--offset-rule (tokinfo &optional after parent) - "Apply the OFFSET-RULES in TOKINFO. -Point is expected to be right in front of the token corresponding to TOKINFO. -If computing the indentation after the token, then AFTER is the position -after the token, otherwise it should be nil. -PARENT if non-nil should be the parent info returned by `smie-backward-sexp'." - (let ((rules (cdr tokinfo)) - next prev - offset) - (while (consp rules) - (let ((rule (pop rules))) - (cond - ((not (consp rule)) (setq offset rule)) - ((eq (car rule) '+) (setq offset rule)) - ((eq (car rule) :hanging) - (when (smie-indent--hanging-p) - (setq rules (cdr rule)))) - ((eq (car rule) :bolp) - (when (smie-indent--bolp) - (setq rules (cdr rule)))) - ((eq (car rule) :eolp) - (unless after - (error "Can't use :eolp in :before indentation rules")) - (when (> after (line-end-position)) - (setq rules (cdr rule)))) - ((eq (car rule) :prev) - (unless prev - (save-excursion - (setq prev (smie-indent-backward-token)))) - (when (equal (car prev) (cadr rule)) - (setq rules (cddr rule)))) - ((eq (car rule) :next) - (unless next - (unless after - (error "Can't use :next in :before indentation rules")) - (save-excursion - (goto-char after) - (setq next (smie-indent-forward-token)))) - (when (equal (car next) (cadr rule)) - (setq rules (cddr rule)))) - ((eq (car rule) :parent) - (unless parent - (save-excursion - (if after (goto-char after)) - (setq parent (smie-backward-sexp 'halfsexp)))) - (when (if (listp (cadr rule)) - (member (nth 2 parent) (cadr rule)) - (equal (nth 2 parent) (cadr rule))) - (setq rules (cddr rule)))) - (t (error "Unknown rule %s for indentation of %s" - rule (car tokinfo)))))) - ;; If `offset' is not set yet, use `rules' to handle the case where - ;; the tokinfo uses the old-style ((PARENT . TOK). OFFSET). - (unless offset (setq offset rules)) - (when (boundp 'smie-indent-debug-log) - (push (list (point) offset tokinfo) smie-indent-debug-log)) - offset)) +(defun smie-indent--rule (kind token &optional after parent) + (let ((smie--parent parent) + (smie--after after)) + (funcall smie-rules-function kind token))) (defun smie-indent--column (offset &optional base parent virtual-point) "Compute the actual column to use for a given OFFSET. @@ -1012,6 +999,9 @@ If VIRTUAL-POINT is non-nil, then `point' is virtual." (if (consp parent) (goto-char (cadr parent))) (smie-indent-virtual)) ((eq offset nil) nil) + ;; FIXME: would be good to get rid of this since smie-rules-function + ;; can usually do the lookup trivially, but in cases where + ;; smie-rules-function returns (+ point VAR) it's not nearly as trivial. ((and (symbolp offset) (boundp 'offset)) (smie-indent--column (symbol-value offset) base parent virtual-point)) (t (error "Unknown indentation offset %s" offset)))) @@ -1046,11 +1036,11 @@ This is used when we're not trying to indent point but just need to compute the column at which point should be indented in order to figure out the indentation of some other (further down) point." ;; Trust pre-existing indentation on other lines. - (if (smie-indent--bolp) (current-column) (smie-indent-calculate))) + (if (smie-bolp) (current-column) (smie-indent-calculate))) (defun smie-indent-fixindent () ;; Obey the `fixindent' special comment. - (and (smie-indent--bolp) + (and (smie-bolp) (save-excursion (comment-normalize-vars) (re-search-forward (concat comment-start-skip @@ -1090,43 +1080,31 @@ in order to figure out the indentation of some other (further down) point." (save-excursion (goto-char pos) ;; Different cases: - ;; - smie-indent--bolp: "indent according to others". + ;; - smie-bolp: "indent according to others". ;; - common hanging: "indent according to others". ;; - SML-let hanging: "indent like parent". ;; - if-after-else: "indent-like parent". ;; - middle-of-line: "trust current position". (cond ((null (cdr toklevels)) nil) ;Not a keyword. - ((smie-indent--bolp) + ((smie-bolp) ;; For an open-paren-like thingy at BOL, always indent only ;; based on other rules (typically smie-indent-after-keyword). nil) (t ;; We're only ever here for virtual-indent, which is why ;; we can use (current-column) as answer for `point'. - (let* ((tokinfo (or (assoc (cons :before token) - smie-indent-rules) + (let* ((offset (or (smie-indent--rule :before token) ;; By default use point unless we're hanging. - `((:before . ,token) (:hanging nil) point))) - ;; (after (prog1 (point) (goto-char pos))) - (offset (smie-indent--offset-rule tokinfo))) + (unless (smie-hanging-p) 'point)))) (smie-indent--column offset))))) ;; FIXME: This still looks too much like black magic!! - ;; FIXME: Rather than a bunch of rules like (PARENT . TOKEN), we - ;; want a single rule for TOKEN with different cases for each PARENT. (let* ((parent (smie-backward-sexp 'halfsexp)) - (tokinfo - (or (assoc (cons (caddr parent) token) - smie-indent-rules) - (assoc (cons :before token) smie-indent-rules) - ;; Default rule. - `((:before . ,token) - ;; (:parent open 0) - point))) (offset (save-excursion (goto-char pos) - (smie-indent--offset-rule tokinfo nil parent)))) + (or (smie-indent--rule :before token nil parent) + 'point)))) ;; Different behaviors: ;; - align with parent. ;; - parent + offset. @@ -1151,10 +1129,10 @@ in order to figure out the indentation of some other (further down) point." nil) ((eq (car parent) (car toklevels)) ;; We bumped into a same-level operator. align with it. - (if (and (smie-indent--bolp) (/= (point) pos) + (if (and (smie-bolp) (/= (point) pos) (save-excursion (goto-char (goto-char (cadr parent))) - (not (smie-indent--bolp))) + (not (smie-bolp))) ;; Check the offset of `token' rather then its parent ;; because its parent may have used a special rule. E.g. ;; function foo; @@ -1190,8 +1168,8 @@ in order to figure out the indentation of some other (further down) point." ;; -> d ;; So as to align with the earliest appropriate place. (smie-indent-virtual))) - (tokinfo - (if (and (= (point) pos) (smie-indent--bolp) + (t + (if (and (= (point) pos) (smie-bolp) (or (eq offset 'point) (and (consp offset) (memq 'point offset)))) ;; Since we started at BOL, we're not computing a virtual @@ -1209,7 +1187,7 @@ in order to figure out the indentation of some other (further down) point." ;; Don't do it for virtual indentations. We should normally never be "in ;; front of a comment" when doing virtual-indentation anyway. And if we are ;; (as can happen in octave-mode), moving forward can lead to inf-loops. - (and (smie-indent--bolp) + (and (smie-bolp) (let ((pos (point))) (save-excursion (beginning-of-line) @@ -1254,27 +1232,18 @@ in order to figure out the indentation of some other (further down) point." (save-excursion (let* ((pos (point)) (toklevel (smie-indent-backward-token)) - (tok (car toklevel)) - (tokinfo (assoc tok smie-indent-rules))) - ;; Set some default indent rules. - (if (and toklevel (null (cadr toklevel)) (null tokinfo)) - (setq tokinfo (list (car toklevel)))) - ;; (if (and tokinfo (null toklevel)) - ;; (error "Token %S has indent rule but has no parsing info" tok)) + (tok (car toklevel))) (when toklevel - (unless tokinfo - ;; The default indentation after a keyword/operator is 0 for - ;; infix and t for prefix. - ;; Using the BNF syntax, we could come up with better - ;; defaults, but we only have the precedence levels here. - (setq tokinfo (list tok 'default-rule - (if (cadr toklevel) 0 (smie-indent--offset t))))) (let ((offset - (or (smie-indent--offset-rule tokinfo pos) - (smie-indent--offset t)))) - (let ((before (point))) + (or (smie-indent--rule :after tok pos) + ;; The default indentation after a keyword/operator is + ;; 0 for infix and t for prefix. + (if (or (null (cadr toklevel)) + (rassoc tok smie-closer-alist)) + (smie-indent--offset 'basic) 0))) + (before (point))) (goto-char pos) - (smie-indent--column offset before))))))) + (smie-indent--column offset before)))))) (defun smie-indent-exps () ;; Indentation of sequences of simple expressions without @@ -1297,13 +1266,14 @@ in order to figure out the indentation of some other (further down) point." arg) (while (and (null (car (smie-backward-sexp))) (push (point) positions) - (not (smie-indent--bolp)))) + (not (smie-bolp)))) (save-excursion ;; Figure out if the atom we just skipped is an argument rather ;; than a function. - (setq arg (or (null (car (smie-backward-sexp))) - (member (funcall smie-backward-token-function) - (cdr (assoc 'list-intro smie-indent-rules)))))) + (setq arg + (or (null (car (smie-backward-sexp))) + (funcall smie-rules-function :list-intro + (funcall smie-backward-token-function))))) (cond ((null positions) ;; We're the first expression of the list. In that case, the @@ -1362,18 +1332,51 @@ to which that point should be aligned, if we were to reindent it.") (save-excursion (indent-line-to indent)) (indent-line-to indent))))) -(defun smie-indent-debug () - "Show the rules used to compute indentation of current line." - (interactive) - (let ((smie-indent-debug-log '())) - (smie-indent-calculate) - ;; FIXME: please improve! - (message "%S" smie-indent-debug-log))) - -(defun smie-setup (op-levels indent-rules) - (set (make-local-variable 'smie-indent-rules) indent-rules) +(defun smie-setup (op-levels rules-function &rest keywords) + "Setup SMIE navigation and indentation. +OP-LEVELS is a grammar table generated by `smie-prec2-levels'. +RULES-FUNCTION is a set of indentation rules for use on `smie-rules-function'. +KEYWORDS are additional arguments, which can use the following keywords: +- :forward-token FUN +- :backward-token FUN" + (set (make-local-variable 'smie-rules-function) rules-function) (set (make-local-variable 'smie-op-levels) op-levels) - (set (make-local-variable 'indent-line-function) 'smie-indent-line)) + (set (make-local-variable 'indent-line-function) 'smie-indent-line) + (set (make-local-variable 'forward-sexp-function) + 'smie-forward-sexp-command) + (while keywords + (let ((k (pop keywords)) + (v (pop keywords))) + (case k + (:forward-token + (set (make-local-variable 'smie-forward-token-function) v)) + (:backward-token + (set (make-local-variable 'smie-backward-token-function) v)) + (t (message "smie-setup: ignoring unknown keyword %s" k))))) + (let ((ca (cdr (assq :smie-closer-alist op-levels)))) + (when ca + (set (make-local-variable 'smie-closer-alist) ca) + ;; Only needed for interactive calls to blink-matching-open. + (set (make-local-variable 'blink-matching-check-function) + #'smie-blink-matching-check) + (add-hook 'post-self-insert-hook + #'smie-blink-matching-open 'append 'local) + (set (make-local-variable 'smie-blink-matching-triggers) + (append smie-blink-matching-triggers + ;; Rather than wait for SPC to blink, try to blink as + ;; soon as we type the last char of a block ender. + (let ((closers (sort (mapcar #'cdr smie-closer-alist) + #'string-lessp)) + (triggers ()) + closer) + (while (setq closer (pop closers)) + (unless (and closers + ;; FIXME: this eliminates prefixes of other + ;; closers, but we should probably elimnate + ;; prefixes of other keywords as well. + (string-prefix-p closer (car closers))) + (push (aref closer (1- (length closer))) triggers))) + (delete-dups triggers))))))) (provide 'smie) diff --git a/lisp/progmodes/octave-mod.el b/lisp/progmodes/octave-mod.el index 40931c3d54d..fdd5e867b7b 100644 --- a/lisp/progmodes/octave-mod.el +++ b/lisp/progmodes/octave-mod.el @@ -446,9 +446,6 @@ Non-nil means always go to the next Octave code line after sending." ;; (fundesc (atom "=" atom)) )) -(defconst octave-smie-closer-alist - (smie-bnf-closer-alist octave-smie-bnf-table)) - (defconst octave-smie-op-levels (smie-prec2-levels (smie-merge-prec2s @@ -521,15 +518,18 @@ Non-nil means always go to the next Octave code line after sending." (t (smie-default-forward-token)))) -(defconst octave-smie-indent-rules - '((";" - (:parent ("function" "if" "while" "else" "elseif" "for" "otherwise" - "case" "try" "catch" "unwind_protect" "unwind_protect_cleanup") - ;; FIXME: don't hardcode 2. - (+ parent octave-block-offset)) - ;; (:parent "switch" 4) ;For (invalid) code between switch and case. - 0) - ((:before . "case") octave-block-offset))) +(defun octave-smie-rules (kind token) + (pcase (cons kind token) + (`(:elem . basic) octave-block-offset) + (`(:before . "case") octave-block-offset) + (`(:after . ";") + (if (smie-parent-p "function" "if" "while" "else" "elseif" "for" + "otherwise" "case" "try" "catch" "unwind_protect" + "unwind_protect_cleanup") + '(+ parent octave-block-offset) + ;; For (invalid) code between switch and case. + ;; (if (smie-parent-p "switch") 4) + 0)))) (defvar electric-indent-chars) @@ -619,32 +619,15 @@ already added. You just need to add a description of the problem, including a reproducible test case and send the message." (setq local-abbrev-table octave-abbrev-table) - (smie-setup octave-smie-op-levels octave-smie-indent-rules) + (smie-setup octave-smie-op-levels #'octave-smie-rules + :forward-token #'octave-smie-forward-token + :backward-token #'octave-smie-backward-token) (set (make-local-variable 'smie-indent-basic) 'octave-block-offset) - (set (make-local-variable 'smie-backward-token-function) - 'octave-smie-backward-token) - (set (make-local-variable 'smie-forward-token-function) - 'octave-smie-forward-token) - (set (make-local-variable 'forward-sexp-function) - 'smie-forward-sexp-command) - (set (make-local-variable 'smie-closer-alist) octave-smie-closer-alist) - ;; Only needed for interactive calls to blink-matching-open. - (set (make-local-variable 'blink-matching-check-function) - #'smie-blink-matching-check) - - (when octave-blink-matching-block - (add-hook 'post-self-insert-hook #'smie-blink-matching-open 'append 'local) + (set (make-local-variable 'smie-blink-matching-triggers) - (append smie-blink-matching-triggers '(\;) - ;; Rather than wait for SPC or ; to blink, try to blink as - ;; soon as we type the last char of a block ender. - ;; But strip ?d from this list so that we don't blink twice - ;; when the user writes "endif" (once at "end" and another - ;; time at "endif"). - (delq ?d (delete-dups - (mapcar (lambda (kw) - (aref (cdr kw) (1- (length (cdr kw))))) - smie-closer-alist)))))) + (cons ?\; smie-blink-matching-triggers)) + (unless octave-blink-matching-block + (remove-hook 'post-self-insert-hook #'smie-blink-matching-open 'local)) (set (make-local-variable 'electric-indent-chars) (cons ?\; electric-indent-chars)) diff --git a/lisp/progmodes/prolog.el b/lisp/progmodes/prolog.el index f3db7fad135..3e388dac56d 100644 --- a/lisp/progmodes/prolog.el +++ b/lisp/progmodes/prolog.el @@ -173,10 +173,11 @@ When nil, send actual operating system end of file." ) "Precedence levels of infix operators.") -(defconst prolog-smie-indent-rules - '((":-") - ("->")) - "Prolog indentation rules.") +(defun prolog-smie-rules (kind token) + (pcase (cons kind token) + (`(:elem . basic) prolog-indent-width) + (`(:after . ".") 0) ;; To work around smie-closer-alist. + (`(:after . ,(or `":-" `"->")) prolog-indent-width))) (defun prolog-mode-variables () (make-local-variable 'paragraph-separate) @@ -185,19 +186,17 @@ When nil, send actual operating system end of file." (setq paragraph-ignore-fill-prefix t) (make-local-variable 'imenu-generic-expression) (setq imenu-generic-expression '((nil "^\\sw+" 0))) - (smie-setup prolog-smie-op-levels prolog-smie-indent-rules) - (set (make-local-variable 'smie-forward-token-function) - #'prolog-smie-forward-token) - (set (make-local-variable 'smie-backward-token-function) - #'prolog-smie-backward-token) - (set (make-local-variable 'forward-sexp-function) - 'smie-forward-sexp-command) - (set (make-local-variable 'smie-indent-basic) prolog-indent-width) + + ;; Setup SMIE. + (smie-setup prolog-smie-op-levels #'prolog-smie-rules + :forward-token #'prolog-smie-forward-token + :backward-token #'prolog-smie-backward-token) (set (make-local-variable 'smie-blink-matching-triggers) '(?.)) (set (make-local-variable 'smie-closer-alist) '((t . "."))) (add-hook 'post-self-insert-hook #'smie-blink-matching-open 'append 'local) ;; There's no real closer in Prolog anyway. (set (make-local-variable 'smie-blink-matching-inners) t) + (make-local-variable 'comment-start) (setq comment-start "%") (make-local-variable 'comment-start-skip) -- 2.39.5