-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathclang-format+.el
271 lines (230 loc) · 10.8 KB
/
clang-format+.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
;;; clang-format+.el --- Minor mode for automatic clang-format application -*- lexical-binding: t; -*-
;; Copyright (c) 2019 Valeriy Savchenko (GNU/GPL Licence)
;; Authors: Valeriy Savchenko <sinmipt@gmail.com>
;; URL: https://github.com/SavchenkoValeriy/emacs-clang-format-plus
;; Version: 1.0.0
;; Package-Requires: ((emacs "25.1") (clang-format "20180406.1514"))
;; Keywords: c c++ clang-format
;; This file is NOT part of GNU Emacs.
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program; if not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; Minor mode for applying clang-format automatically on save.
;; It can also apply clang-format to the modified parts of the region only
;; and try to be smart about it.
;;; Code:
(require 'cc-cmds)
(require 'clang-format)
(require 'cl-lib)
(defgroup clang-format+ nil
"Minor mode for automatic clang-format application"
:group 'convenience)
(defcustom clang-format+-context
'definition
"How much context to reformat after modifications.
When a buffer is modified, clang-format+ can reformat only the
modified parts or larger enclosing contexts. The default is to
reformat the whole class or function in which a modification is
made.
Possible values:
`buffer' Reformat the whole buffer.
`definition' Reformat the enclosing definition (class/function/etc., but not
namespace).
`modification' Reformat only the modified parts."
:type '(choice (const :tag "The whole buffer" buffer)
(const :tag "The whole class or function" definition)
(const :tag "Only the modification" modification))
:group 'clang-format+)
(defcustom clang-format+-offset-modified-region
0
"Number of extra lines to reformat outside of a modified region.
Clang-format+ extends the region to reformat both at the
beginning and at the end. If `clang-format+-context' is set to
`definition', the region will only be extended for modifications
outside of definitions."
:type 'integer
:group 'clang-format+)
(defcustom clang-format+-always-enable
nil
"Whether to enable formatting even if no style has been selected.
If nil, only enable formatting if a style has been selected by
either adding a \".clang-format\" (or \"_clang-format\") file to
the source tree or by setting `clang-format-style' to something
else than \"file\". If non-nil, always enable formatting."
:type 'boolean
:group 'clang-format+)
(defvar clang-format+-saved)
(defvar clang-format+-enabled)
(make-variable-buffer-local 'clang-format+-enabled)
(defmacro clang-format+-with-save (&rest forms)
"Run FORMS with restriction and excursion saved once."
(declare (debug (body)))
`(if (and (boundp 'clang-format+-saved)
clang-format+-saved)
(progn
,@forms)
(let ((clang-format+-saved t))
(save-excursion
(save-restriction
,@forms)))))
(defun clang-format+-map-changes (func &optional start-position end-position)
"Call FUNC with each changed region (START-POSITION END-POSITION).
This simply uses an end marker since we are modifying the buffer
in place."
;; See `hilit-chg-map-changes' and `ws-butler-map-changes'.
(let ((start (or start-position (point-min)))
(limit (copy-marker (or end-position (point-max))))
prop end)
(while (and start (< start limit))
(setq prop (get-text-property start 'clang-format+-chg))
(setq end (text-property-not-all start limit 'clang-format+-chg prop))
(if prop
(funcall func prop start (or end limit)))
(setq start end))
(set-marker limit nil)))
(defun clang-format+-before-save ()
"Run ‘clang-format’ on the current buffer."
(if (eq clang-format+-context 'buffer)
(clang-format-buffer)
(clang-format+-apply-to-modifications)))
(defun clang-format+-apply-to-modifications ()
"Apply ‘clang-format’ to modified parts of the current buffer."
(let ((processed nil))
(clang-format+-map-changes
(lambda (_prop beg end)
(setq beg (clang-format+-get-region-beginning beg)
end (clang-format+-get-region-end end))
;; We run clang-format for every little change to the code N times,
;; which is exactly N-1 times more than needed.
;; Unfortunately, while the clang-format executable accepts many regions
;; as its input, the clang-format-region function accepts only one.
;;
;; And even though we run it this many times, we at least don't want
;; to run it more than once on the same piece of code.
;;
;; For this purpose, we check that this region is not processed yet...
(unless (clang-format+-in-processed processed beg end)
;; no need to track changes made by clang-format
(remove-hook 'after-change-functions
'clang-format+-after-change t)
(clang-format-region beg end)
;; ...and remember processed ones
(add-to-list 'processed (cons beg end))
(add-hook 'after-change-functions
#'clang-format+-after-change t t))))))
(defun clang-format+-in-processed (processed beg end)
"Check if the given region BEG END is in PROCESSED.
PROCESSED should be a list of cons pairs denoting begins
and ends of already processed regions."
(cl-some (lambda (region) (<= (car region) beg end (cdr region)))
processed))
(defun clang-format+-get-region-beginning (pointer)
"Get where the reformatting region should start for the POINTER."
(clang-format+-get-region-internal pointer
#'c-beginning-of-defun
#'previous-line))
(defun clang-format+-get-region-end (pointer)
"Get where the reformatting region should end for the POINTER."
;; Subtract one from end to overcome Emacs bug #17784, since we
;; always expand to end of line anyway, this should be OK.
(clang-format+-get-region-internal (1- pointer)
#'c-end-of-defun
#'next-line))
(defun clang-format+-get-region-internal (pointer
definition-move
fallback-move)
"Move from POINTER by one of the given move actions and return the new point.
Only returns a new point, not persistently moving there.
DEFINITION-MOVE will be used if POINTER is inside of a definition.
DEFINITION-MOVE shouldn't take any arguments.
FALLBACK-MOVE will be used if POINTER is outside of the definition,
or when modification of the whole definition is not allowed."
(save-excursion
(goto-char pointer)
(if (and (eq clang-format+-context 'definition)
(clang-format+-inside-of-enclosing-definition-p))
(funcall definition-move)
(funcall fallback-move clang-format+-offset-modified-region))
(point)))
(defun clang-format+-inside-of-enclosing-definition-p ()
"Check if the pointer inside of the definition."
;; Adding new functions or classes into namespaces is a normal practice,
;; and we shouldn't reformat the WHOLE namespace because of this.
;; That's why we don't want to consider it as a definition.
(unless (clang-format+-inside-of-namespace-p)
(save-excursion
(let ((original (point))
(start (progn (c-beginning-of-defun) (point)))
(end (progn (c-end-of-defun) (point))))
(<= start original end)))))
(defun clang-format+-inside-of-namespace-p ()
"Check if the pointer is inside of a namespace."
;; this code is highly inspired by the code
;; from `c-show-syntactic-information'
(let* ((syntax-stack (if (boundp 'c-syntactic-context)
;; Use `c-syntactic-context' in the same way as
;; `c-indent-line', to be consistent.
c-syntactic-context
(c-save-buffer-state nil
(c-guess-basic-syntax))))
(top-level-context (caar syntax-stack)))
(equal top-level-context 'innamespace)))
(defun clang-format+-clear-properties ()
"Clear all clang-format+ text properties in buffer."
(with-silent-modifications
(clang-format+-map-changes (lambda (_prop start end)
(remove-list-of-text-properties
start end '(clang-format+-chg))))))
(defun clang-format+-after-change (beg end length-before)
"Remember buffer modification.
Mark text from BEG to END as modified.
LENGTH-BEFORE stands for the length of the text before modification."
(let ((type (if (and (= beg end) (> length-before 0))
'delete
'chg)))
(if undo-in-progress
;; add back deleted text during undo
(if (and (zerop length-before)
(> end beg)
(eq (get-text-property end 'clang-format+-chg) 'delete))
(remove-list-of-text-properties end (1+ end) '(clang-format+-chg)))
(with-silent-modifications
(when (eq type 'delete)
(setq end (min (+ end 1) (point-max))))
(put-text-property beg end 'clang-format+-chg type)))))
(defun clang-format+-after-save ()
"Restore trimmed whitespace before point."
(clang-format+-clear-properties))
;;;###autoload
(define-minor-mode clang-format+-mode
"Run clang-format on save."
:lighter (:eval (if clang-format+-enabled " cf+" " cf-"))
:group 'clang-format+
(if clang-format+-mode
(progn
(setq clang-format+-enabled
(or clang-format+-always-enable
(not (string= clang-format-style "file"))
(locate-dominating-file "." ".clang-format")
(locate-dominating-file "." "_clang-format")))
(when clang-format+-enabled
(add-hook 'after-change-functions #'clang-format+-after-change t t)
(add-hook 'before-save-hook #'clang-format+-before-save t t)
(add-hook 'after-save-hook #'clang-format+-after-save t t)
(add-hook 'after-revert-hook #'clang-format+-after-save t t)
(add-hook 'edit-server-done-hook #'clang-format+-before-save t t)))
(remove-hook 'after-change-functions 'clang-format+-after-change t)
(remove-hook 'before-save-hook 'clang-format+-before-save t)
(remove-hook 'after-save-hook 'clang-format+-after-save t)
(remove-hook 'after-revert-hook 'clang-format+-after-save t)
(remove-hook 'edit-server-done-hook 'clang-format+-before-save t)))
(provide 'clang-format+)
;;; clang-format+.el ends here