summaryrefslogtreecommitdiff
path: root/lisp/electric.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/electric.el')
-rw-r--r--lisp/electric.el260
1 files changed, 183 insertions, 77 deletions
diff --git a/lisp/electric.el b/lisp/electric.el
index 3fc1fbbbcaa..07da2f1d9e7 100644
--- a/lisp/electric.el
+++ b/lisp/electric.el
@@ -198,8 +198,8 @@ relative order must be maintained within it."
(setq-default post-self-insert-hook
(sort (default-value 'post-self-insert-hook)
#'(lambda (fn1 fn2)
- (< (or (get fn1 'priority) 0)
- (or (get fn2 'priority) 0))))))
+ (< (or (if (symbolp fn1) (get fn1 'priority)) 0)
+ (or (if (symbolp fn2) (get fn2 'priority)) 0))))))
;;; Electric indentation.
@@ -223,9 +223,9 @@ Python does not lend itself to fully automatic indentation.")
(defvar electric-indent-functions-without-reindent
'(indent-relative indent-to-left-margin indent-relative-maybe
- py-indent-line coffee-indent-line org-indent-line yaml-indent-line
- haskell-indentation-indent-line haskell-indent-cycle haskell-simple-indent
- yaml-indent-line)
+ indent-relative-first-indent-point py-indent-line coffee-indent-line
+ org-indent-line yaml-indent-line haskell-indentation-indent-line
+ haskell-indent-cycle haskell-simple-indent yaml-indent-line)
"List of indent functions that can't reindent.
If `indent-line-function' is one of those, then `electric-indent-mode' will
not try to reindent lines. It is normally better to make the major
@@ -260,32 +260,42 @@ or comment."
(or (memq act '(nil no-indent))
;; In a string or comment.
(unless (eq act 'do-indent) (nth 8 (syntax-ppss))))))))
- ;; For newline, we want to reindent both lines and basically behave like
- ;; reindent-then-newline-and-indent (whose code we hence copied).
- (let ((at-newline (<= pos (line-beginning-position))))
- (when at-newline
- (let ((before (copy-marker (1- pos) t)))
- (save-excursion
- (unless (or (memq indent-line-function
- electric-indent-functions-without-reindent)
- electric-indent-inhibit)
- ;; Don't reindent the previous line if the indentation function
- ;; is not a real one.
- (goto-char before)
- (indent-according-to-mode))
- ;; We are at EOL before the call to indent-according-to-mode, and
- ;; after it we usually are as well, but not always. We tried to
- ;; address it with `save-excursion' but that uses a normal marker
- ;; whereas we need `move after insertion', so we do the
- ;; save/restore by hand.
- (goto-char before)
- (when (eolp)
- ;; Remove the trailing whitespace after indentation because
- ;; indentation may (re)introduce the whitespace.
- (delete-horizontal-space t)))))
- (unless (and electric-indent-inhibit
- (not at-newline))
- (indent-according-to-mode))))))
+ ;; If we error during indent, silently give up since this is an
+ ;; automatic action that the user didn't explicitly request.
+ ;; But we don't want to suppress errors from elsewhere in *this*
+ ;; function, hence the `condition-case' and `throw' (Bug#18764).
+ (catch 'indent-error
+ ;; For newline, we want to reindent both lines and basically
+ ;; behave like reindent-then-newline-and-indent (whose code we
+ ;; hence copied).
+ (let ((at-newline (<= pos (line-beginning-position))))
+ (when at-newline
+ (let ((before (copy-marker (1- pos) t)))
+ (save-excursion
+ (unless
+ (or (memq indent-line-function
+ electric-indent-functions-without-reindent)
+ electric-indent-inhibit)
+ ;; Don't reindent the previous line if the
+ ;; indentation function is not a real one.
+ (goto-char before)
+ (condition-case-unless-debug ()
+ (indent-according-to-mode)
+ (error (throw 'indent-error nil)))
+ ;; The goal here will be to remove the trailing
+ ;; whitespace after reindentation of the previous line
+ ;; because that may have (re)introduced it.
+ (goto-char before)
+ ;; We were at EOL in marker `before' before the call
+ ;; to `indent-according-to-mode' but after we may
+ ;; not be (Bug#15767).
+ (when (and (eolp))
+ (delete-horizontal-space t))))))
+ (unless (and electric-indent-inhibit
+ (not at-newline))
+ (condition-case-unless-debug ()
+ (indent-according-to-mode)
+ (error (throw 'indent-error nil)))))))))
(put 'electric-indent-post-self-insert-function 'priority 60)
@@ -314,9 +324,6 @@ column specified by the function `current-left-margin'."
;;;###autoload
(define-minor-mode electric-indent-mode
"Toggle on-the-fly reindentation (Electric Indent mode).
-With a prefix argument ARG, enable Electric Indent mode if ARG is
-positive, and disable it otherwise. If called from Lisp, enable
-the mode if ARG is omitted or nil.
When enabled, this reindents whenever the hook `electric-indent-functions'
returns non-nil, or if you insert a character from `electric-indent-chars'.
@@ -355,54 +362,122 @@ use `electric-indent-local-mode'."
(defvar electric-layout-rules nil
"List of rules saying where to automatically insert newlines.
-Each rule has the form (CHAR . WHERE) where CHAR is the char that
-was just inserted and WHERE specifies where to insert newlines
-and can be: nil, `before', `after', `around', `after-stay', or a
-function of no arguments that returns one of those symbols.
+Each rule has the form (CHAR . WHERE), the rule matching if the
+character just inserted was CHAR. WHERE specifies where to
+insert newlines, and can be:
+
+* one of the symbols `before', `after', `around', `after-stay',
+ or nil.
+
+* a list of the preceding symbols, processed in order of
+ appearance to insert multiple newlines;
+
+* a function of no arguments that returns one of the previous
+ values.
+
+Each symbol specifies where, in relation to the position POS of
+the character inserted, the newline character(s) should be
+inserted. `after-stay' means insert a newline after POS but stay
+in the same place.
-The symbols specify where in relation to CHAR the newline
-character(s) should be inserted. `after-stay' means insert a
-newline after CHAR but stay in the same place.")
+Instead of the (CHAR . WHERE) form, a rule can also be just a
+function of a single argument, the character just inserted. It
+is called at that position, and should return a value compatible with
+WHERE if the rule matches, or nil if it doesn't match.
+
+If multiple rules match, only first one is executed.")
+
+;; TODO: Make this a defcustom?
+(defvar electric-layout-allow-duplicate-newlines nil
+ "If non-nil, allow duplication of `before' newlines.")
(defun electric-layout-post-self-insert-function ()
- (let* ((rule (cdr (assq last-command-event electric-layout-rules)))
- pos)
+ (when electric-layout-mode
+ (electric-layout-post-self-insert-function-1)))
+
+;; for edebug's sake, a separate function
+(defun electric-layout-post-self-insert-function-1 ()
+ (let* ((pos (electric--after-char-pos))
+ probe
+ (rules electric-layout-rules)
+ (rule
+ (catch 'done
+ (when pos
+ (while (setq probe (pop rules))
+ (cond ((and (consp probe)
+ (eq (car probe) last-command-event))
+ (throw 'done (cdr probe)))
+ ((functionp probe)
+ (let ((res
+ (save-excursion
+ (goto-char pos)
+ (funcall probe last-command-event))))
+ (when res (throw 'done res))))))))))
(when (and rule
- (setq pos (electric--after-char-pos))
;; Not in a string or comment.
(not (nth 8 (save-excursion (syntax-ppss pos)))))
- (let ((end (point-marker))
- (sym (if (functionp rule) (funcall rule) rule)))
- (set-marker-insertion-type end (not (eq sym 'after-stay)))
- (goto-char pos)
- (pcase sym
- ;; FIXME: we used `newline' down here which called
- ;; self-insert-command and ran post-self-insert-hook recursively.
- ;; It happened to make electric-indent-mode work automatically with
- ;; electric-layout-mode (at the cost of re-indenting lines
- ;; multiple times), but I'm not sure it's what we want.
- ;;
- ;; FIXME: check eolp before inserting \n?
- (`before (goto-char (1- pos)) (skip-chars-backward " \t")
- (unless (bolp) (insert "\n")))
- (`after (insert "\n"))
- (`after-stay (save-excursion
- (let ((electric-layout-rules nil))
- (newline 1 t))))
- (`around (save-excursion
- (goto-char (1- pos)) (skip-chars-backward " \t")
- (unless (bolp) (insert "\n")))
- (insert "\n"))) ; FIXME: check eolp before inserting \n?
- (goto-char end)))))
+ (goto-char pos)
+ (when (functionp rule) (setq rule (funcall rule)))
+ (dolist (sym (if (symbolp rule) (list rule) rule))
+ (let* ((nl-after
+ (lambda ()
+ ;; FIXME: we use `newline', which calls
+ ;; `self-insert-command' and ran
+ ;; `post-self-insert-hook' recursively. It happened
+ ;; to make `electric-indent-mode' work automatically
+ ;; with `electric-layout-mode' (at the cost of
+ ;; re-indenting lines multiple times), but I'm not
+ ;; sure it's what we want.
+ ;;
+ ;; JT@19/02/22: Indeed in the case of `before'
+ ;; newlines, re-indentation is prevented.
+ ;;
+ ;; FIXME: when `newline'ing, we exceptionally
+ ;; prevent a specific behaviour of
+ ;; `eletric-pair-mode', that of opening an extra
+ ;; newline between newly inserted matching paris.
+ ;; In theory that behaviour should be provided by
+ ;; `electric-layout-mode' instead, which should be
+ ;; possible given the current API.
+ ;;
+ ;; FIXME: check eolp before inserting \n?
+ (let ((electric-layout-mode nil)
+ (electric-pair-open-newline-between-pairs nil))
+ (newline 1 t))))
+ (nl-before
+ (lambda ()
+ (save-excursion
+ (goto-char (1- pos))
+ ;; Normally, we don't duplicate newlines, but when
+ ;; we're being called for i.e. a closer brace for
+ ;; `electric-pair-mode' generally make sense. So
+ ;; consult `electric-layout-allow-duplicate-newlines'
+ (unless (and (not electric-layout-allow-duplicate-newlines)
+ (progn (skip-chars-backward " \t")
+ (bolp)))
+ ;; FIXME: JT@19/03/22: Make sure the `before'
+ ;; newline being inserted here does not trigger
+ ;; reindentation. It doesn't seem to be our job
+ ;; to do so and it break with `cc-mode's
+ ;; indentation function. Later on we can add a
+ ;; before-and-maybe-indent, or if the user
+ ;; really wants to reindent, then
+ ;; `last-command-event' should be in
+ ;; `electric-indent-chars'.
+ (let ((electric-indent-inhibit t))
+ (funcall nl-after)))))))
+ (pcase sym
+ ('before (funcall nl-before))
+ ('after (funcall nl-after))
+ ('after-stay (save-excursion (funcall nl-after)))
+ ('around (funcall nl-before) (funcall nl-after))))))))
(put 'electric-layout-post-self-insert-function 'priority 40)
;;;###autoload
(define-minor-mode electric-layout-mode
"Automatically insert newlines around some chars.
-With a prefix argument ARG, enable Electric Layout mode if ARG is
-positive, and disable it otherwise. If called from Lisp, enable
-the mode if ARG is omitted or nil.
+
The variable `electric-layout-rules' says when and how to insert newlines."
:global t :group 'electricity
(cond (electric-layout-mode
@@ -413,6 +488,19 @@ The variable `electric-layout-rules' says when and how to insert newlines."
(remove-hook 'post-self-insert-hook
#'electric-layout-post-self-insert-function))))
+;;;###autoload
+(define-minor-mode electric-layout-local-mode
+ "Toggle `electric-layout-mode' only in this buffer."
+ :variable (buffer-local-value 'electric-layout-mode (current-buffer))
+ (cond
+ ((eq electric-layout-mode (default-value 'electric-layout-mode))
+ (kill-local-variable 'electric-layout-mode))
+ ((not (default-value 'electric-layout-mode))
+ ;; Locally enabled, but globally disabled.
+ (electric-layout-mode 1) ; Setup the hooks.
+ (setq-default electric-layout-mode nil) ; But keep it globally disabled.
+ )))
+
;;; Electric quoting.
(defcustom electric-quote-comment t
@@ -451,6 +539,14 @@ whitespace, opening parenthesis, or quote and leaves \\=` alone."
:version "26.1"
:type 'boolean :safe #'booleanp :group 'electricity)
+(defcustom electric-quote-replace-double nil
+ "Non-nil means to replace \" with an electric double quote.
+Emacs replaces \" with an opening double quote after a line
+break, whitespace, opening parenthesis, or quote, and with a
+closing double quote otherwise."
+ :version "26.1"
+ :type 'boolean :safe #'booleanp :group 'electricity)
+
(defvar electric-quote-inhibit-functions ()
"List of functions that should inhibit electric quoting.
When the variable `electric-quote-mode' is non-nil, Emacs will
@@ -461,13 +557,17 @@ substitution is inhibited. The functions are called after the
after the inserted character. The functions in this hook should
not move point or change the current buffer.")
+(defvar electric-pair-text-pairs)
+
(defun electric-quote-post-self-insert-function ()
"Function that `electric-quote-mode' adds to `post-self-insert-hook'.
This requotes when a quoting key is typed."
(when (and electric-quote-mode
(or (eq last-command-event ?\')
(and (not electric-quote-context-sensitive)
- (eq last-command-event ?\`)))
+ (eq last-command-event ?\`))
+ (and electric-quote-replace-double
+ (eq last-command-event ?\")))
(not (run-hook-with-args-until-success
'electric-quote-inhibit-functions))
(if (derived-mode-p 'text-mode)
@@ -488,9 +588,12 @@ This requotes when a quoting key is typed."
(save-excursion
(let ((backtick ?\`))
(if (or (eq last-command-event ?\`)
- (and electric-quote-context-sensitive
+ (and (or electric-quote-context-sensitive
+ (and electric-quote-replace-double
+ (eq last-command-event ?\")))
(save-excursion
(backward-char)
+ (skip-syntax-backward "\\")
(or (bobp) (bolp)
(memq (char-before) (list q< q<<))
(memq (char-syntax (char-before))
@@ -506,22 +609,25 @@ This requotes when a quoting key is typed."
(setq last-command-event q<<))
((search-backward (string backtick) (1- (point)) t)
(replace-match (string q<))
- (setq last-command-event q<)))
+ (setq last-command-event q<))
+ ((search-backward "\"" (1- (point)) t)
+ (replace-match (string q<<))
+ (setq last-command-event q<<)))
(cond ((search-backward (string q> ?') (- (point) 2) t)
(replace-match (string q>>))
(setq last-command-event q>>))
((search-backward "'" (1- (point)) t)
(replace-match (string q>))
- (setq last-command-event q>))))))))))
+ (setq last-command-event q>))
+ ((search-backward "\"" (1- (point)) t)
+ (replace-match (string q>>))
+ (setq last-command-event q>>))))))))))
(put 'electric-quote-post-self-insert-function 'priority 10)
;;;###autoload
(define-minor-mode electric-quote-mode
"Toggle on-the-fly requoting (Electric Quote mode).
-With a prefix argument ARG, enable Electric Quote mode if
-ARG is positive, and disable it otherwise. If called from Lisp,
-enable the mode if ARG is omitted or nil.
When enabled, as you type this replaces \\=` with ‘, \\=' with ’,
\\=`\\=` with “, and \\='\\=' with ”. This occurs only in comments, strings,