emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Emacs-diffs] master 949295a: Extend electric-layout-mode to handle more


From: João Távora
Subject: [Emacs-diffs] master 949295a: Extend electric-layout-mode to handle more complex layouts (bug#33794)
Date: Wed, 2 Jan 2019 07:57:13 -0500 (EST)

branch: master
commit 949295ae1a8a79a181b2bf614b9c69849f2fd667
Author: João Távora <address@hidden>
Commit: João Távora <address@hidden>

    Extend electric-layout-mode to handle more complex layouts (bug#33794)
    
    Entries in electric-layout-rules can specify multiple
    newline-related actions which are executed in order of appearance.
    
    Also, have it play nice with electric-pair-mode when inserting a
    newlines, particularly with electric-pair-open-newline-between-pairs.
    
    Entries in electric-layout-rules can also be functions.  Among other
    things, the logic behind electric-pair-open-newline-between-pairs
    could now be moved to electric-layout-mode, but this commit doesn't do
    that yet.
    
    This change was motivated by bug#33794 and is an alternative solution
    to the problem reported in that bug.
    
    * lisp/electric.el (electric-layout-rules): Adjust docstring.
    (electric-layout-post-self-insert-function): Call
    electric-layout-post-self-insert-function-1.
    (electric-layout-post-self-insert-function-1): Rename from
    electric-layout-post-self-insert-function.  Redesign.
    (electric-layout-local-mode): New minor mode.
    
    * test/lisp/electric-tests.el (electric-layout-int-main-kernel-style)
    (electric-layout-int-main-allman-style)
    (electric-modes-in-c-mode-with-self-insert-command)
    (electric-pair-mode-newline-between-parens)
    (electric-layout-mode-newline-between-parens-without-e-p-m)
    (electric-layout-mode-newline-between-parens-without-e-p-m-2): New
    tests.
    (plainer-c-mode): New helper.
---
 lisp/electric.el            | 125 ++++++++++++++++++++++++++++++++------------
 test/lisp/electric-tests.el | 104 ++++++++++++++++++++++++++++++++++++
 2 files changed, 196 insertions(+), 33 deletions(-)

diff --git a/lisp/electric.el b/lisp/electric.el
index 36841bc..e7ebdf5 100644
--- a/lisp/electric.el
+++ b/lisp/electric.el
@@ -363,45 +363,91 @@ 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:
 
-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.")
+* 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.
+
+Instead of the (CHAR . WHERE) form, a rule can also be just a
+function of a single argument, the character just inserted.  It
+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.")
 
 (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
+         probe
+         (rules electric-layout-rules)
+         (rule
+          (catch 'done
+            (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
+                               (or pos (setq pos (electric--after-char-pos))))
+                              (funcall probe last-command-event))))
+                       (when res (throw 'done res)))))))))
     (when (and rule
-               (setq pos (electric--after-char-pos))
+               (or pos (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.
+                  ;;
+                  ;; 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)) (skip-chars-backward " 
\t")
+                                (unless (bolp) (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)
 
@@ -419,6 +465,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
diff --git a/test/lisp/electric-tests.el b/test/lisp/electric-tests.el
index 467abd1..5a4b20e 100644
--- a/test/lisp/electric-tests.el
+++ b/test/lisp/electric-tests.el
@@ -812,5 +812,109 @@ baz\"\""
   :bindings '((comment-start . "<!--") (comment-use-syntax . t))
   :test-in-comments nil :test-in-strings nil)
 
+
+;;; tests for `electric-layout-mode'
+
+(ert-deftest electric-layout-int-main-kernel-style ()
+  (ert-with-test-buffer ()
+    (c-mode)
+    (electric-layout-local-mode 1)
+    (electric-pair-local-mode 1)
+    (electric-indent-local-mode 1)
+    (setq-local electric-layout-rules
+                '((?\{ . (after-stay after))))
+    (insert "int main () ")
+    (let ((last-command-event ?\{))
+      (call-interactively (key-binding `[,last-command-event])))
+    (should (equal (buffer-string) "int main () {\n  \n}"))))
+
+(ert-deftest electric-layout-int-main-allman-style ()
+  (ert-with-test-buffer ()
+    (c-mode)
+    (electric-layout-local-mode 1)
+    (electric-pair-local-mode 1)
+    (electric-indent-local-mode 1)
+    (setq-local electric-layout-rules
+                '((?\{ . (before after-stay after))))
+    (insert "int main () ")
+    (let ((last-command-event ?\{))
+      (call-interactively (key-binding `[,last-command-event])))
+    (should (equal (buffer-string) "int main ()\n{\n  \n}"))))
+
+(define-derived-mode plainer-c-mode c-mode "pC"
+  "A plainer/saner C-mode with no internal electric machinery."
+  (c-toggle-electric-state -1)
+  (setq-local electric-indent-local-mode-hook nil)
+  (setq-local electric-indent-mode-hook nil)
+  (electric-indent-local-mode 1)
+  (dolist (key '(?\" ?\' ?\{ ?\} ?\( ?\) ?\[ ?\]))
+    (local-set-key (vector key) 'self-insert-command)))
+
+(ert-deftest electric-modes-in-c-mode-with-self-insert-command ()
+  (ert-with-test-buffer ()
+    (plainer-c-mode)
+    (electric-layout-local-mode 1)
+    (electric-pair-local-mode 1)
+    (electric-indent-local-mode 1)
+    (setq-local electric-layout-rules
+                '((?\{ . (before after-stay after))))
+    (insert "int main () ")
+    (let ((last-command-event ?\{))
+      (call-interactively (key-binding `[,last-command-event])))
+    (should (equal (buffer-string) "int main ()\n{\n  \n}"))))
+
+(ert-deftest electric-pair-mode-newline-between-parens ()
+  (ert-with-test-buffer ()
+    (plainer-c-mode)
+    (electric-layout-local-mode -1) ;; ensure e-l-m mode is off
+    (electric-pair-local-mode 1)
+    (insert-before-markers "int main () {}")
+    (backward-char 1)
+    (let ((last-command-event ?
))
+      (call-interactively (key-binding `[,last-command-event])))
+    (should (equal (buffer-string) "int main () {\n  \n}"))))
+
+(ert-deftest electric-layout-mode-newline-between-parens-without-e-p-m ()
+  (ert-with-test-buffer ()
+    (plainer-c-mode)
+    (electric-layout-local-mode 1)
+    (electric-pair-local-mode -1) ;; ensure e-p-m mode is off
+    (electric-indent-local-mode 1)
+    (setq-local electric-layout-rules
+                '((?\n
+                   .
+                   (lambda ()
+                     (when (eq (save-excursion
+                                 (skip-chars-backward "\t\s")
+                                 (char-before (1- (point))))
+                               (matching-paren (char-after)))
+                       '(after-stay))))))
+    (insert "int main () {}")
+    (backward-char 1)
+    (let ((last-command-event ?
))
+      (call-interactively (key-binding `[,last-command-event])))
+    (should (equal (buffer-string) "int main () {\n  \n}"))))
+
+(ert-deftest electric-layout-mode-newline-between-parens-without-e-p-m-2 ()
+  (ert-with-test-buffer ()
+    (plainer-c-mode)
+    (electric-layout-local-mode 1)
+    (electric-pair-local-mode -1) ;; ensure e-p-m mode is off
+    (electric-indent-local-mode 1)
+    (setq-local electric-layout-rules
+                '((lambda (char)
+                    (when (and
+                           (eq char ?\n)
+                           (eq (save-excursion
+                                 (skip-chars-backward "\t\s")
+                                 (char-before (1- (point))))
+                               (matching-paren (char-after))))
+                       '(after-stay)))))
+    (insert "int main () {}")
+    (backward-char 1)
+    (let ((last-command-event ?
))
+      (call-interactively (key-binding `[,last-command-event])))
+    (should (equal (buffer-string) "int main () {\n  \n}"))))
+
 (provide 'electric-tests)
 ;;; electric-tests.el ends here



reply via email to

[Prev in Thread] Current Thread [Next in Thread]