├── .ert-runner
├── .gitignore
├── Cask
├── Makefile
├── README.org
├── TODO.org
├── multi-line-candidate.el
├── multi-line-cycle.el
├── multi-line-decorator.el
├── multi-line-enter.el
├── multi-line-find.el
├── multi-line-highlight.el
├── multi-line-respace.el
├── multi-line-shared.el
├── multi-line.el
└── test
├── multi-line-elisp-test.el
├── multi-line-python-test.el
├── multi-line-ruby-test.el
└── multi-line-test.el
/.ert-runner:
--------------------------------------------------------------------------------
1 | -L .
2 | -l ./test/multi-line-test.el
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.elc
2 | /.cask
3 | /README.html
4 |
--------------------------------------------------------------------------------
/Cask:
--------------------------------------------------------------------------------
1 | (source gnu)
2 | (source melpa)
3 |
4 | (package-file "multi-line.el")
5 | (depends-on "ert-runner")
6 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | CASK = cask
2 |
3 | SRCS = $(shell find . -maxdepth 1 -name '*.el')
4 | OBJECTS = $(SRCS:.el=.elc)
5 | EMACS = $(shell sh -c 'type -P emacs')
6 |
7 | .PHONY: test compile clean install
8 |
9 | .cask:
10 | $(CASK) install
11 |
12 | $(OBJECTS): .cask
13 | $(CASK) build
14 |
15 | compile: $(OBJECTS)
16 |
17 | install: compile
18 |
19 | clean-elc:
20 | rm -f $(OBJECTS)
21 |
22 | clean: clean-elc
23 | rm -rf .cask/
24 |
25 | test: $(OBJECTS)
26 | cask exec ert-runner -L $(PWD)
27 |
--------------------------------------------------------------------------------
/README.org:
--------------------------------------------------------------------------------
1 | # -*- mode: org; -*-
2 |
3 | #+HTML_HEAD:
4 | #+HTML_HEAD:
5 |
6 | #+HTML_HEAD:
7 | #+HTML_HEAD:
8 | #+HTML_HEAD:
9 | #+HTML_HEAD:
10 |
11 | [[https://travis-ci.org/IvanMalison/multi-line][file:https://travis-ci.org/IvanMalison/multi-line.svg?branch=master]]
12 | [[http://melpa.org/#/multi-line][file:https://melpa.org/packages/multi-line-badge.svg]]
13 | [[https://stable.melpa.org/#/multi-line][file:https://stable.melpa.org/packages/multi-line-badge.svg]]
14 |
15 | * Demo
16 | [[https://asciinema.org/a/dwft2l94f75x9l46wmdhbm5lh?t=4][https://asciinema.org/a/dwft2l94f75x9l46wmdhbm5lh.png]]
17 |
18 | * About
19 | multi-line aims to provide a flexible framework for automatically
20 | multi-lining and single-lining function invocations and definitions,
21 | array and map literals and more. It relies on functions that are
22 | defined on a per major mode basis wherever it can so that it operates
23 | correctly across many different programming languages.
24 |
25 | * Supported Languages
26 | The following languages are officially supported by multi-line
27 | - C/C++
28 | - clojure
29 | - emacs-lisp
30 | - golang
31 | - java
32 | - javascript
33 | - python
34 | - ruby
35 | - rust
36 | - scala
37 |
38 | It is likely that multi-line will function pretty well in any language using
39 | typical brace/parenthesis/comma syntax, provided that the major mode has
40 | properly defined ~forward-sexp~ and ~indent-line~. If you find that multi-line
41 | works well without modification in your language of choice please file an issue
42 | or submit a pull request to have it added to the list of officially supported
43 | languages.
44 | * Installation
45 | Install from MELPA with ~M-x package-install multi-line~. See the [[https://github.com/milkypostman/melpa][melpa
46 | repository]] for details about how to set up MELPA if you have not already done
47 | so.
48 | * Setup
49 | #+BEGIN_SRC emacs-lisp
50 | (require 'multi-line)
51 | (global-set-key (kbd "C-c d") 'multi-line)
52 | #+END_SRC
53 | * Usage
54 | Execute the ~multi-line~ command (with M-x or a keybinding) inside the body of
55 | the expression you wish to multi-line, as show in the demo above. Invoking
56 | multi-line multiple times on the same definition will cycle between the
57 | different respacing styles that are configured for the current buffer.
58 |
59 | When invoked with a prefix argument (~C-u~), multi-line will "single-line" the
60 | expression at point, removing all new lines and replacing them with spaces (or
61 | the character configured for single lining).
62 | * Configuration
63 | multi-line can be configured to behave differently depending on the major mode
64 | of the current-buffer. The behavior of multi-line is described with a
65 | multi-line-strategy object that has three components: a find-strategy, an
66 | enter-strategy and a replace-strategy. The ~multi-line-defhook~ macro can be
67 | used to set a major mode specific multi-lining strategy. The strategy defined
68 | with the defhook will become active in any buffers with the specified mode.
69 |
70 | In the following example we set the default multi-lining behavior for the
71 | clojure programming language.
72 |
73 | #+BEGIN_SRC emacs-lisp
74 | (multi-line-defhook clojure
75 | (make-instance multi-line-strategy
76 | :find (make-instance multi-line-forward-sexp-find-strategy
77 | :split-regex "[[:space:]\n]+"
78 | :done-regex "[[:space:]]*)]}"
79 | :split-advance-fn 'multi-line-lisp-advance-fn)
80 | :respace multi-line-lisp-respacer))
81 | #+END_SRC
82 |
83 | This expands to
84 |
85 | #+BEGIN_SRC emacs-lisp
86 | (eval-and-compile
87 | (defvar multi-line-clojure-strategy)
88 | (setq multi-line-clojure-strategy
89 | (make-instance multi-line-strategy
90 | :find
91 | (make-instance multi-line-forward-sexp-find-strategy
92 | :split-regex "[[:space:]]+"
93 | :done-regex "[[:space:]]*)]}"
94 | :split-advance-fn 'multi-line-lisp-advance-fn)
95 | :respace multi-line-lisp-respacer))
96 | (defun multi-line-clojure-mode-hook nil
97 | (setq-local multi-line-current-strategy multi-line-clojure-strategy))
98 | (add-hook 'clojure-mode-hook 'multi-line-clojure-mode-hook t))
99 | #+END_SRC
100 |
101 | Users will most often want to configure the respacing portion of the multi-line
102 | strategy. If you prefer to always add a newline at every available split point
103 | you might set up the default multi-line strategy as follows:
104 |
105 | #+BEGIN_SRC emacs-lisp
106 | (setq-default multi-line-current-strategy
107 | (multi-line-strategy
108 | :respace (multi-line-default-respacers
109 | (make-instance multi-line-always-newline))))
110 | #+END_SRC
111 | ** Built-in Mode Specific Behavior
112 | multi-line has some built in mode specific behavior that is enabled by default.
113 | The interactive function ~multi-line-disable-mode-hooks~ disables this mode
114 | specific behavior.
115 |
--------------------------------------------------------------------------------
/TODO.org:
--------------------------------------------------------------------------------
1 | * DONE Account for characters AFTER last respace position when respacing to a fill column
2 | :LOGBOOK:
3 | - State "DONE" from "TODO" [2016-08-24 Wed 16:24]
4 | - State "TODO" from [2016-08-22 Mon 15:50]
5 | :END:
6 | * DONE Handle forced grouping of grouped multi-line targets as with keywords in lisp
7 | :LOGBOOK:
8 | - State "DONE" from "TODO" [2016-08-26 Fri 23:55]
9 | - State "TODO" from [2016-08-22 Mon 15:51]
10 | :END:
11 | #+BEGIN_SRC emacs-lisp
12 | (multi-line-forward-sexp-find-strategy
13 | :split-regex "[[:space:]\n]+"
14 | :done-regex "[[:space:]]*)}]"
15 | :split-advance-fn 'multi-line-lisp-advance-fn)
16 | #+END_SRC
17 | * DONE Cycle through different respace strategies with multiple multi-line invocations
18 | :LOGBOOK:
19 | - State "DONE" from "TODO" [2016-08-22 Mon 17:12]
20 | - State "TODO" from [2016-08-22 Mon 15:52]
21 | :END:
22 | * DONE Handle applying multi-line strategies in derived modes
23 | :LOGBOOK:
24 | - State "DONE" from "TODO" [2016-08-23 Tue 13:58]
25 | - State "TODO" from [2016-08-22 Mon 16:03]
26 | :END:
27 | Should multi-line-strategy just be made buffer local?
28 | Seems like that would make things much simpler
29 | * TODO "Aggressive" fill multi-lining
30 | :LOGBOOK:
31 | - State "TODO" from [2016-08-22 Mon 20:56]
32 | :END:
33 | ...Where a new-line is inserted before not doing so would exceed the fill column to avoid situations like where multi-lining becomes very ineffective
34 | #+BEGIN_SRC emacs-lisp
35 | (org-projectile:update-project-to-org-filepath-fdsafd-fds "multi-line"
36 | (imalison:join-paths "~"))
37 | #+END_SRC
38 |
39 | The "aggresive" version would looke like
40 |
41 | #+BEGIN_SRC emacs-lisp
42 | (org-projectile:update-project-to-org-filepath-fdsafd-fds
43 | "multi-line" (imalison:join-paths "~"))
44 | #+END_SRC
45 | * TODO Recursive multi-lining
46 | :LOGBOOK:
47 | - State "TODO" from [2016-08-22 Mon 21:02]
48 | :END:
49 | Ex:
50 | #+BEGIN_SRC emacs-lisp
51 | (my-function-name "a long ass string" "more stuff" (afdasfdsa afdsafdsa afdsafdsa afdsafa afdfdsa "more crap" "crap"))
52 | #+END_SRC
53 | To:
54 | #+BEGIN_SRC emacs-lisp
55 | (my-function-name "a long ass string" "more stuff"
56 | (afdasfdsa afdsafdsa afdsafdsa afdsafa afdfdsa
57 | "more crap" "crap"))
58 | #+END_SRC
59 | Instead of:
60 | #+BEGIN_SRC emacs-lisp
61 | (my-function-name "a long ass string" "more stuff"
62 | (afdasfdsa afdsafdsa afdsafdsa afdsafa afdfdsa "more crap" "crap"))
63 | #+END_SRC
64 | * DONE Fill column does not seem to work properly
65 | :PROPERTIES:
66 | :CREATED: [2015-11-27 Fri 11:58]
67 | :END:
68 | :LOGBOOK:
69 | - State "DONE" from "TODO" [2016-08-24 Wed 16:23]
70 | - State "TODO" from [2015-11-27 Fri 11:58]
71 | :END:
72 | ...in situations where the next separator mark is not on the same line as the multi-lining line because it looks at the column number position at the separator (usually ,)
73 | * DONE Fill respacer does not consider characters from things that follow the end of the respaced definition
74 | :PROPERTIES:
75 | :CREATED: [2015-11-23 Mon 17:59]
76 | :END:
77 | :LOGBOOK:
78 | - State "DONE" from "TODO" [2016-08-24 Wed 16:24]
79 | - Note taken on [2015-11-30 Mon 20:24] \\
80 | Here is an example:
81 |
82 | #+BEGIN_SRC go
83 | func (a *Account) paramsToStateChange(newState ordert.AccountState) *db.AccountStateChange {
84 | }
85 | #+END_SRC
86 | - State "TODO" from [2015-11-23 Mon 17:59]
87 | :END:
88 | * TODO Being inside of quotes can mess things up in python mode
89 | :PROPERTIES:
90 | :CREATED: [2015-11-23 Mon 13:03]
91 | :END:
92 | :LOGBOOK:
93 | - State "TODO" from [2015-11-23 Mon 13:03]
94 | :END:
95 | * DONE ruby-map literals
96 | :PROPERTIES:
97 | :CREATED: [2015-11-29 Sun 23:59]
98 | :END:
99 | :LOGBOOK:
100 | - State "DONE" from "TODO" [2016-08-26 Fri 23:56]
101 | - State "TODO" from [2015-11-29 Sun 23:59]
102 | :END:
103 | * TODO Apply hooks to existing buffers to make the right strategies active
104 | :LOGBOOK:
105 | - State "TODO" from "TODO" [2016-08-27 Sat 12:31]
106 | :END:
107 | * TODO Pairing on : keywords is too aggressive, what if the :keyword is actually an argument
108 | :LOGBOOK:
109 | - State "TODO" from [2016-08-27 Sat 12:31]
110 | :END:
111 |
--------------------------------------------------------------------------------
/multi-line-candidate.el:
--------------------------------------------------------------------------------
1 | ;;; multi-line-candidate.el --- multi-line statements -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2015-2023 Ivan Malison
4 |
5 | ;; This program is free software; you can redistribute it and/or modify
6 | ;; it under the terms of the GNU General Public License as published by
7 | ;; the Free Software Foundation, either version 3 of the License, or
8 | ;; (at your option) any later version.
9 |
10 | ;; This program is distributed in the hope that it will be useful,
11 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | ;; GNU General Public License for more details.
14 |
15 | ;; You should have received a copy of the GNU General Public License
16 | ;; along with this program. If not, see .
17 |
18 | ;;; Commentary:
19 |
20 | ;; multi-line-candidate defines a wrapper object for a marker with some
21 | ;; additional context
22 |
23 | ;;; Code:
24 |
25 | (require 'cl-lib)
26 | (require 'eieio)
27 |
28 | (defclass multi-line-candidate ()
29 | ((marker :initarg :marker :initform (point-marker))
30 | (original-spacing :initarg :original-spacing :initform nil)))
31 |
32 | (cl-defmethod multi-line-candidate-position ((candidate multi-line-candidate))
33 | (marker-position (oref candidate marker)))
34 |
35 | (provide 'multi-line-candidate)
36 | ;;; multi-line-candidate.el ends here
37 |
--------------------------------------------------------------------------------
/multi-line-cycle.el:
--------------------------------------------------------------------------------
1 | ;;; multi-line-cycle.el --- multi-line statements -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2015-2023 Ivan Malison
4 |
5 | ;; This program is free software; you can redistribute it and/or modify
6 | ;; it under the terms of the GNU General Public License as published by
7 | ;; the Free Software Foundation, either version 3 of the License, or
8 | ;; (at your option) any later version.
9 |
10 | ;; This program is distributed in the hope that it will be useful,
11 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | ;; GNU General Public License for more details.
14 |
15 | ;; You should have received a copy of the GNU General Public License
16 | ;; along with this program. If not, see .
17 |
18 | ;;; Commentary:
19 |
20 | ;; multi-line-cycle defines a respacer object that dispatch to various
21 | ;; other respacers based on context and the previous user action.
22 |
23 | ;;; Code:
24 |
25 | (require 'cl-lib)
26 | (require 'eieio)
27 |
28 | (require 'multi-line-candidate)
29 |
30 | ;; This variable is for internal use only
31 | (defvar multi-line-last-cycler nil)
32 |
33 | (defclass multi-line-cycle-respacer ()
34 | ((respacers :initarg :respacers)
35 | (named-respacers :initarg :named-respacers :initform nil)
36 | (last-cycle-marker :initform nil)
37 | (cycle-index :initform 0)
38 | (command-at-last-cycle :initform nil)
39 | (check-markers :initform t :initarg check-markers)
40 | (check-last-command :initform nil :initarg check-last-command)))
41 |
42 | (cl-defmethod multi-line-respace ((cycler multi-line-cycle-respacer) candidates
43 | &optional context)
44 | (let* ((respacer-name (plist-get context :respacer-name))
45 | (respacer-index (plist-get context :respacer-index))
46 | (respacer
47 | (or (plist-get (oref cycler named-respacers)
48 | respacer-name)
49 | (when respacer-index
50 | (nth respacer-index (oref cycler respacers))))))
51 | (if respacer
52 | ;; We want to reset in this case because something has been selected
53 | ;; explicitly.
54 | (multi-line-cycler-reset cycler nil)
55 | ;; Pass in the first marker that was found so that we get consistent
56 | ;; cycling behavior even if the cursor is moved.
57 | (setq respacer (multi-line-cycle cycler (oref (car candidates) marker))))
58 | (multi-line-respace respacer candidates context)))
59 |
60 | (cl-defmethod multi-line-cycle ((cycler multi-line-cycle-respacer) current-marker)
61 | (if (and (eq multi-line-last-cycler cycler)
62 | (or (not (oref cycler check-last-command))
63 | (equal (oref cycler command-at-last-cycle) last-command))
64 | (or (not (oref cycler check-markers))
65 | (let ((last-marker (oref cycler last-cycle-marker)))
66 | (equal current-marker last-marker))))
67 | (multi-line-increment-cycle-index cycler)
68 | (multi-line-cycler-reset cycler current-marker))
69 | (multi-line-current-respacer cycler))
70 |
71 | (cl-defmethod multi-line-current-respacer ((cycler multi-line-cycle-respacer))
72 | (nth (oref cycler cycle-index) (oref cycler respacers)))
73 |
74 | (cl-defmethod multi-line-cycler-reset ((cycler multi-line-cycle-respacer) current-marker)
75 | (oset cycler last-cycle-marker current-marker)
76 | (oset cycler cycle-index 0)
77 | (oset cycler command-at-last-cycle this-command)
78 | (setq multi-line-last-cycler cycler))
79 |
80 | (cl-defmethod multi-line-increment-cycle-index ((cycler multi-line-cycle-respacer)
81 | &optional amount)
82 | (unless amount (setq amount 1))
83 | (oset cycler cycle-index
84 | (% (+ (oref cycler cycle-index) amount)
85 | (length (oref cycler respacers)))))
86 |
87 | (defun multi-line-build-from-respacers-list (respacers-list)
88 | (let* ((named-respacers nil)
89 | (respacers
90 | (cl-loop for respacer-spec in respacers-list
91 | collect (pcase respacer-spec
92 | (`(,name . ,respacer)
93 | (setq named-respacers
94 | (plist-put named-respacers name respacer))
95 | respacer)
96 | (_ respacer-spec)))))
97 | (make-instance 'multi-line-cycle-respacer :respacers respacers
98 | :named-respacers named-respacers)))
99 |
100 | (provide 'multi-line-cycle)
101 | ;;; multi-line-cycle.el ends here
102 |
--------------------------------------------------------------------------------
/multi-line-decorator.el:
--------------------------------------------------------------------------------
1 | ;;; multi-line-decorator.el --- multi-line statements -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2015-2023 Ivan Malison
4 |
5 | ;; This program is free software; you can redistribute it and/or modify
6 | ;; it under the terms of the GNU General Public License as published by
7 | ;; the Free Software Foundation, either version 3 of the License, or
8 | ;; (at your option) any later version.
9 |
10 | ;; This program is distributed in the hope that it will be useful,
11 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | ;; GNU General Public License for more details.
14 |
15 | ;; You should have received a copy of the GNU General Public License
16 | ;; along with this program. If not, see .
17 |
18 | ;;; Commentary:
19 |
20 | ;; multi-line-decorator defines a collection of decorator respacers
21 | ;; that can be used to add behavior to existing respacers.
22 |
23 | ;;; Code:
24 |
25 | (require 'cl-lib)
26 | (require 'eieio)
27 | (require 'shut-up)
28 |
29 | (require 'multi-line-candidate)
30 | (require 'multi-line-respace)
31 | (require 'multi-line-shared)
32 |
33 | (put 'multi-line-pre-decorator 'lisp-indent-function 'defun)
34 | (put 'multi-line-post-decorator 'lisp-indent-function 'defun)
35 | (put 'multi-line-post-all-decorator 'lisp-indent-function 'defun)
36 |
37 | (defmacro multi-line-compose (name &rest funcs)
38 | "Build a new function with NAME that is the composition of FUNCS."
39 | `(defun ,name (arg)
40 | (multi-line-compose-helper ,funcs)))
41 |
42 | (defmacro multi-line-compose-helper (funcs)
43 | "Builds funcalls of FUNCS applied to the arg."
44 | (if (equal (length funcs) 0)
45 | (quote arg)
46 | `(funcall ,(car funcs) (multi-line-compose-helper ,(cdr funcs)))))
47 |
48 | ;; TODO: Get rid of inheritance here
49 | (defclass multi-line-each-decorator (multi-line-respacer)
50 | ((respacer :initarg :respacer)
51 | (decorator :initarg :decorator)))
52 |
53 | (cl-defmethod multi-line-respace-one ((decorator multi-line-each-decorator)
54 | index candidates)
55 | (funcall (oref decorator decorator) (oref decorator respacer) index
56 | candidates))
57 |
58 | (defmacro multi-line-pre-decorator (name &rest forms)
59 | "Build respacer decorator NAME that execute FORMS before each respacing.
60 |
61 | FORMS can use the variables index and candidates which will be
62 | appropriately populated by the executor."
63 | `(defun ,name (decorated-respacer)
64 | (make-instance 'multi-line-each-decorator
65 | :respacer decorated-respacer
66 | :decorator (lambda (respacer index candidates)
67 | ,@forms
68 | (multi-line-respace-one respacer index candidates)))))
69 |
70 | (defmacro multi-line-post-decorator (name &rest forms)
71 | "Build respacer decorator NAME that execute FORMS after each respacing.
72 |
73 | FORMS can use the variables index and candidates which will be
74 | appropriately populated by the executor."
75 | `(defun ,name (respacer)
76 | (make-instance 'multi-line-each-decorator
77 | :respacer respacer
78 | :decorator (lambda (respacer index candidates)
79 | (multi-line-respace-one respacer index candidates)
80 | ,@forms))))
81 |
82 | (defmacro multi-line-post-all-decorator (name &rest forms)
83 | "Build respacer decorator NAME that execute FORMS after all respacing.
84 |
85 | FORMS can use the variables index and candidates which will be
86 | appropriately populated by the executor."
87 | `(multi-line-post-decorator
88 | ,name (when (equal index (- (length candidates) 1))
89 | (goto-char (multi-line-candidate-position (car (last candidates))))
90 | ,@forms)))
91 |
92 | (multi-line-pre-decorator multi-line-space-clearing-respacer
93 | (multi-line-clear-whitespace-at-point))
94 |
95 | (multi-line-post-all-decorator multi-line-trailing-comma-respacer
96 | (multi-line-add-remove-or-leave-final-comma))
97 |
98 | (multi-line-post-decorator
99 | multi-line-reindenting-respacer
100 | (shut-up
101 | (indent-region (multi-line-candidate-position (car candidates))
102 | (multi-line-candidate-position (car (last candidates))))))
103 |
104 | (multi-line-post-decorator
105 | multi-line-per-line-reindenting-respacer
106 | (shut-up
107 | (let ((start-position (multi-line-candidate-position (car candidates)))
108 | (end-position (multi-line-candidate-position (car (last candidates))))
109 | last-reindent)
110 | (save-excursion
111 | (goto-char start-position)
112 | (while (and (<= (point) end-position) (or (not last-reindent) (> (point) last-reindent)))
113 | (setq last-reindent (point))
114 | (funcall indent-line-function)
115 | (forward-line)
116 | (move-beginning-of-line nil))))))
117 |
118 | (multi-line-compose multi-line-clearing-reindenting-respacer
119 | 'multi-line-reindenting-respacer
120 | 'multi-line-space-clearing-respacer)
121 |
122 | (defclass multi-line-space-restoring-respacer ()
123 | ((respacer :initarg :respacer)))
124 |
125 | (cl-defmethod multi-line-respace-one ((respacer multi-line-space-restoring-respacer)
126 | index candidates)
127 | (cl-destructuring-bind (startm . endm) (multi-line-space-markers)
128 | (let* ((start (marker-position startm))
129 | (end (marker-position endm))
130 | (spanning-start (progn
131 | (goto-char (- start 1))
132 | (point-marker)))
133 | (spanning-end (progn
134 | (goto-char (+ end 1))
135 | (point-marker)))
136 | (space-to-restore (buffer-substring start end)))
137 | (delete-region start end)
138 | (let ((spanning-string (buffer-substring (marker-position spanning-start)
139 | (marker-position spanning-end))))
140 | (multi-line-respace-one (oref respacer respacer) index candidates)
141 | (when (equal (buffer-substring (marker-position spanning-start)
142 | (marker-position spanning-end))
143 | spanning-string)
144 | (goto-char (marker-position startm))
145 | (insert space-to-restore))))))
146 |
147 | (defun multi-line-restoring-reindenting-respacer (respacer)
148 | (multi-line-reindenting-respacer
149 | (multi-line-space-restoring-respacer :respacer respacer)))
150 |
151 | (provide 'multi-line-decorator)
152 | ;;; multi-line-decorator.el ends here
153 |
--------------------------------------------------------------------------------
/multi-line-enter.el:
--------------------------------------------------------------------------------
1 | ;;; multi-line-enter.el --- multi-line statements -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2015-2023 Ivan Malison
4 |
5 | ;; This program is free software; you can redistribute it and/or modify
6 | ;; it under the terms of the GNU General Public License as published by
7 | ;; the Free Software Foundation, either version 3 of the License, or
8 | ;; (at your option) any later version.
9 |
10 | ;; This program is distributed in the hope that it will be useful,
11 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | ;; GNU General Public License for more details.
14 |
15 | ;; You should have received a copy of the GNU General Public License
16 | ;; along with this program. If not, see .
17 |
18 | ;;; Commentary:
19 |
20 | ;; multi-line-enter defines various approaches for going to the
21 | ;; beginning of a statement that can be multi-lined.
22 |
23 | ;;; Code:
24 |
25 | (require 'cl-lib)
26 | (require 'eieio)
27 | (require 'multi-line-shared)
28 |
29 | (defclass multi-line-up-list-enter-strategy ()
30 | ((skip-chars :initarg :skip-chars :initform nil)))
31 |
32 | (cl-defmethod multi-line-enter ((enter multi-line-up-list-enter-strategy)
33 | &optional _context)
34 | (multi-line-up-list-back)
35 | (when (oref enter skip-chars)
36 | (while (looking-at (format "[%s]" (oref enter skip-chars)))
37 | (forward-char)))
38 | (forward-char))
39 |
40 | (defclass multi-line-looking-at-enter-strategy ()
41 | ((enter-regex :initarg :enter-regex :initform "[[:space]]*[{([]")))
42 |
43 | (cl-defmethod multi-line-can-enter ((strategy multi-line-looking-at-enter-strategy))
44 | (looking-at (oref strategy enter-regex)))
45 |
46 | (cl-defmethod multi-line-enter ((strategy multi-line-looking-at-enter-strategy))
47 | (re-search-forward (oref strategy enter-regex)))
48 |
49 | (provide 'multi-line-enter)
50 | ;;; multi-line-enter.el ends here
51 |
--------------------------------------------------------------------------------
/multi-line-find.el:
--------------------------------------------------------------------------------
1 | ;;; multi-line-find.el --- multi-line statements -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2015-2023 Ivan Malison
4 |
5 | ;; This program is free software; you can redistribute it and/or modify
6 | ;; it under the terms of the GNU General Public License as published by
7 | ;; the Free Software Foundation, either version 3 of the License, or
8 | ;; (at your option) any later version.
9 |
10 | ;; This program is distributed in the hope that it will be useful,
11 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | ;; GNU General Public License for more details.
14 |
15 | ;; You should have received a copy of the GNU General Public License
16 | ;; along with this program. If not, see .
17 |
18 | ;;; Commentary:
19 |
20 | ;; multi-line-find defines various approaches for finding the points
21 | ;; at which a statement can be split into multiple lines.
22 |
23 | ;;; Code:
24 |
25 | (require 'cl-lib)
26 | (require 'eieio)
27 |
28 | (require 'multi-line-candidate)
29 | (require 'multi-line-enter)
30 | (require 'multi-line-shared)
31 |
32 | (defclass multi-line-forward-sexp-find-strategy ()
33 | ((split-regex :initarg :split-regex :initform "[[:space:]]*,")
34 | (done-regex :initarg :done-regex :initform "[[:space:]]*[})\]]")
35 | (split-advance-fn :initarg :split-advance-fn :initform
36 | 'multi-line-comma-advance)))
37 |
38 | (cl-defmethod multi-line-at-end-of-candidates
39 | ((strategy multi-line-forward-sexp-find-strategy))
40 | (or (looking-at (oref strategy done-regex))
41 | (save-excursion
42 | ;; Check to see if we get a scan error when trying to move forward
43 | (condition-case _ignored (forward-sexp) ('scan-error t)))))
44 |
45 | (cl-defmethod multi-line-advance
46 | ((strategy multi-line-forward-sexp-find-strategy) &optional _context)
47 | (cl-loop
48 | for failed = (condition-case _ignored (forward-sexp)
49 | ('error t))
50 | when (or failed
51 | (when (looking-at (oref strategy split-regex))
52 | (funcall (oref strategy split-advance-fn))
53 | t))
54 | return nil))
55 |
56 | (cl-defmethod multi-line-find ((strategy multi-line-forward-sexp-find-strategy)
57 | &optional context)
58 | (nconc (list (make-instance 'multi-line-candidate))
59 | (progn
60 | ;; XXX: This is a hack to make hash literals work in ruby. For some
61 | ;; reason if you execute forward sexp at a '{' but there is a newline
62 | ;; immediately following that character it passes over the entire
63 | ;; hash body.
64 | (re-search-forward "[^[:space:]\n]") (backward-char)
65 | (cl-loop
66 | do (multi-line-advance strategy context)
67 | ;; Make sure we're not adding the same point twice
68 | until (and last-point (equal (point) last-point))
69 | for last-point = (point)
70 | collect (make-instance 'multi-line-candidate)
71 | until (multi-line-at-end-of-candidates strategy)))))
72 |
73 | ;; A finder decorator that removes candidates that follow "keyword" arguments,
74 | ;; so that things like:
75 | ;; :akey "avalue"
76 | ;; are always paired
77 | (defclass multi-line-keyword-pairing-finder ()
78 | ((child :initarg :child)
79 | (keyword-string :initarg :keyword-string :initform ":")))
80 |
81 | (cl-defmethod multi-line-find ((strategy multi-line-keyword-pairing-finder)
82 | &optional context)
83 | (let ((candidates (multi-line-find (oref strategy child) context))
84 | last-was-included last-candidate)
85 | (cl-loop for candidate in candidates
86 | for include-this =
87 | (or
88 | (not last-was-included)
89 | (progn
90 | (goto-char (multi-line-candidate-position last-candidate))
91 | (re-search-forward "[^[:space:]]")
92 | (backward-char)
93 | (not (looking-at (oref strategy keyword-string)))))
94 | do (progn
95 | (setq last-was-included include-this)
96 | (setq last-candidate candidate))
97 | when include-this
98 | collect candidate)))
99 |
100 | (provide 'multi-line-find)
101 | ;;; multi-line-find.el ends here
102 |
--------------------------------------------------------------------------------
/multi-line-highlight.el:
--------------------------------------------------------------------------------
1 | ;;; multi-line-highlight.el --- multi-line statements -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2015-2023 Ivan Malison
4 |
5 | ;; Author: Ivan Malison
6 | ;; Keywords: multi line length whitespace programming tools
7 | ;; URL: https://github.com/IvanMalison/multi-line
8 | ;; Package-Requires: ((emacs "24") (s "1.9.0") (cl-lib "0.5") (dash "2.12.0") (shut-up "0.3.2"))
9 | ;; Version: 0.1.4
10 |
11 | ;; This program is free software; you can redistribute it and/or modify
12 | ;; it under the terms of the GNU General Public License as published by
13 | ;; the Free Software Foundation, either version 3 of the License, or
14 | ;; (at your option) any later version.
15 |
16 | ;; This program is distributed in the hope that it will be useful,
17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 | ;; GNU General Public License for more details.
20 |
21 | ;; You should have received a copy of the GNU General Public License
22 | ;; along with this program. If not, see .
23 |
24 | ;;; Commentary:
25 |
26 | ;; Highlight respacing candidates.
27 |
28 | ;;; Code:
29 |
30 | (require 'multi-line)
31 |
32 | (defvar multi-line-overlays-to-remove nil)
33 |
34 | ;;;###autoload
35 | (defun multi-line-clear-highlights ()
36 | "Remove any existing multi-line highlight overlays."
37 | (interactive)
38 | (cl-loop for overlay in multi-line-overlays-to-remove
39 | do (delete-overlay overlay)))
40 |
41 | ;;;###autoload
42 | (defun multi-line-highlight-current-candidates ()
43 | "Highlight the positions at which multi-line will consider adding newlines."
44 | (interactive)
45 | (let ((candidates
46 | (save-excursion
47 | (multi-line-candidates multi-line-current-strategy))))
48 | (cl-loop for candidate in candidates do
49 | (push (multi-line-highlight-candidate candidate)
50 | multi-line-overlays-to-remove))))
51 |
52 | (defun multi-line-highlight-candidate (candidate)
53 | (let* ((position (multi-line-candidate-position candidate))
54 | (overlay (make-overlay (1- position) position)))
55 | (overlay-put overlay 'face 'highlight)
56 | overlay))
57 |
58 | (provide 'multi-line-highlight)
59 | ;;; multi-line-highlight.el ends here
60 |
--------------------------------------------------------------------------------
/multi-line-respace.el:
--------------------------------------------------------------------------------
1 | ;;; multi-line-respace.el --- multi-line statements -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2015-2023 Ivan Malison
4 |
5 | ;; This program is free software; you can redistribute it and/or modify
6 | ;; it under the terms of the GNU General Public License as published by
7 | ;; the Free Software Foundation, either version 3 of the License, or
8 | ;; (at your option) any later version.
9 |
10 | ;; This program is distributed in the hope that it will be useful,
11 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | ;; GNU General Public License for more details.
14 |
15 | ;; You should have received a copy of the GNU General Public License
16 | ;; along with this program. If not, see .
17 |
18 | ;;; Commentary:
19 |
20 | ;; multi-line-respace defines various generally applicable respace
21 | ;; strategies.
22 |
23 | ;;; Code:
24 |
25 | (require 'cl-lib)
26 | (require 'eieio)
27 | (require 'dash)
28 | (require 's)
29 |
30 | (require 'multi-line-candidate)
31 | (require 'multi-line-cycle)
32 | (require 'multi-line-shared)
33 |
34 | (defclass multi-line-respacer () nil)
35 |
36 | (cl-defmethod multi-line-respace ((respacer multi-line-respacer) candidates
37 | &optional _context)
38 | (cl-loop for candidate being the elements of candidates using (index i) do
39 | (goto-char (multi-line-candidate-position candidate))
40 | (multi-line-respace-one respacer i candidates)))
41 |
42 | (defclass multi-line-space (multi-line-respacer)
43 | ((spacer :initarg :spacer :initform " ")))
44 |
45 | (cl-defmethod multi-line-respace-one ((respacer multi-line-space)
46 | _index _candidates)
47 | (when (not (multi-line-spacer-at-point respacer))
48 | (insert (oref respacer spacer))))
49 |
50 | (cl-defmethod multi-line-spacer-at-point ((respacer multi-line-space))
51 | ;; TODO/XXX: This would cause problems with a spacer that was more than one
52 | ;; character long.
53 | (save-excursion (re-search-backward (format "[^%s]" (oref respacer spacer)))
54 | (forward-char)
55 | (looking-at (oref respacer spacer))))
56 |
57 | (defclass multi-line-always-newline (multi-line-respacer) nil)
58 |
59 | (cl-defmethod multi-line-respace-one ((_respacer multi-line-always-newline)
60 | _index _candidates)
61 | (newline-and-indent))
62 |
63 | (defclass multi-line-selecting-respacer nil
64 | ((indices-to-respacer :initarg :indices-to-respacer)
65 | (default :initarg :default :initform nil)))
66 |
67 | (cl-defmethod multi-line-respace-one ((respacer multi-line-selecting-respacer)
68 | index candidates)
69 | (let ((selected (multi-line-select-respacer respacer index candidates)))
70 | (when selected
71 | (multi-line-respace-one selected index candidates))))
72 |
73 | (cl-defmethod multi-line-select-respacer ((respacer multi-line-selecting-respacer)
74 | index candidates)
75 | (cl-loop for (indices . r) in (oref respacer indices-to-respacer)
76 | when
77 | (memq index (multi-line-actual-indices indices candidates))
78 | return r
79 | finally return (oref respacer default)))
80 |
81 | (defun multi-line-never-newline ()
82 | (make-instance 'multi-line-selecting-respacer
83 | :default (make-instance 'multi-line-space)
84 | :indices-to-respacer (list (cons (list 0 -1) nil))))
85 |
86 | (defclass multi-line-fill-respacer (multi-line-respacer)
87 | ((newline-respacer
88 | :initarg :newline-respacer)
89 | (sl-respacer
90 | :initarg :sl-respacer)
91 | (first-index :initform 0 :initarg :first-index)
92 | (final-index :initform -1 :initarg :final-index)))
93 |
94 | ;; There seems to be a really strange issue where the default value for a field
95 | ;; passed to :initform is evaluated incorretly in SUBCLASSES. There may be some
96 | ;; interaction with byte compilation as well, causing this (though I haven't
97 | ;; verified this).
98 | ;; Specifically, when creating an instance of a subclass, instead of evaluating the
99 | ;; function call 'multi-line-never-newline' and using its return value to initialize
100 | ;; the 'sl-respacer' slot, it was treating 'multi-line-never-newline' as a literal
101 | ;; list containing a single symbol, and using that list as the default value. This
102 | ;; resulted in a type error later on when 'multi-line-respace-one' expected an
103 | ;; instance of 'multi-line-selecting-respacer', but instead received a list.
104 | (cl-defmethod initialize-instance :after ((obj multi-line-fill-respacer) &rest _args)
105 | (unless (slot-boundp obj 'newline-respacer)
106 | (oset obj newline-respacer (make-instance 'multi-line-always-newline)))
107 | (unless (slot-boundp obj 'sl-respacer)
108 | (oset obj sl-respacer (multi-line-never-newline))))
109 |
110 | (cl-defmethod multi-line-should-newline ((respacer multi-line-fill-respacer)
111 | index candidates)
112 | (let ((candidates-length (length candidates)))
113 | (when (<= (multi-line-first-index respacer candidates-length)
114 | index (multi-line-final-index respacer candidates-length))
115 | (multi-line-check-fill-column respacer index candidates))))
116 |
117 | (cl-defmethod multi-line-first-index ((respacer multi-line-fill-respacer)
118 | candidates-length)
119 | (mod (oref respacer first-index) candidates-length))
120 |
121 | (cl-defmethod multi-line-final-index ((respacer multi-line-fill-respacer)
122 | candidates-length)
123 | (mod (oref respacer final-index) candidates-length))
124 |
125 | (cl-defmethod multi-line-check-fill-column ((respacer multi-line-fill-respacer)
126 | index candidates)
127 | (> (multi-line-min-max-column-if-no-newline respacer index candidates)
128 | (multi-line-get-fill-column respacer)))
129 |
130 | (cl-defmethod multi-line-min-max-column-if-no-newline
131 | ((respacer multi-line-fill-respacer) index candidates)
132 | "Compute the minimum line length of the current expression,
133 | assuming that no newline is inserted at the current candidate."
134 |
135 | (let* ((next-index (+ index 1))
136 | (final-index (multi-line-final-index respacer (length candidates)))
137 | (next-candidate (nth next-index candidates))
138 | (next-candidate-position
139 | (if next-candidate
140 | (multi-line-candidate-position next-candidate)
141 | (save-excursion (end-of-line) (point)))))
142 | (save-excursion
143 | (cond ((or
144 | ;; This is the last chance to respace, so we need to
145 | ;; consider anything else that is on the current line.
146 | (equal index final-index)
147 | ;; There is at least one newline in between this marker and the
148 | ;; next marker, so we maximize the end of line columns between
149 | ;; them.
150 | (s-contains?
151 | "\n" (buffer-substring (multi-line-candidate-position
152 | (nth index candidates))
153 | next-candidate-position)))
154 | (cl-loop
155 | do (let ((inhibit-point-motion-hooks t))
156 | (end-of-line))
157 | maximize (current-column)
158 | do (forward-line)
159 | while (< (point) next-candidate-position)))
160 | (;; We look at the column of the next candidate
161 | next-candidate
162 | (goto-char (multi-line-candidate-position next-candidate))
163 | (current-column))))))
164 |
165 | (cl-defmethod multi-line-respace-one ((respacer multi-line-fill-respacer)
166 | index candidates)
167 | (let ((selected
168 | (if (multi-line-should-newline respacer index candidates)
169 | (oref respacer newline-respacer)
170 | (oref respacer sl-respacer))))
171 | (multi-line-respace-one selected index candidates)))
172 |
173 | (defclass multi-line-fixed-fill-respacer (multi-line-fill-respacer)
174 | ((newline-at :initarg :newline-at :initform 80)))
175 |
176 | (cl-defmethod multi-line-get-fill-column
177 | ((respacer multi-line-fixed-fill-respacer))
178 | (oref respacer newline-at))
179 |
180 | (defclass multi-line-fill-column-respacer (multi-line-fill-respacer) nil)
181 |
182 | (cl-defmethod multi-line-get-fill-column ((_r multi-line-fill-column-respacer))
183 | fill-column)
184 |
185 | (provide 'multi-line-respace)
186 | ;;; multi-line-respace.el ends here
187 |
--------------------------------------------------------------------------------
/multi-line-shared.el:
--------------------------------------------------------------------------------
1 | ;;; multi-line-shared.el --- multi-line statements -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2015-2023 Ivan Malison
4 |
5 | ;; This program is free software; you can redistribute it and/or modify
6 | ;; it under the terms of the GNU General Public License as published by
7 | ;; the Free Software Foundation, either version 3 of the License, or
8 | ;; (at your option) any later version.
9 |
10 | ;; This program is distributed in the hope that it will be useful,
11 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | ;; GNU General Public License for more details.
14 |
15 | ;; You should have received a copy of the GNU General Public License
16 | ;; along with this program. If not, see .
17 |
18 | ;;; Commentary:
19 |
20 | ;; multi-line-shared defines functions that are generally useful in
21 | ;; building multi-line strategies.
22 |
23 | ;;; Code:
24 |
25 | (require 'cl-lib)
26 | (require 'eieio)
27 | (require 'dash)
28 | (require 's)
29 |
30 | (defun multi-line-clear-whitespace-at-point ()
31 | "Clear the whitespace at point."
32 | (interactive)
33 | (cl-destructuring-bind (start . end)
34 | (multi-line-space-markers)
35 | (delete-region (marker-position start) (marker-position end))))
36 |
37 | (cl-defun multi-line-space-markers
38 | (&optional (space-matches-string "[:space:]\n"))
39 | "Get markers delimiting whitespace at point.
40 |
41 | SPACE-MATCHES-STRING is as a string containing concatenated
42 | character classes that will be used to find whitespace."
43 | (let ((space-excludes-string (format "[^%s]" space-matches-string)))
44 | (re-search-backward space-excludes-string)
45 | (forward-char)
46 | (let* ((start (point-marker))
47 | (end (progn
48 | (re-search-forward space-excludes-string)
49 | (backward-char)
50 | (point-marker))))
51 | (cons start end))))
52 |
53 | (defun multi-line-add-remove-or-leave-final-comma ()
54 | (save-excursion
55 | (if (looking-at ",?[[:space:]]*\n")
56 | (when (not (or (looking-at ",")
57 | (save-excursion
58 | (re-search-backward "[^[:space:]\n]")
59 | (looking-at ","))))
60 | (insert ","))
61 | (when (or (looking-at ",")
62 | (progn (re-search-backward "[^[:space:]\n]")
63 | (looking-at ",")))
64 | (delete-char 1)))))
65 |
66 | (defun multi-line-lparenthesis-advance ()
67 | "Advance to the beginning of a statement that can be multi-lined."
68 | (re-search-forward "[[{(]"))
69 |
70 | (defun multi-line-up-list-back ()
71 | "Go to the beginning of a statement from inside the statement."
72 | (interactive)
73 | ;; TODO: This could really used some explanation. I have no idea what is going
74 | ;; on here now.
75 | (let ((string-start (nth 8 (syntax-ppss))))
76 | (when string-start
77 | (goto-char string-start)))
78 | (up-list) (backward-sexp))
79 |
80 | (defun multi-line-comma-advance ()
81 | "Pass over a comma when it is present."
82 | (when (looking-at "[[:space:]\n]*,")
83 | (re-search-forward ",")))
84 |
85 | (defun multi-line-is-newline-between-markers (first second)
86 | (s-contains? "\n"
87 | (buffer-substring (marker-position first)
88 | (marker-position second))))
89 |
90 | (defmacro multi-line-predicate-or (&rest predicates)
91 | `(lambda (&rest args)
92 | (or ,@(cl-loop for predicate in predicates
93 | collect `(apply ,predicate args)))))
94 |
95 | (defmacro multi-line-predicate-and (&rest predicates)
96 | `(lambda (&rest args)
97 | (and ,@(cl-loop for predicate in predicates
98 | collect `(apply ,predicate args)))))
99 |
100 | (defun multi-line-last-predicate (index candidates)
101 | (equal index (- (length candidates) 1)))
102 |
103 | (defun multi-line-first-predicate (index _candidates)
104 | (equal index 0))
105 |
106 | (defalias 'multi-line-first-or-last-predicate
107 | (multi-line-predicate-or 'multi-line-first-predicate
108 | 'multi-line-last-predicate))
109 |
110 | (defun multi-line-remove-at-indices (skip-indices list)
111 | (-remove-at-indices (multi-line-actual-indices skip-indices list) list))
112 |
113 | (defun multi-line-actual-indices (skip-indices list)
114 | (let ((list-length (length list)))
115 | (cl-loop for index in skip-indices
116 | collect (mod index list-length))))
117 |
118 | (defun multi-line-interpret-prefix-as-number (prefix)
119 | (cond
120 | ((numberp prefix) prefix)
121 | ((and (-non-nil prefix) (listp prefix))
122 | (truncate (log (car prefix) 4)))
123 | (0)))
124 |
125 | (provide 'multi-line-shared)
126 | ;;; multi-line-shared.el ends here
127 |
--------------------------------------------------------------------------------
/multi-line.el:
--------------------------------------------------------------------------------
1 | ;;; multi-line.el --- multi-line statements -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2015-2023 Ivan Malison
4 |
5 | ;; Author: Ivan Malison
6 | ;; Keywords: multi line length whitespace programming tools convenience files
7 | ;; URL: https://github.com/IvanMalison/multi-line
8 | ;; Package-Requires: ((emacs "24.3") (s "1.9.0") (cl-lib "0.5") (dash "2.12.0") (shut-up "0.3.2"))
9 | ;; Version: 0.1.5
10 |
11 | ;; This program is free software; you can redistribute it and/or modify
12 | ;; it under the terms of the GNU General Public License as published by
13 | ;; the Free Software Foundation, either version 3 of the License, or
14 | ;; (at your option) any later version.
15 |
16 | ;; This program is distributed in the hope that it will be useful,
17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 | ;; GNU General Public License for more details.
20 |
21 | ;; You should have received a copy of the GNU General Public License
22 | ;; along with this program. If not, see .
23 |
24 | ;;; Commentary:
25 |
26 | ;; multi-line aims to provide a flexible framework for automatically
27 | ;; multi-lining and single-lining function invocations and definitions,
28 | ;; array and map literals and more. It relies on functions that are
29 | ;; defined on a per major mode basis wherever it can so that it operates
30 | ;; correctly across many different programming languages.
31 |
32 | ;;; Code:
33 |
34 | (require 'cl-lib)
35 | (require 'eieio)
36 |
37 | (require 'multi-line-cycle)
38 | (require 'multi-line-decorator)
39 | (require 'multi-line-enter)
40 | (require 'multi-line-find)
41 | (require 'multi-line-respace)
42 | (require 'multi-line-shared)
43 |
44 | (defvar multi-line-default-single-line-respacer
45 | (multi-line-clearing-reindenting-respacer
46 | (multi-line-never-newline)))
47 |
48 | (defvar multi-line-always-newline-respacer
49 | (make-instance 'multi-line-always-newline))
50 |
51 | (defvar multi-line-force-first-and-last-respacer
52 | (make-instance
53 | 'multi-line-selecting-respacer
54 | :indices-to-respacer (list
55 | (cons (list 0 -1)
56 | multi-line-always-newline-respacer))
57 | :default (make-instance 'multi-line-fill-column-respacer)))
58 |
59 | (defvar multi-line-skip-first-and-last-respacer
60 | (make-instance 'multi-line-fill-column-respacer
61 | :first-index 1 :final-index -2))
62 |
63 | (cl-defun multi-line-respacers-with-single-line
64 | (respacers
65 | &optional (single-line-respacer multi-line-default-single-line-respacer))
66 | (multi-line-build-from-respacers-list
67 | (append respacers (list (cons :single-line single-line-respacer)))))
68 |
69 | (defun multi-line-default-respacers (&rest respacers)
70 | "Add a single-line strategy to RESPACERS and make a cycling respace strategy."
71 | (multi-line-respacers-with-single-line respacers))
72 |
73 | (defvar multi-line-skip-fill-respacer
74 | (make-instance 'multi-line-fill-column-respacer
75 | :first-index 1 :final-index -2))
76 |
77 | (defvar multi-line-default-respacer-list
78 | (mapcar 'multi-line-clearing-reindenting-respacer
79 | (list multi-line-force-first-and-last-respacer
80 | multi-line-always-newline-respacer
81 | multi-line-skip-first-and-last-respacer)))
82 |
83 | (defvar multi-line-default-respacer
84 | (multi-line-respacers-with-single-line multi-line-default-respacer-list))
85 |
86 | (defun multi-line-get-default-respacer ()
87 | multi-line-default-respacer)
88 |
89 | (defclass multi-line-strategy ()
90 | ((enter :initarg :enter :initform
91 | (make-instance multi-line-up-list-enter-strategy))
92 | (find :initarg :find :initform
93 | (make-instance multi-line-forward-sexp-find-strategy))
94 | (respace :initarg :respace
95 | :initform (multi-line-get-default-respacer))))
96 |
97 | (cl-defmethod multi-line-candidates ((strategy multi-line-strategy)
98 | &optional context)
99 | "Get the multi-line candidates at point using the find attribute of STRATEGY.
100 | CONTEXT is passed to the find strategy."
101 | (let ((enter-strategy (oref strategy enter))
102 | (find-strategy (oref strategy find)))
103 | (multi-line-enter enter-strategy context)
104 | (multi-line-find find-strategy context)))
105 |
106 | (cl-defmethod multi-line-execute ((strategy multi-line-strategy) &optional context)
107 | (when (or (eq context t) (equal context 'single-line))
108 | (setq context (plist-put nil :respacer-name :single-line)))
109 | (save-excursion
110 | (let ((candidates (multi-line-candidates strategy)))
111 | (multi-line-respace (oref strategy respace) candidates context))))
112 |
113 | (defvar-local multi-line-current-strategy
114 | (make-instance 'multi-line-strategy)
115 | "The multi-line strategy that will be used by the command `multi-line'.")
116 |
117 | (defun multi-line-lisp-advance-fn ()
118 | "Advance to the start of the next multi-line split for Lisp."
119 | nil)
120 |
121 | (eval-and-compile
122 | (defvar multi-line-defhook-prefix "multi-line-"))
123 |
124 | (defvar multi-line-mode-to-hook nil)
125 |
126 | (defmacro multi-line-defhook
127 | (the-mode-name strategy-form &optional use-global-enable)
128 | (let* ((mode-string (symbol-name the-mode-name))
129 | (base-string (concat multi-line-defhook-prefix mode-string))
130 | (variable-name (intern (concat base-string "-strategy")))
131 | (hook-name (intern (concat base-string "-mode-hook")))
132 | (mode-hook-name (intern (concat mode-string "-mode-hook"))))
133 | `(progn
134 | (defvar ,variable-name)
135 | (setq ,variable-name ,strategy-form)
136 | (defun ,hook-name ()
137 | (setq-local multi-line-current-strategy ,variable-name))
138 | ,(if use-global-enable
139 | `(add-to-list (quote multi-line-mode-to-hook)
140 | (cons (quote ,mode-hook-name) (quote ,hook-name)))
141 | `(add-hook (quote ,mode-hook-name) (quote ,hook-name) t)))))
142 |
143 | (put 'multi-line-defhook 'lisp-indent-function 1)
144 |
145 | (defvar multi-line-lisp-respacer
146 | (multi-line-default-respacers (multi-line-clearing-reindenting-respacer
147 | multi-line-skip-fill-respacer)))
148 |
149 | (defvar multi-line-lisp-find-strategy
150 | (make-instance
151 | 'multi-line-keyword-pairing-finder :child
152 | (make-instance 'multi-line-forward-sexp-find-strategy
153 | :split-regex "[[:space:]\n]+"
154 | :done-regex "[[:space:]]*)"
155 | :split-advance-fn 'multi-line-lisp-advance-fn)))
156 |
157 | (defvar multi-line-lisp-strategy
158 | (make-instance
159 | 'multi-line-strategy
160 | :find multi-line-lisp-find-strategy
161 | :enter (make-instance 'multi-line-up-list-enter-strategy
162 | :skip-chars "`',@")
163 | :respace multi-line-lisp-respacer))
164 |
165 | (multi-line-defhook lisp multi-line-lisp-strategy t)
166 | (multi-line-defhook emacs-lisp multi-line-lisp-strategy t)
167 |
168 | (defvar multi-line-add-trailing-comma-strategy
169 | (make-instance 'multi-line-strategy
170 | :respace
171 | (multi-line-respacers-with-single-line
172 | (mapcar 'multi-line-trailing-comma-respacer
173 | multi-line-default-respacer-list)
174 | (multi-line-trailing-comma-respacer
175 | multi-line-default-single-line-respacer))))
176 |
177 | (multi-line-defhook python multi-line-add-trailing-comma-strategy t)
178 | (multi-line-defhook go multi-line-add-trailing-comma-strategy t)
179 | (multi-line-defhook ruby multi-line-add-trailing-comma-strategy t)
180 |
181 | (defvar multi-line-leading-commas-find-strategy
182 | (make-instance 'multi-line-forward-sexp-find-strategy
183 | :split-advance-fn (lambda ())))
184 |
185 | (defvar multi-line-leading-commas-strategy
186 | (make-instance
187 | 'multi-line-strategy
188 | :find multi-line-leading-commas-find-strategy
189 | :respace
190 | (multi-line-default-respacers
191 | (multi-line-clearing-reindenting-respacer
192 | (make-instance 'multi-line-always-newline)))))
193 |
194 | (multi-line-defhook haskell multi-line-leading-commas-strategy t)
195 |
196 | (defvar multi-line-clojure-find-strategy
197 | (make-instance
198 | 'multi-line-keyword-pairing-finder :child
199 | (make-instance 'multi-line-forward-sexp-find-strategy
200 | :split-regex "[[:space:]\n]+"
201 | :done-regex "[[:space:]]*)]}"
202 | :split-advance-fn 'multi-line-lisp-advance-fn)))
203 |
204 | (multi-line-defhook clojure
205 | (make-instance
206 | 'multi-line-strategy
207 | :find multi-line-clojure-find-strategy
208 | :enter (make-instance 'multi-line-up-list-enter-strategy
209 | :skip-chars "#~`'@,")
210 | :respace multi-line-lisp-respacer) t)
211 |
212 | ;;;###autoload
213 | (defun multi-line-enable-mode-hooks ()
214 | "Set default language specific strategies for multi-line."
215 | (interactive)
216 | (cl-loop for (target-name . hook-name) in multi-line-mode-to-hook
217 | do (add-hook target-name hook-name t)))
218 |
219 | ;;;###autoload
220 | (defun multi-line-disable-mode-hooks ()
221 | "Remove default language specific strategies for multi-line."
222 | (interactive)
223 | (cl-loop for (target-name . hook-name) in multi-line-mode-to-hook
224 | do (remove-hook target-name hook-name)))
225 |
226 | (multi-line-enable-mode-hooks)
227 |
228 | ;;;###autoload
229 | (defun multi-line (arg)
230 | "Multi-line the statement at point.
231 |
232 | When ARG is provided single-line the statement at point instead."
233 | (interactive "P")
234 | ;; TODO(imalison): Is there a better way to cast to bool?
235 | (let ((for-single-line (if arg t nil)))
236 | (multi-line-execute multi-line-current-strategy for-single-line)))
237 |
238 | ;;;###autoload
239 | (defun multi-line-single-line ()
240 | "Single-line the statement at point."
241 | (interactive)
242 | (multi-line-execute multi-line-current-strategy t))
243 |
244 | (provide 'multi-line)
245 | ;;; multi-line.el ends here
246 |
--------------------------------------------------------------------------------
/test/multi-line-elisp-test.el:
--------------------------------------------------------------------------------
1 | ;;; multi-line-elisp-test.el --- multi-line elisp tests -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2015-2023 Ivan Malison
4 |
5 | ;; This program is free software; you can redistribute it and/or modify
6 | ;; it under the terms of the GNU General Public License as published by
7 | ;; the Free Software Foundation, either version 3 of the License, or
8 | ;; (at your option) any later version.
9 |
10 | ;; This program is distributed in the hope that it will be useful,
11 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | ;; GNU General Public License for more details.
14 |
15 | ;; You should have received a copy of the GNU General Public License
16 | ;; along with this program. If not, see .
17 |
18 | ;;; Commentary:
19 |
20 | ;; Test the behavior of multi-line in emacs-lisp-mode
21 |
22 | ;;; Code:
23 |
24 | (require 'multi-line)
25 | (require 'multi-line-test)
26 |
27 | (cl-defmacro multi-line-deftest-elisp (name initial expected &rest args
28 | &key tags setup &allow-other-keys)
29 | (let ((setup (cons '(forward-char) setup)))
30 | `(multi-line-deftest-for-mode emacs-lisp ,name ,initial ,expected :tags (quote ,tags)
31 | :setup ,setup ,@args)))
32 |
33 | (multi-line-deftest-elisp test-basic-elisp
34 | "(a bbbbbbbbbbbbbbbbbb ccccccccccccccccccccc ddddddddeeeeeeeeekkkkkkkkkkffffffffff gggggggggggg)"
35 | "(a bbbbbbbbbbbbbbbbbb ccccccccccccccccccccc ddddddddeeeeeeeeekkkkkkkkkkffffffffff
36 | gggggggggggg)")
37 |
38 | (multi-line-deftest-elisp test-handles-quoted-lists
39 | "(list '((emacs-lisp-mode) (more-fun-stuff) (setq fill-column 80) (setq indent-tabs-mode nil) (forward-char)))"
40 | "(list '((emacs-lisp-mode) (more-fun-stuff) (setq fill-column 80)
41 | (setq indent-tabs-mode nil) (forward-char)))"
42 | :setup ((search-forward "(more-fun")
43 | (up-list)))
44 |
45 | (multi-line-deftest-elisp test-handles-quasi-quoted-lists
46 | "(list `((emacs-lisp-mode) (more-fun-stuff) (setq fill-column 80) (setq indent-tabs-mode nil) (forward-char)))"
47 | "(list `((emacs-lisp-mode) (more-fun-stuff) (setq fill-column 80)
48 | (setq indent-tabs-mode nil) (forward-char)))"
49 | :setup ((search-forward "(more-fun")
50 | (up-list)))
51 |
52 | (multi-line-deftest-elisp test-handles-unquote
53 | "`(,(list 'afdsafdsafdsfafdsafdsafd 'afdsafdsafdasfdsafs 'afdasfdsafdsafdafdsafdsafdsafdsa))"
54 | "`(,(list 'afdsafdsafdsfafdsafdsafd 'afdsafdsafdasfdsafs
55 | 'afdasfdsafdsafdafdsafdsafdsafdsa))"
56 | :setup ((search-forward "fdsafdsafdsfafdsafdsafd")))
57 |
58 | (multi-line-deftest-elisp test-handles-splice
59 | "`(,@(list 'afdsafdsafdsfafdsafdsafd 'afdsafdsafdasfdsafs 'afdasfdsafdsafdafdsafdsafdsafdsa))"
60 | "`(,@(list 'afdsafdsafdsfafdsafdsafd 'afdsafdsafdasfdsafs
61 | 'afdasfdsafdsafdafdsafdsafdsafdsa))"
62 | :setup ((search-forward "fdsafdsafdsfafdsafdsafd")))
63 |
64 | (multi-line-deftest-elisp colon-keywords-always-paired
65 | "(fdsafdsajklfdjsaklf fdasfdsafdsa fdasfdsafdsafdsafdsaf :f fdsafdsafdfdsafsafdsadf :c fdasfdsafdsafdsafdsafdsafdsafdsafdsafdsa)"
66 | "(fdsafdsajklfdjsaklf fdasfdsafdsa fdasfdsafdsafdsafdsaf
67 | :f fdsafdsafdfdsafsafdsadf
68 | :c fdasfdsafdsafdsafdsafdsafdsafdsafdsafdsa)")
69 |
70 | (multi-line-deftest-elisp test-checks-all-newlines-between-candidates
71 | "(fdsafkldsfdsafdsafdsaf fdsafdsafdsaf fdsafdsafdsa (afdasffda
72 | fdsafdsafdsaf fdsafdsaf fdsafdsafdsafdsa fdsafdsa) a)"
73 | "(fdsafkldsfdsafdsafdsaf fdsafdsafdsaf fdsafdsafdsa
74 | (afdasffda
75 | fdsafdsafdsaf fdsafdsaf fdsafdsafdsafdsa fdsafdsa) a)")
76 |
77 | (multi-line-deftest-elisp test-checks-newlines-if-last-candidate
78 | "(fdsafkldsfdsafdsafdsaf fdsafdsafdsaf fdsafdsafdsa (afdasffda
79 | fdsafdsafdsaf fdsafdsaf fdsafdsafdsafdsa fdsafdsa))"
80 | "(fdsafkldsfdsafdsafdsaf fdsafdsafdsaf fdsafdsafdsa
81 | (afdasffda
82 | fdsafdsafdsaf fdsafdsaf fdsafdsafdsafdsa fdsafdsa))")
83 |
84 | (multi-line-deftest-elisp test-find-index-multi-lining
85 | "
86 | (cl-defun tile-get-next-strategy
87 | (&optional (current-strategy (or tile-current-strategy
88 | (car (last tile-strategies)))))
89 | (let ((current-index (--find-index (equal current-strategy it) tile-strategies)))
90 | (if current-index
91 | (nth (mod (1+ current-index) (length tile-strategies)) tile-strategies)
92 | (car tile-strategies))))"
93 | "
94 | (cl-defun tile-get-next-strategy
95 | (&optional (current-strategy (or tile-current-strategy
96 | (car (last tile-strategies)))))
97 | (let ((current-index (--find-index (equal current-strategy it)
98 | tile-strategies)))
99 | (if current-index
100 | (nth (mod (1+ current-index) (length tile-strategies)) tile-strategies)
101 | (car tile-strategies))))"
102 | :setup ((search-forward "find-index")))
103 |
104 | (provide 'multi-line-elisp-test)
105 | ;;; multi-line-elisp-test.el ends here
106 |
--------------------------------------------------------------------------------
/test/multi-line-python-test.el:
--------------------------------------------------------------------------------
1 | ;;; multi-line-python-test.el --- multi-line python tests -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2015-2023 Ivan Malison
4 |
5 | ;; This program is free software; you can redistribute it and/or modify
6 | ;; it under the terms of the GNU General Public License as published by
7 | ;; the Free Software Foundation, either version 3 of the License, or
8 | ;; (at your option) any later version.
9 |
10 | ;; This program is distributed in the hope that it will be useful,
11 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | ;; GNU General Public License for more details.
14 |
15 | ;; You should have received a copy of the GNU General Public License
16 | ;; along with this program. If not, see .
17 |
18 | ;;; Commentary:
19 |
20 | ;; Test the behavior of multi-line in python-mode
21 |
22 | ;;; Code:
23 |
24 | (require 'multi-line)
25 | (require 'multi-line-test)
26 |
27 | (multi-line-deftest-for-mode python test-basic-python
28 | "
29 | function(nested(fdasfdsaf, fdasfdsaf, fdasfdsaf, fdasfdsa), other, next, another_nested_call(more, cool, quite))
30 | "
31 | (list
32 | "
33 | function(
34 | nested(fdasfdsaf, fdasfdsaf, fdasfdsaf, fdasfdsa), other, next,
35 | another_nested_call(more, cool, quite),
36 | )
37 | "
38 | "
39 | function(
40 | nested(fdasfdsaf, fdasfdsaf, fdasfdsaf, fdasfdsa),
41 | other,
42 | next,
43 | another_nested_call(more, cool, quite),
44 | )
45 | "
46 | "
47 | function(nested(fdasfdsaf, fdasfdsaf, fdasfdsaf, fdasfdsa), other, next,
48 | another_nested_call(more, cool, quite))
49 | "
50 | "
51 | function(nested(fdasfdsaf, fdasfdsaf, fdasfdsaf, fdasfdsa), other, next, another_nested_call(more, cool, quite))
52 | ")
53 | :setup ((search-forward "(") (forward-char)))
54 |
55 | (multi-line-deftest-for-mode python test-python-one-argument
56 | "
57 | fdsafdsafdsafdsafdsafdsafdsafdsafdsa(fdsafdsafdsafdsafdsafdsafdsafdfdsasdfdsadfdsaddffdsaf)
58 | "
59 | "
60 | fdsafdsafdsafdsafdsafdsafdsafdsafdsa(
61 | fdsafdsafdsafdsafdsafdsafdsafdfdsasdfdsadfdsaddffdsaf,
62 | )
63 | "
64 | :setup ((search-forward "(") (forward-char)))
65 |
66 | (multi-line-deftest-for-mode python test-python-nested-dict
67 | "
68 | function(nested(fdasfdsaf, fdasfdsaf, fdasfdsaf, fdasfdsa), {
69 | 'a': 'bfdsafdsafdsafdsafdsafdsafdsafdsafdsafdsa',
70 | 'c': 'bfdsafdjksalf;djsakf;djsaklf;fdjksal;fdsa',
71 | }, next, another_nested_call(more, cool, quite))
72 | "
73 | (list "
74 | function(
75 | nested(fdasfdsaf, fdasfdsaf, fdasfdsaf, fdasfdsa), {
76 | 'a': 'bfdsafdsafdsafdsafdsafdsafdsafdsafdsafdsa',
77 | 'c': 'bfdsafdjksalf;djsakf;djsaklf;fdjksal;fdsa',
78 | }, next, another_nested_call(more, cool, quite),
79 | )
80 | "
81 | "
82 | function(
83 | nested(fdasfdsaf, fdasfdsaf, fdasfdsaf, fdasfdsa),
84 | {
85 | 'a': 'bfdsafdsafdsafdsafdsafdsafdsafdsafdsafdsa',
86 | 'c': 'bfdsafdjksalf;djsakf;djsaklf;fdjksal;fdsa',
87 | },
88 | next,
89 | another_nested_call(more, cool, quite),
90 | )
91 | "
92 | "
93 | function(nested(fdasfdsaf, fdasfdsaf, fdasfdsaf, fdasfdsa), {
94 | 'a': 'bfdsafdsafdsafdsafdsafdsafdsafdsafdsafdsa',
95 | 'c': 'bfdsafdjksalf;djsakf;djsaklf;fdjksal;fdsa',
96 | }, next, another_nested_call(more, cool, quite))
97 | ")
98 | :setup ((search-forward "(") (forward-char)))
99 |
100 | (provide 'multi-line-python-test)
101 | ;;; multi-line-python-test.el ends here
102 |
--------------------------------------------------------------------------------
/test/multi-line-ruby-test.el:
--------------------------------------------------------------------------------
1 | ;;; multi-line-ruby-test.el --- multi-line ruby tests -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2015-2023 Ivan Malison
4 |
5 | ;; This program is free software; you can redistribute it and/or modify
6 | ;; it under the terms of the GNU General Public License as published by
7 | ;; the Free Software Foundation, either version 3 of the License, or
8 | ;; (at your option) any later version.
9 |
10 | ;; This program is distributed in the hope that it will be useful,
11 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | ;; GNU General Public License for more details.
14 |
15 | ;; You should have received a copy of the GNU General Public License
16 | ;; along with this program. If not, see .
17 |
18 | ;;; Commentary:
19 |
20 | ;; Test the behavior of multi-line in ruby-mode
21 |
22 | ;;; Code:
23 |
24 | (require 'multi-line)
25 | (require 'multi-line-test)
26 |
27 | (multi-line-deftest-for-mode ruby test-ruby-hash-literal
28 | "{:avalue => \"cool\", :greater => \"fun\", :avalue3 => \"cool\", :greaterg => \"fun\", :more => \"coool real long\",}"
29 | "{
30 | :avalue => \"cool\", :greater => \"fun\", :avalue3 => \"cool\", :greaterg => \"fun\",
31 | :more => \"coool real long\",
32 | }"
33 | :setup ((search-forward "a")))
34 |
35 | (provide 'multi-line-ruby-test)
36 | ;;; multi-line-ruby-test.el ends here
37 |
--------------------------------------------------------------------------------
/test/multi-line-test.el:
--------------------------------------------------------------------------------
1 | ;;; multi-line-test.el --- multi-line test suite -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2015-2023 Ivan Malison
4 |
5 | ;; This program is free software; you can redistribute it and/or modify
6 | ;; it under the terms of the GNU General Public License as published by
7 | ;; the Free Software Foundation, either version 3 of the License, or
8 | ;; (at your option) any later version.
9 |
10 | ;; This program is distributed in the hope that it will be useful,
11 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | ;; GNU General Public License for more details.
14 |
15 | ;; You should have received a copy of the GNU General Public License
16 | ;; along with this program. If not, see .
17 |
18 | ;;; Commentary:
19 |
20 | ;; The unit test suite of multi-line
21 |
22 | ;;; Code:
23 |
24 | (require 'cl-lib)
25 | (require 'eieio)
26 | (require 'ert)
27 | (require 'shut-up)
28 |
29 | (require 'multi-line)
30 |
31 | (put 'multi-line-deftest 'lisp-indent-function '(lambda (&rest args) 0))
32 |
33 | (defun multi-line-whitespace-trim (original)
34 | (split-string original nil nil "[[:space:]]"))
35 |
36 | (cl-defmacro multi-line-deftest
37 | (name initial-text expected-text &key strategy tags setup)
38 | (let ((expected-texts (if (listp expected-text)
39 | expected-text
40 | `(list ,expected-text)))
41 | (test-name (intern (concat "multi-line-" (symbol-name name)))))
42 | `(ert-deftest ,test-name ()
43 | :tags ,tags
44 | (with-temp-buffer
45 | (insert ,initial-text)
46 | ,(when strategy
47 | `(setq multi-line-current-strategy ,strategy))
48 | (goto-char (point-min))
49 | ,@setup
50 | (cl-loop for expected-text in ,expected-texts
51 | do (multi-line nil)
52 | (should
53 | (equal (multi-line-whitespace-trim expected-text)
54 | (multi-line-whitespace-trim (buffer-string))))))
55 | t)))
56 |
57 | (put 'multi-line-deftest-for-mode 'lisp-indent-function '(lambda (&rest args) 0))
58 |
59 | (cl-defmacro multi-line-deftest-for-mode
60 | (mode name initial expected &rest args &key tags setup &allow-other-keys)
61 | (let ((new-tags (cons mode tags))
62 | (setup (cons `(progn
63 | (shut-up (,(intern (concat (symbol-name mode) "-mode"))))
64 | (setq fill-column 80
65 | indent-tabs-mode nil))
66 | setup)))
67 | `(multi-line-deftest ,name ,initial ,expected :tags (quote ,new-tags) :setup ,setup ,@args)))
68 |
69 | (provide 'multi-line-test)
70 | ;;; multi-line-test.el ends here
71 |
--------------------------------------------------------------------------------