1 ;; gnome-c-align.el --- GNOME-style code alignment -*- lexical-binding: t; -*-
2 ;; Copyright (C) 2016 Free Software Foundation, Inc.
4 ;; Author: Daiki Ueno <ueno@gnu.org>
5 ;; Keywords: GNOME, C, coding style
7 ;; This file is not part of GNU Emacs.
9 ;; This program is free software: you can redistribute it and/or
10 ;; modify it under the terms of the GNU General Public License as
11 ;; published by the Free Software Foundation, either version 3 of the
12 ;; License, or (at your option) any later version.
14 ;; This program is distributed in the hope that it will be useful, but
15 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 ;; General Public License for more details.
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with this program. If not, see
21 ;; <http://www.gnu.org/licenses/>.
28 (defcustom gnome-c-align-max-column 80
29 "Maximum number of columns per line."
30 :type '(choice (integer :tag "Columns")
31 (const :tag "No wrap"))
32 :group 'gnome-c-style)
34 (defvar gnome-c-align-identifier-start-column nil)
35 (make-variable-buffer-local 'gnome-c-align-identifier-start-column)
37 (defvar gnome-c-align-arglist-start-column nil)
38 (make-variable-buffer-local 'gnome-c-align-arglist-start-column)
40 (defvar gnome-c-align-arglist-identifier-start-column nil)
41 (make-variable-buffer-local 'gnome-c-align-arglist-identifier-start-column)
43 (cl-defstruct (gnome-c-align--argument
45 (:constructor gnome-c-align--make-argument (type-start
52 (type-start nil :read-only t)
53 (type-identifier-end nil :read-only t)
54 (type-end nil :read-only t)
55 (identifier-start nil :read-only t)
56 (identifier-end nil :read-only t))
58 (defun gnome-c-align--marker-column (marker)
63 (defun gnome-c-align--indent-to-column (column)
64 ;; Prefer 'char **foo' than 'char ** foo'
65 (when (looking-back "\\*+" nil t)
66 (setq column (- column (- (match-end 0) (match-beginning 0))))
67 (goto-char (match-beginning 0)))
68 ;; FIXME: should respect indent-tabs-mode?
69 (let (indent-tabs-mode)
70 (indent-to-column column)))
72 (defun gnome-c-align--argument-type-width (arg)
73 (- (gnome-c-align--marker-column (gnome-c-align--argument-type-end arg))
74 (gnome-c-align--marker-column (gnome-c-align--argument-type-start arg))))
76 (defun gnome-c-align--argument-type-identifier-width (arg)
77 (- (gnome-c-align--marker-column
78 (gnome-c-align--argument-type-identifier-end arg))
79 (gnome-c-align--marker-column
80 (gnome-c-align--argument-type-start arg))))
82 (defun gnome-c-align--arglist-identifier-start-column (arglist start-column)
83 (let ((max-type-identifier-width
86 (mapcar #'gnome-c-align--argument-type-identifier-width
93 (- (gnome-c-align--argument-type-end argument)
94 (gnome-c-align--argument-type-identifier-end argument)))
96 (+ start-column max-type-identifier-width max-extra-width)))
98 (defun gnome-c-align--argument-identifier-width (argument)
99 (if (gnome-c-align--argument-identifier-start argument)
100 (- (gnome-c-align--marker-column
101 (gnome-c-align--argument-identifier-end argument))
102 (gnome-c-align--marker-column
103 (gnome-c-align--argument-identifier-start argument)))
106 (defun gnome-c-align--arglist-identifier-width (arglist)
107 (apply #'max 0 (mapcar #'gnome-c-align--argument-identifier-width arglist)))
109 (defun gnome-c-align--normalize-arglist-region (arglist beg end)
112 (narrow-to-region beg end)
113 (goto-char (point-min))
114 (while (re-search-forward "\\s-+" nil t)
116 (goto-char (point-min))
117 (while (re-search-forward "\\s-*," nil t)
118 (replace-match ",\n"))
119 (goto-char (point-min))
120 (delete-trailing-whitespace)
121 ;; Remove whitespace at the beginning of line
122 (goto-char (point-min))
123 (while (re-search-forward "^\\s-+" nil t)
125 ;; Remove empty lines
126 (goto-char (point-min))
127 (delete-matching-lines "^$")
128 ;; 'int * * * foo' -> 'int ***foo'
129 (dolist (argument arglist)
130 (goto-char (gnome-c-align--argument-type-end argument))
131 (while (re-search-backward
133 (gnome-c-align--argument-type-identifier-end argument)
135 (replace-match "\\1"))
136 (when (gnome-c-align--argument-identifier-start argument)
137 (goto-char (gnome-c-align--argument-identifier-start argument))
138 (if (looking-back "\\* " nil)
140 (goto-char (gnome-c-align--argument-type-end argument))))))
142 (defun gnome-c-align--parse-arglist (beg end)
145 (narrow-to-region beg end)
153 (goto-char (point-max))
155 (c-backward-syntactic-ws)
156 (setq identifier-end (point-marker))
157 ;; Array argument, such as 'int a[]'
158 (if (eq (preceding-char) ?\])
161 (setq identifier-start (point-marker))
162 (c-backward-syntactic-ws)
163 (if (or (bobp) (eq (preceding-char) ?,))
165 ;; Identifier is omitted, or '...'.
166 (setq type-start identifier-start
167 type-identifier-end identifier-end
168 type-end identifier-end
171 (c-backward-token-2))
172 (setq type-end (point-marker)
173 last-token-start type-end)
174 (while (and (not (bobp))
177 (unless (eq (char-after) ?,)
178 (setq last-token-start (point-marker)))))
179 (c-backward-syntactic-ws))
180 (setq type-start last-token-start)
183 (skip-chars-backward "* " type-start)
184 (c-backward-syntactic-ws)
185 (setq type-identifier-end (point-marker))))
186 (push (gnome-c-align--make-argument type-start
195 (defun gnome-c-align-arglist-at-point (&optional identifier-start-column)
196 "Reformat argument list at point, aligning argument to the right end."
199 (let* (start-column arglist)
200 (cl-destructuring-bind (beg end)
201 (gnome-c-align--arglist-region-at-point (point))
203 (setq start-column (current-column))
205 (narrow-to-region beg end)
206 (setq arglist (gnome-c-align--parse-arglist (point-min) (point-max)))
207 (gnome-c-align--normalize-arglist-region
208 arglist (point-min) (point-max))
209 (unless identifier-start-column
210 (setq identifier-start-column
211 (gnome-c-align--arglist-identifier-start-column arglist 0)))
212 (dolist (argument arglist)
213 (goto-char (gnome-c-align--argument-type-start argument))
214 (let ((column (if (bobp) 0 start-column)))
216 (gnome-c-align--indent-to-column start-column))
217 (when (gnome-c-align--argument-identifier-start argument)
218 (setq column (+ column identifier-start-column))
219 (goto-char (gnome-c-align--argument-identifier-start argument))
220 (gnome-c-align--indent-to-column column)))))))))
222 (cl-defstruct (gnome-c-align--decl
224 (:constructor gnome-c-align--make-decl (start
233 (start nil :read-only t)
234 (end nil :read-only t)
235 (identifier-start nil :read-only t)
236 (identifier-end nil :read-only t)
237 (arglist-start nil :read-only t)
238 (arglist-end nil :read-only t)
239 (arglist nil :read-only t))
241 (defun gnome-c-align--decls-identifier-start-column (decls start-column)
249 (gnome-c-align--marker-column
250 (gnome-c-align--decl-identifier-start decl)))))
251 (if (and gnome-c-align-max-column
252 (> decl-column gnome-c-align-max-column))
257 (defun gnome-c-align--decl-identifier-width (decl)
258 (- (gnome-c-align--marker-column
259 (gnome-c-align--decl-identifier-end decl))
260 (gnome-c-align--marker-column
261 (gnome-c-align--decl-identifier-start decl))))
263 (defun gnome-c-align--decls-arglist-start-column (decls start-column)
265 (+ (gnome-c-align--decls-arglist-identifier-start-column decls 0)
266 (gnome-c-align--decls-arglist-identifier-width decls)
275 (gnome-c-align--decl-identifier-width decl)
277 (if (and gnome-c-align-max-column
278 (> (+ decl-column arglist-width)
279 gnome-c-align-max-column))
284 (defun gnome-c-align--decls-arglist-identifier-width (decls)
285 (apply #'max 0 (mapcar (lambda (decl)
286 (gnome-c-align--arglist-identifier-width
287 (gnome-c-align--decl-arglist decl)))
290 (defun gnome-c-align--decls-arglist-identifier-start-column (decls start-column)
291 (apply #'max 0 (mapcar (lambda (decl)
292 ;; FIXME: should wrap lines inside argument list?
293 (gnome-c-align--arglist-identifier-start-column
294 (gnome-c-align--decl-arglist decl)
298 (defun gnome-c-align--parse-decl (beg end)
299 ;; Parse at most one func declaration found in BEG END.
302 (narrow-to-region beg end)
308 (goto-char (point-min))
309 (c-forward-syntactic-ws)
311 "typedef\\|#\\|G_\\(?:DECLARE\\|DEFINE\\)")
312 (while (and (not (eobp))
313 (not (eq (char-after) ?\()))
315 (c-forward-syntactic-ws))
316 ;; Identifier is vfunc.
317 (when (looking-at "(\\s-*\\*")
319 (c-forward-syntactic-ws)
321 (when (eq (char-after) ?\()
322 (setq arglist-start (point-marker))
323 (c-backward-syntactic-ws)
324 (setq identifier-end (point-marker))
327 (c-backward-token-2))
328 (setq identifier-start (point-marker))
329 (goto-char arglist-start)
331 (setq arglist-end (point-marker))
332 (gnome-c-align--make-decl beg end
333 identifier-start identifier-end
334 arglist-start arglist-end
335 (gnome-c-align--parse-arglist
337 (1- arglist-end)))))))))
339 (defun gnome-c-align--normalize-decl (decl)
341 ;; Replace newlines with a space
343 ;; Ignore lines before identifier-start
344 (goto-char (gnome-c-align--decl-identifier-start decl))
346 (narrow-to-region (point)
347 (gnome-c-align--decl-arglist-end decl))
348 (goto-char (point-min))
349 (while (re-search-forward "\n" nil t)
350 (replace-match " ")))
351 ;; Replace consequent spaces with a space
353 ;; Ignore lines before identifier-start
354 (goto-char (gnome-c-align--decl-identifier-start decl))
356 (narrow-to-region (point)
357 (gnome-c-align--decl-arglist-end decl))
358 (goto-char (point-min))
359 (while (re-search-forward "\\s-+" nil t)
360 (replace-match " ")))
361 (goto-char (gnome-c-align--decl-identifier-start decl))
362 (if (looking-back "\\* " nil)
364 ;; Normalize the argument list
365 (gnome-c-align--normalize-arglist-region
366 (gnome-c-align--decl-arglist decl)
367 (gnome-c-align--decl-arglist-start decl)
368 (gnome-c-align--decl-arglist-end decl))))
370 (defun gnome-c-align--arglist-region-at-point (point)
374 (c-beginning-of-statement-1)
375 (c-backward-syntactic-ws)
376 (unless (eq ?\( (preceding-char))
377 (error "No containing argument list"))
383 (error "No closing parenthesis")))
385 (list start (point)))))
388 (defun gnome-c-align-set-column (symbol)
389 "Set alignment column of SYMBOL."
391 (let ((symbol-name (completing-read "Symbol to change: "
394 "arglist-identifier-start")
396 (list (intern (format "gnome-c-align-%s-column" symbol-name)))))
397 (set symbol (current-column)))
399 (defun gnome-c-align--scan-decls (beg end)
402 (narrow-to-region beg end)
403 (goto-char (point-min))
406 (let (decl-start decl-end decl)
407 (c-forward-syntactic-ws)
408 (setq decl-start (point-marker))
410 (setq decl-end (point-marker))
411 (setq decl (gnome-c-align--parse-decl decl-start decl-end))
416 (defun gnome-c-align--guess-optimal-columns (beg end)
417 (let ((buffer (current-buffer))
420 (insert-buffer-substring-no-properties buffer beg end)
422 (setq decls (gnome-c-align--scan-decls (point-min) (point-max)))
423 (mapc #'gnome-c-align--normalize-decl decls)
424 (let* ((identifier-start-column
425 (gnome-c-align--decls-identifier-start-column
427 (arglist-start-column
428 (gnome-c-align--decls-arglist-start-column
429 decls identifier-start-column))
430 (arglist-identifier-start-column
431 (gnome-c-align--decls-arglist-identifier-start-column
432 decls (+ (length "(") arglist-start-column))))
433 (list (cons 'identifier-start-column
434 identifier-start-column)
435 (cons 'arglist-start-column
436 arglist-start-column)
437 (cons 'arglist-identifier-start-column
438 arglist-identifier-start-column))))))
441 (defun gnome-c-align-guess-optimal-columns (beg end)
442 "Compute the optimal alignment rule from the declarations in BEG and END.
444 This sets `gnome-c-align-identifier-start-column',
445 `gnome-c-align-arglist-start-column', and
446 `gnome-c-align-arglist-identifier-start-column'."
448 (let ((columns (gnome-c-align--guess-optimal-columns beg end)))
449 (setq gnome-c-align-identifier-start-column
450 (cdr (assq 'identifier-start-column columns))
451 gnome-c-align-arglist-start-column
452 (cdr (assq 'arglist-start-column columns))
453 gnome-c-align-arglist-identifier-start-column
454 (cdr (assq 'arglist-identifier-start-column columns)))
456 "identifier-start: %d, arglist-start: %d, arglist-identifier-start: %d"
457 gnome-c-align-identifier-start-column
458 gnome-c-align-arglist-start-column
459 gnome-c-align-arglist-identifier-start-column)))
462 (defun gnome-c-align-guess-columns (beg end)
463 "Guess the existing alignment rule from the declarations in BEG and END.
465 This sets `gnome-c-align-identifier-start-column',
466 `gnome-c-align-arglist-start-column', and
467 `gnome-c-align-arglist-identifier-start-column'."
469 (let ((decls (gnome-c-align--scan-decls beg end))
472 (error "No function declaration in the region"))
473 (setq arglist (gnome-c-align--parse-arglist
474 (1+ (gnome-c-align--decl-arglist-start (car decls)))
475 (1- (gnome-c-align--decl-arglist-end (car decls)))))
477 (error "Empty argument list"))
478 (unless (gnome-c-align--argument-identifier-start (car arglist))
479 (error "No identifier in the argument list"))
480 (setq gnome-c-align-identifier-start-column
481 (gnome-c-align--marker-column
482 (gnome-c-align--decl-identifier-start (car decls)))
483 gnome-c-align-arglist-start-column
484 (gnome-c-align--marker-column
485 (gnome-c-align--decl-arglist-start (car decls)))
486 gnome-c-align-arglist-identifier-start-column
487 (gnome-c-align--marker-column
488 (gnome-c-align--argument-identifier-start (car arglist))))
490 "identifier-start: %d, arglist-start: %d, arglist-identifier-start: %d"
491 gnome-c-align-identifier-start-column
492 gnome-c-align-arglist-start-column
493 gnome-c-align-arglist-identifier-start-column)))
496 (defun gnome-c-align-decls-region (beg end)
497 "Reformat function declarations in the region between BEG and END."
502 (narrow-to-region beg end)
503 (unless (and gnome-c-align-identifier-start-column
504 gnome-c-align-arglist-start-column
505 gnome-c-align-arglist-identifier-start-column)
506 (let ((columns (gnome-c-align--guess-optimal-columns beg end)))
507 (unless gnome-c-align-identifier-start-column
508 (setq gnome-c-align-identifier-start-column
509 (cdr (assq 'identifier-start-column columns))))
510 (unless gnome-c-align-arglist-start-column
511 (setq gnome-c-align-arglist-start-column
512 (cdr (assq 'arglist-start-column columns))))
513 (unless gnome-c-align-arglist-identifier-start-column
514 (setq gnome-c-align-arglist-identifier-start-column
515 (cdr (assq 'arglist-identifier-start-column columns))))))
516 (setq decls (gnome-c-align--scan-decls beg end))
517 (mapc #'gnome-c-align--normalize-decl decls)
519 (goto-char (gnome-c-align--decl-identifier-start decl))
520 (gnome-c-align--indent-to-column
521 gnome-c-align-identifier-start-column)
522 (goto-char (gnome-c-align--decl-identifier-end decl))
523 (when (>= (current-column) gnome-c-align-arglist-start-column)
525 (goto-char (gnome-c-align--decl-arglist-start decl))
526 (gnome-c-align--indent-to-column
527 gnome-c-align-arglist-start-column)
529 (gnome-c-align-arglist-at-point
530 (- (- gnome-c-align-arglist-identifier-start-column
532 gnome-c-align-arglist-start-column)))))))
534 (provide 'gnome-c-align)
536 ;;; gnome-c-align.el ends here