;;; smie.el --- Simple Minded Indentation Engine -*- lexical-binding: t -*-
-;; Copyright (C) 2010-2013 Free Software Foundation, Inc.
+;; Copyright (C) 2010-2015 Free Software Foundation, Inc.
;; Author: Stefan Monnier <monnier@iro.umontreal.ca>
;; Keywords: languages, lisp, internal, parsing, indentation
an open-paren, whereas a RIGHT-LEVEL of nil would correspond to something
like a close-paren.")
-(defvar smie-forward-token-function 'smie-default-forward-token
+(defvar smie-forward-token-function #'smie-default-forward-token
"Function to scan forward for the next token.
Called with no argument should return a token and move to its end.
If no token is found, return nil or the empty string.
It can return nil when bumping into a parenthesis, which lets SMIE
use syntax-tables to handle them in efficient C code.")
-(defvar smie-backward-token-function 'smie-default-backward-token
+(defvar smie-backward-token-function #'smie-default-backward-token
"Function to scan backward the previous token.
Same calling convention as `smie-forward-token-function' except
it should move backward to the beginning of the previous token.")
(condition-case err
(progn (funcall next-sexp 1) nil)
(scan-error
- (let ((epos (nth 2 err)))
+ (let* ((epos1 (nth 2 err))
+ (epos (if (<= (point) epos1) (nth 3 err) epos1)))
(goto-char pos)
(throw 'return
(list t epos
nil: we skipped over an identifier, matched parentheses, ..."
(smie-next-sexp
(indirect-function smie-backward-token-function)
- (indirect-function 'backward-sexp)
- (indirect-function 'smie-op-left)
- (indirect-function 'smie-op-right)
+ (indirect-function #'backward-sexp)
+ (indirect-function #'smie-op-left)
+ (indirect-function #'smie-op-right)
halfsexp))
(defun smie-forward-sexp (&optional halfsexp)
nil: we skipped over an identifier, matched parentheses, ..."
(smie-next-sexp
(indirect-function smie-forward-token-function)
- (indirect-function 'forward-sexp)
- (indirect-function 'smie-op-right)
- (indirect-function 'smie-op-left)
+ (indirect-function #'forward-sexp)
+ (indirect-function #'smie-op-right)
+ (indirect-function #'smie-op-left)
halfsexp))
;;; Miscellaneous commands using the precedence parser.
:type 'integer
:group 'smie)
-(defvar smie-rules-function 'ignore
+(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.
- :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.
+- :close-all, in which case ARG is a close-paren token at indentation and
+ the function should return non-nil if it should be aligned with the opener
+ of the last close-paren token on the same line, if there are multiple.
+ Otherwise, it will be aligned with its own opener.
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
The functions whose name starts with \"smie-rule-\" are helper functions
designed specifically for use in this function.")
+(defvar smie--hanging-eolp-function
+ ;; FIXME: This is a quick hack for 24.4. Don't document it and replace with
+ ;; a well-defined function with a cleaner interface instead!
+ (lambda ()
+ (skip-chars-forward " \t")
+ (or (eolp)
+ (and ;; (looking-at comment-start-skip) ;(bug#16041).
+ (forward-comment (point-max))))))
+
(defalias 'smie-rule-hanging-p 'smie-indent--hanging-p)
(defun smie-indent--hanging-p ()
"Return non-nil if the current token is \"hanging\".
(not (eobp))
;; Could be an open-paren.
(forward-char 1))
- (skip-chars-forward " \t")
- (or (eolp)
- (and (looking-at comment-start-skip)
- (forward-comment (point-max))))
+ (funcall smie--hanging-eolp-function)
(point))))))
(defalias 'smie-rule-bolp 'smie-indent--bolp)
(goto-char (cadr (smie-indent--parent)))
(cons 'column
(+ (or offset 0)
- ;; Use smie-indent-virtual when indenting relative to an opener:
- ;; this will also by default use current-column unless
- ;; that opener is hanging, but will additionally consult
- ;; rules-function, so it gives it a chance to tweak
- ;; indentation (e.g. by forcing indentation relative to
- ;; its own parent, as in fn a => fn b => fn c =>).
- (if (or (not (numberp (car smie--parent)))
- (smie-indent--hanging-p))
- (smie-indent-virtual) (current-column))))))
+ (smie-indent-virtual)))))
(defvar smie-rule-separator-outdent 2)
(defun smie-indent--rule (method token
;; FIXME: Too many parameters.
&optional after parent base-pos)
- "Compute indentation column according to `indent-rule-functions'.
-METHOD and TOKEN are passed to `indent-rule-functions'.
+ "Compute indentation column according to `smie-rules-function'.
+METHOD and TOKEN are passed to `smie-rules-function'.
AFTER is the position after TOKEN, if known.
PARENT is the parent info returned by `smie-backward-sexp', if known.
BASE-POS is the position relative to which offsets should be applied."
;; - :after tok, where
;; ; after is set; parent=nil; base-pos=point;
(save-excursion
- (let ((offset
- (let ((smie--parent parent)
- (smie--token token)
- (smie--after after))
- (funcall smie-rules-function method token))))
+ (let ((offset (smie-indent--rule-1 method token after parent)))
(cond
((not offset) nil)
((eq (car-safe offset) 'column) (cdr offset))
(smie-indent-virtual) (current-column)))))
(t (error "Unknown indentation offset %s" offset))))))
+(defun smie-indent--rule-1 (method token &optional after parent)
+ (let ((smie--parent parent)
+ (smie--token token)
+ (smie--after after))
+ (funcall smie-rules-function method token)))
+
(defun smie-indent-forward-token ()
"Skip token forward and return it, along with its levels."
(let ((tok (funcall smie-forward-token-function)))
(save-excursion
;; (forward-comment (point-max))
(when (looking-at "\\s)")
- (while (not (zerop (skip-syntax-forward ")")))
- (skip-chars-forward " \t"))
+ (if (smie-indent--rule-1 :close-all
+ (buffer-substring-no-properties
+ (point) (1+ (point)))
+ (1+ (point)))
+ (while (not (zerop (skip-syntax-forward ")")))
+ (skip-chars-forward " \t"))
+ (forward-char 1))
(condition-case nil
(progn
(backward-sexp 1)
(append smie-blink-matching-triggers
(delete-dups triggers)))))))
+(declare-function edebug-instrument-function "edebug" (func))
+
(defun smie-edebug ()
"Instrument the `smie-rules-function' for Edebug."
(interactive)
(edebug-instrument-function smie-rules-function)
(error "Sorry, don't know how to instrument a lambda expression")))
+(defun smie--next-indent-change ()
+ "Go to the next line that needs to be reindented (and reindent it)."
+ (interactive)
+ (while
+ (let ((tick (buffer-chars-modified-tick)))
+ (indent-according-to-mode)
+ (eq tick (buffer-chars-modified-tick)))
+ (forward-line 1)))
+
;;; User configuration
;; This is designed to be a completely independent "module", so we can play
where KIND and TOKEN are the elements passed to `smie-rules-function',
NORMAL is the value returned by `smie-rules-function' and NEW is the
value with which to replace it."
+ :version "24.4"
+ ;; FIXME improve value-type.
+ :type '(choice (const nil)
+ (alist :key-type symbol))
+ :initialize 'custom-initialize-default
:set #'smie-config--setter)
(defun smie-config-local (rules)
otraces)
;; Finally, guess the indentation rules.
- (let ((ssigs nil)
- (rules nil))
- ;; Sort the sigs by frequency of occurrence.
- (maphash (lambda (sig sig-data) (push (cons sig sig-data) ssigs)) sigs)
- (setq ssigs (sort ssigs (lambda (sd1 sd2) (> (cadr sd1) (cadr sd2)))))
- (while ssigs
- (pcase-let ((`(,sig ,total ,off-alist ,cotraces) (pop ssigs)))
- (cl-assert (= total (apply #'+ (mapcar #'cdr off-alist))))
- (let* ((sorted-off-alist
- (sort off-alist (lambda (x y) (> (cdr x) (cdr y)))))
- (offset (caar sorted-off-alist)))
- (if (zerop offset)
- ;; Nothing to do with this sig; indentation is
- ;; correct already.
- nil
- (push (cons (+ offset (nth 2 sig)) sig) rules)
- ;; Adjust the rest of the data.
- (pcase-dolist ((and cotrace `(,count ,toffset ,trace))
- cotraces)
- (setf (nth 1 cotrace) (- toffset offset))
- (dolist (sig trace)
- (let ((sig-data (cdr (assq sig ssigs))))
- (when sig-data
- (let* ((ooff-data (assq toffset (nth 1 sig-data)))
- (noffset (- toffset offset))
- (noff-data
- (or (assq noffset (nth 1 sig-data))
- (let ((off-data (cons noffset 0)))
- (push off-data (nth 1 sig-data))
- off-data))))
- (cl-assert (>= (cdr ooff-data) count))
- (cl-decf (cdr ooff-data) count)
- (cl-incf (cdr noff-data) count))))))))))
- (message "Guessing...done")
- rules))))
+ (prog1
+ (smie-config--guess-1 sigs)
+ (message "Guessing...done")))))
+
+(defun smie-config--guess-1 (sigs)
+ (let ((ssigs nil)
+ (rules nil))
+ ;; Sort the sigs by frequency of occurrence.
+ (maphash (lambda (sig sig-data) (push (cons sig sig-data) ssigs)) sigs)
+ (setq ssigs (sort ssigs (lambda (sd1 sd2) (> (cadr sd1) (cadr sd2)))))
+ (while ssigs
+ (pcase-let ((`(,sig ,total ,off-alist ,cotraces) (pop ssigs)))
+ (cl-assert (= total (apply #'+ (mapcar #'cdr off-alist))))
+ (let* ((sorted-off-alist
+ (sort off-alist (lambda (x y) (> (cdr x) (cdr y)))))
+ (offset (caar sorted-off-alist)))
+ (if (zerop offset)
+ ;; Nothing to do with this sig; indentation is
+ ;; correct already.
+ nil
+ (push (cons (+ offset (nth 2 sig)) sig) rules)
+ ;; Adjust the rest of the data.
+ (pcase-dolist ((and cotrace `(,count ,toffset . ,trace))
+ cotraces)
+ (setf (nth 1 cotrace) (- toffset offset))
+ (dolist (sig trace)
+ (let ((sig-data (cdr (assq sig ssigs))))
+ (when sig-data
+ (let* ((ooff-data (assq toffset (nth 1 sig-data)))
+ (noffset (- toffset offset))
+ (noff-data
+ (or (assq noffset (nth 1 sig-data))
+ (let ((off-data (cons noffset 0)))
+ (push off-data (nth 1 sig-data))
+ off-data))))
+ (cl-assert (>= (cdr ooff-data) count))
+ (cl-decf (cdr ooff-data) count)
+ (cl-incf (cdr noff-data) count))))))))))
+ rules))
(defun smie-config-guess ()
- "Try and figure out this buffer's indentation settings."
+ "Try and figure out this buffer's indentation settings.
+To save the result for future sessions, use `smie-config-save'."
(interactive)
+ (if (eq smie-grammar 'unset)
+ (user-error "This buffer does not seem to be using SMIE"))
(let ((config (smie-config--guess (point-min) (point-max))))
(cond
((null config) (message "Nothing to change"))
((null smie-config--buffer-local)
- (message "Local rules set")
- (setq smie-config--buffer-local config))
+ (smie-config-local config)
+ (message "Local rules set"))
((y-or-n-p "Replace existing local config? ")
(message "Local rules replaced")
- (setq smie-config--buffer-local config))
+ (smie-config-local config))
((y-or-n-p "Merge with existing local config? ")
(message "Local rules adjusted")
- (setq smie-config--buffer-local
- (append config smie-config--buffer-local)))
+ (smie-config-local (append config smie-config--buffer-local)))
(t
(message "Rules guessed: %S" config)))))
(defun smie-config-save ()
- "Save local rules for use with this major mode."
+ "Save local rules for use with this major mode.
+One way to generate local rules is the command `smie-config-guess'."
(interactive)
(cond
((null smie-config--buffer-local)
(setcdr existing config)
(push (cons major-mode config) smie-config))
(setq smie-config--mode-local config)
- (kill-local-variable smie-config--buffer-local)
+ (kill-local-variable 'smie-config--buffer-local)
(customize-mark-as-set 'smie-config)))))
(provide 'smie)