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 part of GNU Emacs.
9 ;; GNU Emacs is free software: you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation, either version 3 of the License, or
12 ;; (at your option) any later version.
14 ;; GNU Emacs is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ;; GNU General Public License for more details.
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
27 (defcustom gnome-c-align-max-column 80
28 "Maximum number of columns per line."
29 :type '(choice (integer :tag "Columns")
30 (const :tag "No wrap"))
31 :group 'gnome-c-style)
33 (defvar gnome-c-align-identifier-start-column nil)
34 (make-variable-buffer-local 'gnome-c-align-identifier-start-column)
36 (defvar gnome-c-align-arglist-start-column nil)
37 (make-variable-buffer-local 'gnome-c-align-arglist-start-column)
39 (defvar gnome-c-align-arglist-identifier-start-column nil)
40 (make-variable-buffer-local 'gnome-c-align-arglist-identifier-start-column)
42 (cl-defstruct (gnome-c-align--argument
44 (:constructor gnome-c-align--make-argument (type-start
51 (type-start nil :read-only t)
52 (type-identifier-end nil :read-only t)
53 (type-end nil :read-only t)
54 (identifier-start nil :read-only t)
55 (identifier-end nil :read-only t))
57 (defun gnome-c-align--marker-column (marker)
62 (defun gnome-c-align--indent-to-column (column)
63 ;; Prefer 'char **foo' than 'char ** foo'
64 (when (looking-back "\\*+" nil t)
65 (setq column (- column (- (match-end 0) (match-beginning 0))))
66 (goto-char (match-beginning 0)))
67 ;; FIXME: should respect indent-tabs-mode?
68 (let (indent-tabs-mode)
69 (indent-to-column column)))
71 (defun gnome-c-align--argument-type-width (arg)
72 (- (gnome-c-align--marker-column (gnome-c-align--argument-type-end arg))
73 (gnome-c-align--marker-column (gnome-c-align--argument-type-start arg))))
75 (defun gnome-c-align--argument-type-identifier-width (arg)
76 (- (gnome-c-align--marker-column
77 (gnome-c-align--argument-type-identifier-end arg))
78 (gnome-c-align--marker-column
79 (gnome-c-align--argument-type-start arg))))
81 (defun gnome-c-align--arglist-identifier-start-column (arglist start-column)
82 (let ((max-type-identifier-width
85 (mapcar #'gnome-c-align--argument-type-identifier-width
92 (- (gnome-c-align--argument-type-end argument)
93 (gnome-c-align--argument-type-identifier-end argument)))
95 (+ start-column max-type-identifier-width max-extra-width)))
97 (defun gnome-c-align--argument-identifier-width (argument)
98 (if (gnome-c-align--argument-identifier-start argument)
99 (- (gnome-c-align--marker-column
100 (gnome-c-align--argument-identifier-end argument))
101 (gnome-c-align--marker-column
102 (gnome-c-align--argument-identifier-start argument)))
105 (defun gnome-c-align--arglist-identifier-width (arglist)
106 (apply #'max 0 (mapcar #'gnome-c-align--argument-identifier-width arglist)))
108 (defun gnome-c-align--normalize-arglist-region (arglist beg end)
111 (narrow-to-region beg end)
112 (goto-char (point-min))
113 (while (re-search-forward "\\s-+" nil t)
115 (goto-char (point-min))
116 (while (re-search-forward "\\s-*," nil t)
117 (replace-match ",\n"))
118 (goto-char (point-min))
119 (delete-trailing-whitespace)
120 ;; Remove whitespace at the beginning of line
121 (goto-char (point-min))
122 (while (re-search-forward "^\\s-+" nil t)
124 ;; Remove empty lines
125 (goto-char (point-min))
126 (delete-matching-lines "^$")
127 ;; 'int * * * foo' -> 'int ***foo'
128 (dolist (argument arglist)
129 (goto-char (gnome-c-align--argument-type-end argument))
130 (while (re-search-backward
132 (gnome-c-align--argument-type-identifier-end argument)
134 (replace-match "\\1"))
135 (when (gnome-c-align--argument-identifier-start argument)
136 (goto-char (gnome-c-align--argument-identifier-start argument))
137 (if (looking-back "\\* " nil)
139 (goto-char (gnome-c-align--argument-type-end argument))))))
141 (defun gnome-c-align--parse-arglist (beg end)
144 (narrow-to-region beg end)
152 (goto-char (point-max))
154 (c-backward-syntactic-ws)
155 (setq identifier-end (point-marker))
156 ;; Array argument, such as 'int a[]'
157 (if (eq (preceding-char) ?\])
160 (setq identifier-start (point-marker))
161 (c-backward-syntactic-ws)
162 (if (or (bobp) (eq (preceding-char) ?,))
164 ;; Identifier is omitted, or '...'.
165 (setq type-start identifier-start
166 type-identifier-end identifier-end
167 type-end identifier-end
170 (c-backward-token-2))
171 (setq type-end (point-marker)
172 last-token-start type-end)
173 (while (and (not (bobp))
176 (unless (eq (char-after) ?,)
177 (setq last-token-start (point-marker)))))
178 (c-backward-syntactic-ws))
179 (setq type-start last-token-start)
182 (skip-chars-backward "* " type-start)
183 (c-backward-syntactic-ws)
184 (setq type-identifier-end (point-marker))))
185 (push (gnome-c-align--make-argument type-start
194 (defun gnome-c-align-arglist-at-point (&optional identifier-start-column)
195 "Reformat argument list at point, aligning argument to the right end."
198 (let* (start-column arglist)
199 (cl-destructuring-bind (beg end)
200 (gnome-c-align--arglist-region-at-point (point))
202 (setq start-column (current-column))
204 (narrow-to-region beg end)
205 (setq arglist (gnome-c-align--parse-arglist (point-min) (point-max)))
206 (gnome-c-align--normalize-arglist-region
207 arglist (point-min) (point-max))
208 (unless identifier-start-column
209 (setq identifier-start-column
210 (gnome-c-align--arglist-identifier-start-column arglist 0)))
211 (dolist (argument arglist)
212 (goto-char (gnome-c-align--argument-type-start argument))
213 (let ((column (if (bobp) 0 start-column)))
215 (gnome-c-align--indent-to-column start-column))
216 (when (gnome-c-align--argument-identifier-start argument)
217 (setq column (+ column identifier-start-column))
218 (goto-char (gnome-c-align--argument-identifier-start argument))
219 (gnome-c-align--indent-to-column column)))))))))
221 (cl-defstruct (gnome-c-align--decl
223 (:constructor gnome-c-align--make-decl (start
232 (start nil :read-only t)
233 (end nil :read-only t)
234 (identifier-start nil :read-only t)
235 (identifier-end nil :read-only t)
236 (arglist-start nil :read-only t)
237 (arglist-end nil :read-only t)
238 (arglist nil :read-only t))
240 (defun gnome-c-align--decls-identifier-start-column (decls start-column)
248 (gnome-c-align--marker-column
249 (gnome-c-align--decl-identifier-start decl)))))
250 (if (and gnome-c-align-max-column
251 (> decl-column gnome-c-align-max-column))
256 (defun gnome-c-align--decl-identifier-width (decl)
257 (- (gnome-c-align--marker-column
258 (gnome-c-align--decl-identifier-end decl))
259 (gnome-c-align--marker-column
260 (gnome-c-align--decl-identifier-start decl))))
262 (defun gnome-c-align--decls-arglist-start-column (decls start-column)
264 (+ (gnome-c-align--decls-arglist-identifier-start-column decls 0)
265 (gnome-c-align--decls-arglist-identifier-width decls)
274 (gnome-c-align--decl-identifier-width decl)
276 (if (and gnome-c-align-max-column
277 (> (+ decl-column arglist-width)
278 gnome-c-align-max-column))
283 (defun gnome-c-align--decls-arglist-identifier-width (decls)
284 (apply #'max 0 (mapcar (lambda (decl)
285 (gnome-c-align--arglist-identifier-width
286 (gnome-c-align--decl-arglist decl)))
289 (defun gnome-c-align--decls-arglist-identifier-start-column (decls start-column)
290 (apply #'max 0 (mapcar (lambda (decl)
291 ;; FIXME: should wrap lines inside argument list?
292 (gnome-c-align--arglist-identifier-start-column
293 (gnome-c-align--decl-arglist decl)
297 (defun gnome-c-align--parse-decl (beg end)
298 ;; Parse at most one func declaration found in BEG END.
301 (narrow-to-region beg end)
307 (goto-char (point-min))
308 (c-forward-syntactic-ws)
310 "typedef\\|#\\|G_\\(?:DECLARE\\|DEFINE\\)")
311 (while (and (not (eobp))
312 (not (eq (char-after) ?\()))
314 (c-forward-syntactic-ws))
315 ;; Identifier is vfunc.
316 (when (looking-at "(\\s-*\\*")
318 (c-forward-syntactic-ws)
320 (when (eq (char-after) ?\()
321 (setq arglist-start (point-marker))
322 (c-backward-syntactic-ws)
323 (setq identifier-end (point-marker))
326 (c-backward-token-2))
327 (setq identifier-start (point-marker))
328 (goto-char arglist-start)
330 (setq arglist-end (point-marker))
331 (gnome-c-align--make-decl beg end
332 identifier-start identifier-end
333 arglist-start arglist-end
334 (gnome-c-align--parse-arglist
336 (1- arglist-end)))))))))
338 (defun gnome-c-align--normalize-decl (decl)
340 ;; Replace newlines with a space
342 ;; Ignore lines before identifier-start
343 (goto-char (gnome-c-align--decl-identifier-start decl))
345 (narrow-to-region (point)
346 (gnome-c-align--decl-arglist-end decl))
347 (goto-char (point-min))
348 (while (re-search-forward "\n" nil t)
349 (replace-match " ")))
350 ;; Replace consequent spaces with a space
352 ;; Ignore lines before identifier-start
353 (goto-char (gnome-c-align--decl-identifier-start decl))
355 (narrow-to-region (point)
356 (gnome-c-align--decl-arglist-end decl))
357 (goto-char (point-min))
358 (while (re-search-forward "\\s-+" nil t)
359 (replace-match " ")))
360 (goto-char (gnome-c-align--decl-identifier-start decl))
361 (if (looking-back "\\* " nil)
363 ;; Normalize the argument list
364 (gnome-c-align--normalize-arglist-region
365 (gnome-c-align--decl-arglist decl)
366 (gnome-c-align--decl-arglist-start decl)
367 (gnome-c-align--decl-arglist-end decl))))
369 (defun gnome-c-align--arglist-region-at-point (point)
373 (c-beginning-of-statement-1)
374 (c-backward-syntactic-ws)
375 (unless (eq ?\( (preceding-char))
376 (error "No containing argument list"))
382 (error "No closing parenthesis")))
384 (list start (point)))))
387 (defun gnome-c-align-set-column (symbol)
388 "Set alignment column of SYMBOL."
390 (let ((symbol-name (completing-read "Symbol to change: "
393 "arglist-identifier-start")
395 (list (intern (format "gnome-c-align-%s-column" symbol-name)))))
396 (set symbol (current-column)))
398 (defun gnome-c-align--scan-decls (beg end)
401 (narrow-to-region beg end)
402 (goto-char (point-min))
405 (let (decl-start decl-end decl)
406 (c-forward-syntactic-ws)
407 (setq decl-start (point-marker))
409 (setq decl-end (point-marker))
410 (setq decl (gnome-c-align--parse-decl decl-start decl-end))
415 (defun gnome-c-align--guess-optimal-columns (beg end)
416 (let ((buffer (current-buffer))
419 (insert-buffer-substring-no-properties buffer beg end)
421 (setq decls (gnome-c-align--scan-decls (point-min) (point-max)))
422 (mapc #'gnome-c-align--normalize-decl decls)
423 (let* ((identifier-start-column
424 (gnome-c-align--decls-identifier-start-column
426 (arglist-start-column
427 (gnome-c-align--decls-arglist-start-column
428 decls identifier-start-column))
429 (arglist-identifier-start-column
430 (gnome-c-align--decls-arglist-identifier-start-column
431 decls (+ (length "(") arglist-start-column))))
432 (list (cons 'identifier-start-column
433 identifier-start-column)
434 (cons 'arglist-start-column
435 arglist-start-column)
436 (cons 'arglist-identifier-start-column
437 arglist-identifier-start-column))))))
440 (defun gnome-c-align-guess-optimal-columns (beg end)
441 "Compute the optimal alignment rule from the declarations in BEG and END.
443 This sets `gnome-c-align-identifier-start-column',
444 `gnome-c-align-arglist-start-column', and
445 `gnome-c-align-arglist-identifier-start-column'."
447 (let ((columns (gnome-c-align--guess-optimal-columns beg end)))
448 (setq gnome-c-align-identifier-start-column
449 (cdr (assq 'identifier-start-column columns))
450 gnome-c-align-arglist-start-column
451 (cdr (assq 'arglist-start-column columns))
452 gnome-c-align-arglist-identifier-start-column
453 (cdr (assq 'arglist-identifier-start-column columns)))
455 "identifier-start: %d, arglist-start: %d, arglist-identifier-start: %d"
456 gnome-c-align-identifier-start-column
457 gnome-c-align-arglist-start-column
458 gnome-c-align-arglist-identifier-start-column)))
461 (defun gnome-c-align-guess-columns (beg end)
462 "Guess the existing alignment rule from the declarations in BEG and END.
464 This sets `gnome-c-align-identifier-start-column',
465 `gnome-c-align-arglist-start-column', and
466 `gnome-c-align-arglist-identifier-start-column'."
468 (let ((decls (gnome-c-align--scan-decls beg end))
471 (error "No function declaration in the region"))
472 (setq arglist (gnome-c-align--parse-arglist
473 (1+ (gnome-c-align--decl-arglist-start (car decls)))
474 (1- (gnome-c-align--decl-arglist-end (car decls)))))
476 (error "Empty argument list"))
477 (unless (gnome-c-align--argument-identifier-start (car arglist))
478 (error "No identifier in the argument list"))
479 (setq gnome-c-align-identifier-start-column
480 (gnome-c-align--marker-column
481 (gnome-c-align--decl-identifier-start (car decls)))
482 gnome-c-align-arglist-start-column
483 (gnome-c-align--marker-column
484 (gnome-c-align--decl-arglist-start (car decls)))
485 gnome-c-align-arglist-identifier-start-column
486 (gnome-c-align--marker-column
487 (gnome-c-align--argument-identifier-start (car arglist))))
489 "identifier-start: %d, arglist-start: %d, arglist-identifier-start: %d"
490 gnome-c-align-identifier-start-column
491 gnome-c-align-arglist-start-column
492 gnome-c-align-arglist-identifier-start-column)))
495 (defun gnome-c-align-decls-region (beg end)
496 "Reformat function declarations in the region between BEG and END."
501 (narrow-to-region beg end)
502 (unless (and gnome-c-align-identifier-start-column
503 gnome-c-align-arglist-start-column
504 gnome-c-align-arglist-identifier-start-column)
505 (let ((columns (gnome-c-align--guess-optimal-columns beg end)))
506 (unless gnome-c-align-identifier-start-column
507 (setq gnome-c-align-identifier-start-column
508 (cdr (assq 'identifier-start-column columns))))
509 (unless gnome-c-align-arglist-start-column
510 (setq gnome-c-align-arglist-start-column
511 (cdr (assq 'arglist-start-column columns))))
512 (unless gnome-c-align-arglist-identifier-start-column
513 (setq gnome-c-align-arglist-identifier-start-column
514 (cdr (assq 'arglist-identifier-start-column columns))))))
515 (setq decls (gnome-c-align--scan-decls beg end))
516 (mapc #'gnome-c-align--normalize-decl decls)
518 (goto-char (gnome-c-align--decl-identifier-start decl))
519 (gnome-c-align--indent-to-column
520 gnome-c-align-identifier-start-column)
521 (goto-char (gnome-c-align--decl-identifier-end decl))
522 (when (>= (current-column) gnome-c-align-arglist-start-column)
524 (goto-char (gnome-c-align--decl-arglist-start decl))
525 (gnome-c-align--indent-to-column
526 gnome-c-align-arglist-start-column)
528 (gnome-c-align-arglist-at-point
529 (- (- gnome-c-align-arglist-identifier-start-column
531 gnome-c-align-arglist-start-column)))))))
533 (provide 'gnome-c-align)
535 ;;; gnome-c-align.el ends here