Skip to content

Commit 5c01da5

Browse files
committed
Update mix-format.el
1 parent 5e0a255 commit 5c01da5

File tree

1 file changed

+196
-43
lines changed

1 file changed

+196
-43
lines changed

mix-format.el

Lines changed: 196 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
;;; mix-format.el --- Emacs plugin to mix format Elixir files
22

3-
;; Copyright (C) 2017 Anil Wadghule
3+
;; Copyright (C) 2018 Anil Wadghule
44

55
;; Author: Anil Wadghule <[email protected]>
66
;; URL: https://github.com/anildigital/mix-format
@@ -21,57 +21,210 @@
2121

2222
;; The mix-format function formats the elixir files with Elixir's `mix format`
2323
;; command
24-
25-
;; e.g.
2624
;;
25+
;; Customize the elixir and mix paths
26+
;;
27+
;; In Emacs, run following command to customize option
28+
;;
29+
;; M-x customize-option
30+
;;
31+
;; Customize-variable: mixfmt-elixir
32+
;; and set your elixir executable path there. After that run:
33+
;;
34+
;; M-x customize-option
35+
;;
36+
;; Customize-variable: mixfmt-mix
37+
;; and set your mix executable path there.
38+
;;
39+
;; Your machine's elixir and mix executable paths can be found with which command as shown below
40+
;;
41+
;; $ which mix
42+
;; /usr/local/bin/mix
43+
;;
44+
;; Usage
45+
;;
46+
;; Require from Emacs
2747
;; (require 'mix-format)
48+
;;
49+
;; Use it
2850
;; M-x mix-format
2951
;;
52+
;; elixir-mode hook
53+
;; (add-hook 'elixir-mode-hook
54+
;; (lambda () (add-hook 'before-save-hook 'mix-format-before-save)))
55+
56+
;; To use a .formatter.exs you can either set mixfmt-args globally to a path like this:
57+
58+
;; (setq mixfmt-args (list "--dot-formatter" "/path/to/.formatter.exs"))
59+
;; or you set mixfmt-args in a hook like this:
60+
61+
;; (add-hook 'mix-format-hook '(lambda ()
62+
;; (if (projectile-project-p)
63+
;; (setq mixfmt-args (list "--dot-formatter" (concat (projectile-project-root) "/.formatter.exs")))
64+
;; (setq mixfmt-args nil))))
65+
;;
66+
;; In this example we use Projectile to get the project root and set mixfmt-args accordingly.
67+
68+
(defcustom mixfmt-elixir "elixir"
69+
"Path to the Elixir interpreter."
70+
:type 'string
71+
:group 'mix-format)
72+
73+
(defcustom mixfmt-mix "/usr/bin/mix"
74+
"Path to the 'mix' executable."
75+
:type 'string
76+
:group 'mix-format)
77+
78+
(defcustom mixfmt-args nil
79+
"Additional arguments to 'mix format'"
80+
:type '(repeat string)
81+
:group 'mix-format)
82+
83+
(defcustom mix-format-hook nil
84+
"Hook called by `mix-format'."
85+
:type 'hook
86+
:group 'mix-format)
87+
3088

3189
;;; Code
90+
91+
;;;###autoload
92+
(defun mix-format-before-save ()
93+
"Add this to .emacs to run mix format on the current buffer when saving:
94+
\(add-hook 'before-save-hook 'mix-format-before-save).
95+
96+
Note that this will cause ‘elixir-mode’ to get loaded the first time
97+
you save any file, kind of defeating the point of autoloading."
98+
99+
(interactive)
100+
(when (eq major-mode 'elixir-mode) (mix-format)))
101+
102+
103+
(defun mixfmt--goto-line (line)
104+
(goto-char (point-min))
105+
(forward-line (1- line)))
106+
107+
(defun mixfmt--delete-whole-line (&optional arg)
108+
"Delete the current line without putting it in the `kill-ring'.
109+
Derived from function `kill-whole-line'. ARG is defined as for that
110+
function.
111+
112+
Shamelessly stolen from go-mode (https://github.com/dominikh/go-mode.el)"
113+
(setq arg (or arg 1))
114+
(if (and (> arg 0)
115+
(eobp)
116+
(save-excursion (forward-visible-line 0) (eobp)))
117+
(signal 'end-of-buffer nil))
118+
(if (and (< arg 0)
119+
(bobp)
120+
(save-excursion (end-of-visible-line) (bobp)))
121+
(signal 'beginning-of-buffer nil))
122+
(cond ((zerop arg)
123+
(delete-region (progn (forward-visible-line 0) (point))
124+
(progn (end-of-visible-line) (point))))
125+
((< arg 0)
126+
(delete-region (progn (end-of-visible-line) (point))
127+
(progn (forward-visible-line (1+ arg))
128+
(unless (bobp)
129+
(backward-char))
130+
(point))))
131+
(t
132+
(delete-region (progn (forward-visible-line 0) (point))
133+
(progn (forward-visible-line arg) (point))))))
134+
135+
(defun mixfmt--apply-rcs-patch (patch-buffer)
136+
"Apply an RCS-formatted diff from PATCH-BUFFER to the current buffer.
137+
Shamelessly stolen from go-mode (https://github.com/dominikh/go-mode.el)"
138+
139+
(let ((target-buffer (current-buffer))
140+
;; Relative offset between buffer line numbers and line numbers
141+
;; in patch.
142+
;;
143+
;; Line numbers in the patch are based on the source file, so
144+
;; we have to keep an offset when making changes to the
145+
;; buffer.
146+
;;
147+
;; Appending lines decrements the offset (possibly making it
148+
;; negative), deleting lines increments it. This order
149+
;; simplifies the forward-line invocations.
150+
(line-offset 0))
151+
(save-excursion
152+
(with-current-buffer patch-buffer
153+
(goto-char (point-min))
154+
(while (not (eobp))
155+
(unless (looking-at "^\\([ad]\\)\\([0-9]+\\) \\([0-9]+\\)")
156+
(error "Invalid rcs patch or internal error in mixfmt--apply-rcs-patch"))
157+
(forward-line)
158+
(let ((action (match-string 1))
159+
(from (string-to-number (match-string 2)))
160+
(len (string-to-number (match-string 3))))
161+
(cond
162+
((equal action "a")
163+
(let ((start (point)))
164+
(forward-line len)
165+
(let ((text (buffer-substring start (point))))
166+
(with-current-buffer target-buffer
167+
(cl-decf line-offset len)
168+
(goto-char (point-min))
169+
(forward-line (- from len line-offset))
170+
(insert text)))))
171+
((equal action "d")
172+
(with-current-buffer target-buffer
173+
(mixfmt--goto-line (- from line-offset))
174+
(cl-incf line-offset len)
175+
(mixfmt--delete-whole-line len)))
176+
(t
177+
(error "Invalid rcs patch or internal error in mixfmt--apply-rcs-patch"))))))))
178+
)
179+
180+
;;;###autoload
32181
(defun mix-format (&optional is-interactive)
33182
(interactive "p")
34183

35-
(unwind-protect
36-
(let* (
37-
(in-file (make-temp-file "mix-format"))
38-
(out-file (make-temp-file "mix-format"))
39-
(err-file (make-temp-file "mix-format"))
40-
(contents (buffer-substring-no-properties (point-min) (point-max)))
41-
(_ (with-temp-file in-file (insert contents))))
42-
43-
44-
(let* ((command "mix")
45-
(error-buffer (get-buffer-create "*mix-format errors*"))
46-
(retcode (with-temp-buffer
47-
(call-process command
48-
nil (list out-file err-file)
49-
nil
50-
"format"
51-
"--print"
52-
in-file))))
53-
54-
(with-current-buffer error-buffer
55-
(read-only-mode 0)
56-
(insert-file-contents err-file nil nil nil t)
57-
(ansi-color-apply-on-region (point-min) (point-max))
58-
(special-mode))
59-
60-
(if (zerop retcode )
61-
(let ((p (point)))
62-
(save-excursion
63-
(erase-buffer)
64-
(insert-buffer-substring out-file)
65-
(message "mix format applied"))
66-
(goto-char p))
67-
68-
(if is-interactive
69-
(display-buffer error-buffer)
70-
(message "mix-format failed: see %s" (buffer-name error-buffer)))
71-
))
72-
(delete-file in-file)
73-
(delete-file err-file)
74-
(delete-file out-file))))
184+
(let ((outbuff (get-buffer-create "*mix-format-output*"))
185+
(errbuff (get-buffer-create "*mix-format-errors*"))
186+
(tmpfile (make-temp-file "mix-format" nil ".ex"))
187+
(our-mixfmt-args (list mixfmt-mix "format"))
188+
(output nil))
189+
190+
(unwind-protect
191+
(save-restriction
192+
(with-current-buffer outbuff
193+
(erase-buffer))
194+
195+
(with-current-buffer errbuff
196+
(setq buffer-read-only nil)
197+
(erase-buffer))
198+
199+
(write-region nil nil tmpfile)
200+
201+
(run-hooks 'mix-format-hook)
202+
203+
(when mixfmt-args
204+
(setq our-mixfmt-args (append our-mixfmt-args mixfmt-args)))
205+
(setq our-mixfmt-args (append our-mixfmt-args (list tmpfile)))
206+
207+
(if (zerop (apply #'call-process mixfmt-elixir nil errbuff nil our-mixfmt-args))
208+
(progn
209+
(if (zerop (call-process-region (point-min) (point-max) "diff" nil outbuff nil "-n" "-" tmpfile))
210+
(message "File is already formatted")
211+
(progn
212+
(mixfmt--apply-rcs-patch outbuff)
213+
(message "mix format applied")))
214+
(kill-buffer errbuff))
215+
216+
(progn
217+
(with-current-buffer errbuff
218+
(setq buffer-read-only t)
219+
(ansi-color-apply-on-region (point-min) (point-max))
220+
(special-mode))
221+
222+
(if is-interactive
223+
(display-buffer errbuff)
224+
(message "mix-format failed: see %s" (buffer-name errbuff)))))
225+
226+
(delete-file tmpfile)
227+
(kill-buffer outbuff)))))
75228

76229
(provide 'mix-format)
77230

0 commit comments

Comments
 (0)