├── .github
├── FUNDING.yml
├── dependabot.yml
└── workflows
│ └── test.yml
├── .gitignore
├── Makefile
├── README.md
├── reformatter-tests.el
└── reformatter.el
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | patreon: sanityinc
2 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: github-actions
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | open-pull-requests-limit: 10
8 | commit-message:
9 | prefix: "chore"
10 | include: "scope"
11 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | pull_request:
5 | push:
6 | paths-ignore:
7 | - '**.md'
8 |
9 | jobs:
10 | lint:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: purcell/setup-emacs@master
14 | with:
15 | version: 29.1
16 | - uses: actions/checkout@v4
17 | - name: Run tests
18 | run: make package-lint
19 |
20 | build:
21 | runs-on: ubuntu-latest
22 | strategy:
23 | matrix:
24 | emacs_version:
25 | - 24.3
26 | - 24.5
27 | - 25.1
28 | - 25.3
29 | - 26.1
30 | - 26.3
31 | - 27.1
32 | - 27.2
33 | - 28.1
34 | - 28.2
35 | - 29.1
36 | - snapshot
37 | steps:
38 | - uses: cachix/install-nix-action@v31
39 | with:
40 | nix_path: nixpkgs=channel:nixos-unstable
41 | - uses: purcell/setup-emacs@master
42 | with:
43 | version: ${{ matrix.emacs_version }}
44 | - uses: actions/checkout@v4
45 | - name: Install deps for tests
46 | run: nix profile install 'nixpkgs#shfmt'
47 | - name: Run tests
48 | run: make compile test
49 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /*.elc
2 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | EMACS ?= emacs
2 |
3 | # A space-separated list of required package names
4 | DEPS =
5 |
6 | INIT_PACKAGES="(progn \
7 | (require 'package) \
8 | (push '(\"melpa\" . \"https://melpa.org/packages/\") package-archives) \
9 | (package-initialize) \
10 | (dolist (pkg '(PACKAGES)) \
11 | (unless (package-installed-p pkg) \
12 | (unless (assoc pkg package-archive-contents) \
13 | (package-refresh-contents)) \
14 | (package-install pkg))) \
15 | )"
16 |
17 | all: compile package-lint test clean-elc
18 |
19 | package-lint:
20 | ${EMACS} -Q --eval $(subst PACKAGES,package-lint,${INIT_PACKAGES}) -batch -f package-lint-batch-and-exit reformatter.el
21 |
22 | test:
23 | ${EMACS} -Q --eval $(subst PACKAGES,${DEPS},${INIT_PACKAGES}) -batch -l reformatter.el -l reformatter-tests.el -f ert-run-tests-batch-and-exit
24 |
25 | compile: clean-elc
26 | ${EMACS} -Q --eval $(subst PACKAGES,${DEPS},${INIT_PACKAGES}) -L . -batch -f batch-byte-compile *.el
27 |
28 | clean-elc:
29 | rm -f f.elc
30 |
31 | .PHONY: all compile clean-elc package-lint
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](http://melpa.org/#/reformatter)
2 | [](http://stable.melpa.org/#/reformatter)
3 | [](https://elpa.nongnu.org/nongnu/reformatter.html)
4 | [](https://github.com/purcell/emacs-reformatter/actions/workflows/test.yml)
5 |
6 |
7 | # Define commands which run reformatters on the current Emacs buffer
8 |
9 | This library lets elisp authors easily define an idiomatic command to
10 | reformat the current buffer using a command-line program, together
11 | with an optional minor mode which can apply this command automatically
12 | on save.
13 |
14 | By default, reformatter.el expects programs to read from stdin and
15 | write to stdout, and you should prefer this mode of operation where
16 | possible. If this isn't possible with your particular formatting
17 | program, refer to the options for `reformatter-define`, and see the
18 | examples in the package's tests.
19 |
20 | In its initial release it supports only reformatters which can read
21 | from stdin and write to stdout, but a more versatile interface will
22 | be provided as development continues.
23 |
24 | As an example, let's define a reformat command that applies the "dhall
25 | format" command. We'll assume here that we've already defined a
26 | variable `dhall-command` which holds the string name or path of the
27 | dhall executable:
28 |
29 | ```el
30 | (reformatter-define dhall-format
31 | :program dhall-command
32 | :args '("format")
33 | :lighter " DF")
34 | ```
35 |
36 | The `reformatter-define` macro expands to code which generates
37 | `dhall-format-buffer` and `dhall-format-region` interactive commands,
38 | and a local minor mode called `dhall-format-on-save-mode`. The `:args`
39 | and `:program` expressions will be evaluated at runtime, so they can
40 | refer to variables that may (later) have a buffer-local value. A
41 | custom variable will be generated for the mode lighter, with the
42 | supplied value becoming the default.
43 |
44 | The generated minor mode allows idiomatic per-directory or per-file
45 | customisation, via the "modes" support baked into Emacs' file-local
46 | and directory-local variables mechanisms. For example, users of the
47 | above example might add the following to a project-specific
48 | `.dir-locals.el` file:
49 |
50 | ```el
51 | ((dhall-mode
52 | (mode . dhall-format-on-save)))
53 | ```
54 |
55 | See the documentation for `reformatter-define`, which provides a
56 | number of options for customising the generated code.
57 |
58 | Library authors might like to provide autoloads for the generated
59 | code, e.g.:
60 |
61 | ```el
62 | ;;;###autoload (autoload 'dhall-format-buffer "current-file" nil t)
63 | ;;;###autoload (autoload 'dhall-format-region "current-file" nil t)
64 | ;;;###autoload (autoload 'dhall-format-on-save-mode "current-file" nil t)
65 | ```
66 |
67 | ## Examples of usage in the wild
68 |
69 | To find reverse dependencies, look for "Needed by" on the [MELPA page
70 | for reformatter](https://melpa.org/#/reformatter). Here are some
71 | specific examples:
72 |
73 | * [dhall-mode.el](https://github.com/psibi/dhall-mode/blob/master/dhall-mode.el)
74 | * [elm-format.el](https://github.com/jcollard/elm-mode/blob/master/elm-format.el), in `elm-mode`
75 | * [sqlformat.el](https://github.com/purcell/sqlformat/blob/master/sqlformat.el)
76 | * [Here](https://github.com/purcell/emacs.d/blob/14f645a9bde04498ce2b60de268c2cbafa13604a/lisp/init-purescript.el#L18-L19) is the author defining a reformatter in his own configuration
77 |
78 | ## Rationale
79 |
80 | I contribute to a number of Emacs programming language modes and
81 | tools, and increasingly use code reformatters in my daily work. It's
82 | surprisingly difficult to write robust, correct code to apply these
83 | reformatters, given that it must consider such issues as:
84 |
85 | * Missing programs
86 | * Buffers not yet saved to a file
87 | * Displaying error output
88 | * Colorising ANSI escape sequences in any error output
89 | * Handling file encodings correctly
90 |
91 | With this library, I hope to help the community standardise on best
92 | practices, and make things easier for tool authors and end users
93 | alike.
94 |
95 | ## FAQ
96 |
97 | ### How is this different from [format-all.el](https://github.com/lassik/emacs-format-all-the-code)?
98 |
99 | `format-all` is a very different approach: it aims to provide a single
100 | minor mode which you then enable and configure to do the right thing
101 | (including nothing) for all the languages you use. It even tries to
102 | tell you how to install missing programs. It's an interesting project,
103 | but IMO it's hard to design the configuration for such a grand unified
104 | approach, and it can get complex. For example, you'd have to be able
105 | to configure which of two possible reformatters you want to use for a
106 | specific language, and to be able to do that on a per-project basis.
107 |
108 | In contrast reformatter produces small, self-contained and separate
109 | formatters and minor modes which all work consistently and are
110 | individually configured. It makes it possible to replace existing
111 | formatter code, and it's also very convenient for users to define
112 | their own ad-hoc reformatter wrappers
113 |
114 | ## Installation
115 |
116 | ### Manual
117 |
118 | Ensure `reformatter.el` is in a directory on your load-path, and add
119 | the following to your `~/.emacs` or `~/.emacs.d/init.el`:
120 |
121 | ```elisp
122 | (require 'reformatter)
123 | ```
124 |
125 | ### MELPA
126 |
127 | If you're an Emacs 24 user or you have a recent version of
128 | `package.el` you can install `reformatter` from the
129 | [MELPA](http://melpa.org) repository. The version of
130 | `reformatter` there will always be up-to-date.
131 |
132 | ## About
133 |
134 | Author: Steve Purcell
135 |
136 | Homepage: https://github.com/purcell/emacs-reformatter
137 |
138 |
139 |
140 | [💝 Support this project and my other Open Source work](https://www.patreon.com/sanityinc)
141 |
142 | [💼 LinkedIn profile](https://uk.linkedin.com/in/stevepurcell)
143 |
144 | [✍ sanityinc.com](https://www.sanityinc.com/)
145 |
--------------------------------------------------------------------------------
/reformatter-tests.el:
--------------------------------------------------------------------------------
1 | ;;; reformatter-tests.el --- Test suite for reformatter -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2020 Steve Purcell
4 |
5 | ;; Author: Steve Purcell
6 | ;; Keywords:
7 |
8 | ;; This program is free software; you can redistribute it and/or modify
9 | ;; it under the terms of the GNU General Public License as published by
10 | ;; the Free Software Foundation, either version 3 of the License, or
11 | ;; (at your option) any later version.
12 |
13 | ;; This program is distributed in the hope that it will be useful,
14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | ;; GNU General Public License for more details.
17 |
18 | ;; You should have received a copy of the GNU General Public License
19 | ;; along with this program. If not, see .
20 |
21 | ;;; Commentary:
22 |
23 | ;; Just a few basic regression tests
24 |
25 | ;;; Code:
26 |
27 | (require 'reformatter)
28 | (require 'ert)
29 |
30 | (defgroup reformatter-tests nil "Reformatter tests" :group 'test)
31 |
32 | ;; We use `shfmt' because it can operate in a few modes
33 |
34 | ;; Pure stdin/stdout
35 | (reformatter-define reformatter-tests-shfmt-stdio
36 | :program "shfmt"
37 | :args nil
38 | :mode nil)
39 |
40 | (ert-deftest reformatter-tests-pure-stdio-no-args ()
41 | (with-temp-buffer
42 | (insert "[ foo ] && echo yes\n")
43 | (reformatter-tests-shfmt-stdio-buffer)
44 | (should (equal "[ foo ] && echo yes\n" (buffer-string)))))
45 |
46 | ;; Read from stdin/stdout
47 | (reformatter-define reformatter-tests-shfmt-tempfile-in-stdout
48 | :program "shfmt"
49 | :stdin nil
50 | :args (list input-file))
51 |
52 | (ert-deftest reformatter-tests-tempfile-in-stdout ()
53 | (with-temp-buffer
54 | (insert "[ foo ] && echo yes\n")
55 | (reformatter-tests-shfmt-tempfile-in-stdout-buffer)
56 | (should (equal "[ foo ] && echo yes\n" (buffer-string)))))
57 |
58 | ;; Same as `reformatter-tests-shfmt-tempfile-in-stdout', but with a
59 | ;; slash in the symbol name.
60 | (reformatter-define reformatter-tests-tempfile/with-slash-in-symbol-name
61 | :program "shfmt"
62 | :stdin nil
63 | :args (list input-file))
64 |
65 | (ert-deftest reformatter-tests-tempfile-with-slash-in-symbol-name ()
66 | (with-temp-buffer
67 | (insert "[ foo ] && echo yes\n")
68 | (reformatter-tests-tempfile/with-slash-in-symbol-name-buffer)
69 | (should (equal "[ foo ] && echo yes\n" (buffer-string)))))
70 |
71 | ;; Modify a file in place
72 | (reformatter-define reformatter-tests-shfmt-in-place
73 | :program "shfmt"
74 | :stdin nil
75 | :stdout nil
76 | :args (list "-w" input-file))
77 |
78 | (ert-deftest reformatter-tests-tempfile-in-place ()
79 | (with-temp-buffer
80 | (insert "[ foo ] && echo yes\n")
81 | (reformatter-tests-shfmt-in-place-buffer)
82 | (should (equal "[ foo ] && echo yes\n" (buffer-string)))))
83 |
84 | ;; Formatting commands tagged for specific modes: `command-modes' checks which
85 | ;; modes they're defined to be interactively usable in, but it's only available
86 | ;; in Emacs 28 and newer.
87 | (when (fboundp 'command-modes)
88 | (reformatter-define reformatter-tests-shfmt-no-interactive-modes
89 | :program "shfmt")
90 |
91 | (ert-deftest reformatter-tests-no-interactive-modes ()
92 | (should (not (command-modes 'reformatter-tests-shfmt-no-interactive-modes-buffer)))
93 | (should (not (command-modes 'reformatter-tests-shfmt-no-interactive-modes-region))))
94 |
95 | (reformatter-define reformatter-tests-shfmt-single-interactive-mode
96 | :program "shfmt"
97 | :interactive-modes (sh-mode))
98 |
99 | (ert-deftest reformatter-tests-single-interactive-mode ()
100 | (should (equal (command-modes 'reformatter-tests-shfmt-single-interactive-mode-buffer)
101 | '(sh-mode)))
102 | (should (equal (command-modes 'reformatter-tests-shfmt-single-interactive-mode-region)
103 | '(sh-mode))))
104 |
105 | (reformatter-define reformatter-tests-shfmt-multiple-interactive-modes
106 | :program "shfmt"
107 | :interactive-modes (sh-mode haskell-mode))
108 |
109 | (ert-deftest reformatter-tests-multiple-interactive-modes ()
110 | (should (equal (command-modes 'reformatter-tests-shfmt-multiple-interactive-modes-buffer)
111 | '(sh-mode haskell-mode)))
112 | (should (equal (command-modes 'reformatter-tests-shfmt-multiple-interactive-modes-region)
113 | '(sh-mode haskell-mode)))))
114 |
115 |
116 | (provide 'reformatter-tests)
117 | ;;; reformatter-tests.el ends here
118 |
--------------------------------------------------------------------------------
/reformatter.el:
--------------------------------------------------------------------------------
1 | ;;; reformatter.el --- Define commands which run reformatters on the current buffer -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2019 Steve Purcell
4 |
5 | ;; Author: Steve Purcell
6 | ;; Keywords: convenience, tools
7 | ;; Homepage: https://github.com/purcell/emacs-reformatter
8 | ;; Package-Requires: ((emacs "24.3"))
9 | ;; Package-Version: 0.8
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 | ;; This library lets elisp authors easily define an idiomatic command
27 | ;; to reformat the current buffer using a command-line program,
28 | ;; together with an optional minor mode which can apply this command
29 | ;; automatically on save.
30 |
31 | ;; By default, reformatter.el expects programs to read from stdin and
32 | ;; write to stdout, and you should prefer this mode of operation where
33 | ;; possible. If this isn't possible with your particular formatting
34 | ;; program, refer to the options for `reformatter-define', and see the
35 | ;; examples in the package's tests.
36 |
37 | ;; As an example, let's define a reformat command that applies the
38 | ;; "dhall format" command. We'll assume here that we've already defined a
39 | ;; variable `dhall-command' which holds the string name or path of the
40 | ;; dhall executable:
41 |
42 | ;; (reformatter-define dhall-format
43 | ;; :program dhall-command
44 | ;; :args '("format"))
45 |
46 | ;; The `reformatter-define' macro expands to code which generates
47 | ;; `dhall-format-buffer' and `dhall-format-region' interactive
48 | ;; commands, and a local minor mode called
49 | ;; `dhall-format-on-save-mode'. The :args" and :program expressions
50 | ;; will be evaluated at runtime, so they can refer to variables that
51 | ;; may (later) have a buffer-local value. A custom variable will be
52 | ;; generated for the mode lighter, with the supplied value becoming
53 | ;; the default.
54 |
55 | ;; The generated minor mode allows idiomatic per-directory or per-file
56 | ;; customisation, via the "modes" support baked into Emacs' file-local
57 | ;; and directory-local variables mechanisms. For example, users of
58 | ;; the above example might add the following to a project-specific
59 | ;; .dir-locals.el file:
60 |
61 | ;; ((dhall-mode
62 | ;; (mode . dhall-format-on-save)))
63 |
64 | ;; See the documentation for `reformatter-define', which provides a
65 | ;; number of options for customising the generated code.
66 |
67 | ;; Library authors might like to provide autoloads for the generated
68 | ;; code, e.g.:
69 |
70 | ;; ;;;###autoload (autoload 'dhall-format-buffer "current-file" nil t)
71 | ;; ;;;###autoload (autoload 'dhall-format-region "current-file" nil t)
72 | ;; ;;;###autoload (autoload 'dhall-format-on-save-mode "current-file" nil t)
73 |
74 | ;;; Code:
75 | (eval-when-compile
76 | (require 'cl-lib))
77 | (require 'ansi-color)
78 |
79 | (defun reformatter--make-temp-file (sym)
80 | "Create a temporary file whose filename is based on SYM, but with
81 | slashes replaced by underscores. `make-temp-file' fails
82 | otherwise as it cannot create intermediate directories."
83 | (make-temp-file
84 | (replace-regexp-in-string "/" "_" (symbol-name sym))))
85 |
86 | (defun reformatter--do-region (name beg end program args stdin stdout input-file exit-code-success-p display-errors &optional working-directory)
87 | "Do the work of reformatter called NAME.
88 | Reformats the current buffer's region from BEG to END using PROGRAM and
89 | ARGS. When DISPLAY-ERRORS is non-nil, shows a buffer if the formatting
90 | fails. For args STDIN, STDOUT, INPUT-FILE, EXIT-CODE-SUCCESS-P and
91 | WORKING-DIRECTORY see the documentation of the `reformatter-define' macro."
92 | (cl-assert input-file)
93 | (cl-assert (functionp exit-code-success-p))
94 | (when (and input-file
95 | (buffer-file-name)
96 | (string= (file-truename input-file)
97 | (file-truename (buffer-file-name))))
98 | (error "The reformatter must not operate on the current file in-place"))
99 | (let* ((stderr-file (reformatter--make-temp-file name))
100 | (stdout-file (reformatter--make-temp-file name))
101 | ;; Setting this coding system might not universally be
102 | ;; the best default, but was apparently necessary for
103 | ;; some hand-rolled reformatter functions that this
104 | ;; library was written to replace.
105 | (coding-system-for-read 'utf-8)
106 | (coding-system-for-write 'utf-8)
107 | (default-directory (or working-directory default-directory)))
108 | (unwind-protect
109 | (progn
110 | (write-region beg end input-file nil :quiet)
111 | (let* ((error-buffer (get-buffer-create (format "*%s errors*" name)))
112 | (retcode
113 | (condition-case e
114 | (apply 'call-process program
115 | (when stdin input-file)
116 | (list (list :file stdout-file) stderr-file)
117 | nil
118 | args)
119 | (error e))))
120 | (with-current-buffer error-buffer
121 | (let ((inhibit-read-only t))
122 | (insert-file-contents stderr-file nil nil nil t)
123 | (unless (integerp retcode)
124 | (insert (error-message-string retcode)))
125 | (ansi-color-apply-on-region (point-min) (point-max)))
126 | (special-mode))
127 | (if (and (integerp retcode) (funcall exit-code-success-p retcode))
128 | (progn
129 | (save-restriction
130 | ;; This replacement method minimises
131 | ;; disruption to marker positions and the
132 | ;; undo list
133 | (narrow-to-region beg end)
134 | (reformatter-replace-buffer-contents-from-file (if stdout
135 | stdout-file
136 | input-file)))
137 | ;; If there are no errors then we hide the error buffer
138 | (delete-windows-on error-buffer))
139 | (if display-errors
140 | (display-buffer error-buffer)
141 | (message (concat (symbol-name name) " failed: see %s") (buffer-name error-buffer))))))
142 | (delete-file stderr-file)
143 | (delete-file stdout-file))))
144 |
145 | ;;;###autoload
146 | (cl-defmacro reformatter-define (name &key program args (mode t) (stdin t) (stdout t) input-file lighter keymap group (exit-code-success-p 'zerop) working-directory interactive-modes)
147 | "Define a reformatter command with NAME.
148 |
149 | When called, the reformatter will use PROGRAM and any ARGS to
150 | reformat the current buffer. The contents of the buffer will be
151 | passed as standard input to the reformatter, which should output
152 | them to standard output. A nonzero exit code will be reported as
153 | failure, and the output of the command to standard error will be
154 | displayed to the user.
155 |
156 | The macro accepts the following keyword arguments:
157 |
158 | PROGRAM (required)
159 |
160 | Provides a form which should evaluate to a string at runtime,
161 | e.g. a literal string, or the name of a variable which holds
162 | the program path.
163 |
164 | ARGS
165 |
166 | Command-line arguments for the program. If provided, this is a
167 | form which evaluates to a list of strings at runtime. Default
168 | is the empty list. This form is evaluated at runtime so that
169 | you can use buffer-local variables to influence the args passed
170 | to the reformatter program: the variable `input-file' will be
171 | lexically bound to the path of a file containing the text to be
172 | reformatted: see the keyword options INPUT-FILE, STDIN and
173 | STDOUT for more information.
174 |
175 | STDIN
176 |
177 | When non-nil (the default), the program is passed the input
178 | data on stdin. Set this to nil when your reformatter can only
179 | operate on files in place. In such a case, your ARGS should
180 | include a reference to the `input-file' variable, which will be
181 | bound to an input path when evaluated.
182 |
183 | STDOUT
184 |
185 | When non-nil (the default), the program is expected to write
186 | the reformatted text to stdout. Set this to nil if your
187 | reformatter can only operate on files in place, in which case
188 | the contents of the temporary input file will be used as the
189 | replacement text.
190 |
191 | INPUT-FILE
192 |
193 | Sometimes your reformatter program might expect files to be in
194 | a certain directory or have a certain file extension. This option
195 | lets you handle that.
196 |
197 | If provided, it is a form which will be evaluated before each
198 | run of the formatter, and is expected to return a temporary
199 | file path suitable for holding the region to be reformatted.
200 | It must not produce the same path as the current buffer's file
201 | if that is set: you shouldn't be operating directly on the
202 | buffer's backing file. The temporary input file will be
203 | deleted automatically. You might find the functions
204 | `reformatter-temp-file-in-current-directory' and
205 | `reformatter-temp-file' helpful.
206 |
207 | MODE
208 |
209 | Unless nil, also generate a minor mode that will call the
210 | reformatter command from `before-save-hook' when enabled.
211 | Default is t.
212 |
213 | GROUP
214 |
215 | If provided, this is the custom group used for any generated
216 | modes or custom variables. Don't forget to declare this group
217 | using a `defgroup' form.
218 |
219 | LIGHTER
220 |
221 | If provided, this is a mode lighter string which will be used
222 | for the \"-on-save\" minor mode. It should have a leading
223 | space. The supplied value will be used as the default for a
224 | generated custom variable which specifies the mode lighter.
225 | Default is nil, ie. no lighter.
226 |
227 | KEYMAP
228 |
229 | If provided, this is the symbol name of the \"-on-save\" mode's
230 | keymap, which you must declare yourself. Default is no keymap.
231 |
232 | EXIT-CODE-SUCCESS-P
233 |
234 | If provided, this is a function object callable with `funcall'
235 | which accepts an integer process exit code, and returns non-nil
236 | if that exit code is considered successful. This could be a
237 | lambda, quoted symbol or sharp-quoted symbol. If not supplied,
238 | the code is considered successful if it is `zerop'.
239 |
240 | WORKING-DIRECTORY
241 |
242 | Directory where your reformatter program is started. If provided, this
243 | should be a form that evaluates to a string at runtime. Default is the
244 | value of `default-directory' in the buffer.
245 |
246 | INTERACTIVE-MODES
247 |
248 | If provided, this is a list of mode names (as unquoted
249 | symbols). The created commands for formatting regions and
250 | buffers are then tagged for interactive use in these modes,
251 | making them compatible with some built-in predicate functions
252 | for `read-extended-command-predicate', like
253 | `command-completion-default-include-p'."
254 | (declare (indent defun))
255 | (cl-assert (symbolp name))
256 | (cl-assert (functionp exit-code-success-p))
257 | (cl-assert program)
258 | ;; Note: we skip using `gensym' here because the macro arguments are only
259 | ;; referred to once below, but this may have to change later.
260 | (let* ((buffer-fn-name (intern (format "%s-buffer" name)))
261 | (region-fn-name (intern (format "%s-region" name)))
262 | (minor-mode-form
263 | (when mode
264 | (let ((on-save-mode-name (intern (format "%s-on-save-mode" name)))
265 | (lighter-name (intern (format "%s-on-save-mode-lighter" name))))
266 | `(progn
267 | (defcustom ,lighter-name ,lighter
268 | ,(format "Mode lighter for `%s'." on-save-mode-name)
269 | :group ,group
270 | :type 'string)
271 | (define-minor-mode ,on-save-mode-name
272 | ,(format "When enabled, call `%s' when this buffer is saved.
273 |
274 | To enable this unconditionally in a major mode, add this mode
275 | to the major mode's hook. To enable it in specific files or directories,
276 | use the local variables \"mode\" mechanism, e.g. in \".dir-locals.el\" you
277 | might use:
278 |
279 | ((some-major-mode
280 | (mode . %s-on-save)))
281 | " buffer-fn-name name)
282 | :global nil
283 | :lighter ,lighter-name
284 | :keymap ,keymap
285 | :group ,group
286 | (if ,on-save-mode-name
287 | (add-hook 'before-save-hook ',buffer-fn-name nil t)
288 | (remove-hook 'before-save-hook ',buffer-fn-name t))))))))
289 | `(progn
290 | (defun ,region-fn-name (beg end &optional display-errors)
291 | "Reformats the region from BEG to END.
292 | When called interactively, or with prefix argument
293 | DISPLAY-ERRORS, shows a buffer if the formatting fails."
294 | (interactive "rp" ,@interactive-modes)
295 | (let ((input-file ,(if input-file
296 | input-file
297 | `(reformatter--make-temp-file ',name))))
298 | ;; Evaluate args with input-file bound
299 | (unwind-protect
300 | (progn
301 | (reformatter--do-region
302 | ',name beg end
303 | ,program ,args ,stdin ,stdout input-file
304 | #',exit-code-success-p display-errors ,working-directory))
305 | (when (file-exists-p input-file)
306 | (delete-file input-file)))))
307 |
308 | (defun ,buffer-fn-name (&optional display-errors)
309 | "Reformats the current buffer.
310 | When called interactively, or with prefix argument
311 | DISPLAY-ERRORS, shows a buffer if the formatting fails."
312 | (interactive "p" ,@interactive-modes)
313 | (message "Formatting buffer")
314 | (,region-fn-name (point-min) (point-max) display-errors))
315 |
316 | ,minor-mode-form)))
317 |
318 |
319 | (defun reformatter-replace-buffer-contents-from-file (file)
320 | "Replace the accessible portion of the current buffer with the contents of FILE."
321 | ;; While the function `replace-buffer-contents' exists in recent
322 | ;; Emacs versions, it exhibits pathologically slow behaviour in many
323 | ;; cases, and the simple replacement approach we use instead is well
324 | ;; proven and typically preserves point and markers to a reasonable
325 | ;; degree.
326 | (insert-file-contents file nil nil nil t))
327 |
328 | (defun reformatter-temp-file (&optional default-extension)
329 | "Make a temp file re-using the current extension.
330 | If the current file is not backed by a file, then use
331 | DEFAULT-EXTENSION, which should not contain a leading dot.
332 |
333 | The working directory for the command will always be the
334 | `default-directory' of the calling buffer."
335 | (let ((extension (if buffer-file-name
336 | (file-name-extension buffer-file-name)
337 | default-extension)))
338 | (make-temp-file "reformatter" nil
339 | (when extension
340 | (concat "." extension)))))
341 |
342 | (defun reformatter-temp-file-in-current-directory (&optional default-extension)
343 | "Make a temp file in the current directory re-using the current extension.
344 | If the current file is not backed by a file, then use
345 | DEFAULT-EXTENSION, which should not contain a leading dot."
346 | (let ((temporary-file-directory default-directory))
347 | (reformatter-temp-file default-extension)))
348 |
349 | (provide 'reformatter)
350 | ;;; reformatter.el ends here
351 |
--------------------------------------------------------------------------------