diff options
Diffstat (limited to 'lisp/electric.el')
-rw-r--r-- | lisp/electric.el | 260 |
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, |