1 ;; gnome-align.el --- GNOME-style code alignment -*- lexical-binding: t; -*-
2 ;; Copyright (C) 2016 Daiki Ueno <ueno@gnu.org>
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 (defgroup gnome-minor-mode nil
29 "GNOME-style C source code editing"
33 (defcustom gnome-align-max-column 80
34 "Maximum number of columns per line."
35 :type '(choice (integer :tag "Columns")
36 (const :tag "No wrap"))
37 :group 'gnome-minor-mode)
39 (defvar gnome-align-identifier-start-column nil)
40 (make-variable-buffer-local 'gnome-align-identifier-start-column)
42 (defvar gnome-align-arglist-start-column nil)
43 (make-variable-buffer-local 'gnome-align-arglist-start-column)
45 (defvar gnome-align-arglist-identifier-start-column nil)
46 (make-variable-buffer-local 'gnome-align-arglist-identifier-start-column)
48 (cl-defstruct (gnome-align--argument
50 (:constructor gnome-align--make-argument (type-start
56 (type-start nil :read-only t)
57 (type-end nil :read-only t)
58 (identifier-start nil :read-only t)
59 (identifier-end nil :read-only t))
61 (defun gnome-align--marker-column (marker)
66 (defun gnome-align--indent-to-column (column)
67 ;; Prefer 'char **foo' than 'char ** foo'
68 (when (looking-back "\*+" nil t)
69 (setq column (- column (- (match-end 0) (match-beginning 0))))
70 (goto-char (match-beginning 0)))
71 ;; FIXME: should respect indent-tabs-mode?
72 (let (indent-tabs-mode)
73 (indent-to-column column)))
75 (defun gnome-align--argument-type-width (arg)
76 (- (gnome-align--marker-column (gnome-align--argument-type-end arg))
77 (gnome-align--marker-column (gnome-align--argument-type-start arg))))
79 (defun gnome-align--arglist-identifier-start-column (arglist start-column)
80 (let ((column start-column)
82 (dolist (argument arglist)
83 (setq argument-column (+ start-column
84 (gnome-align--argument-type-width argument)))
85 (when (gnome-align--argument-identifier-start argument)
87 (goto-char (gnome-align--argument-identifier-start argument))
88 (when (eq (preceding-char) ? )
89 (setq argument-column (1+ argument-column)))))
90 (when (> argument-column column)
91 (setq column argument-column)))
94 (defun gnome-align--argument-identifier-width (argument)
95 (if (gnome-align--argument-identifier-start argument)
96 (- (gnome-align--marker-column
97 (gnome-align--argument-identifier-end argument))
98 (gnome-align--marker-column
99 (gnome-align--argument-identifier-start argument)))
102 (defun gnome-align--arglist-identifier-width (arglist)
105 (dolist (argument arglist)
106 (setq argument-width (gnome-align--argument-identifier-width argument))
107 (when (> argument-width width)
108 (setq width argument-width)))
111 (defun gnome-align--normalize-arglist-region (beg end)
114 (narrow-to-region beg end)
115 (goto-char (point-min))
116 (while (re-search-forward "\\s-+" nil t)
118 (goto-char (point-min))
119 (while (re-search-forward "\\s-*," nil t)
120 (replace-match ",\n"))
121 (goto-char (point-min))
122 (delete-trailing-whitespace)
123 ;; Remove whitespace at the beginning of line
124 (goto-char (point-min))
125 (while (re-search-forward "^\\s-+" nil t)
127 ;; Remove empty lines
128 (goto-char (point-min))
129 (delete-matching-lines "^$"))))
131 (defun gnome-align--parse-arglist (beg end)
134 (narrow-to-region beg end)
141 (goto-char (point-max))
143 (c-backward-syntactic-ws)
144 (setq identifier-end (point-marker))
145 ;; Array argument, such as 'int a[]'
146 (if (eq (preceding-char) ?\])
149 (setq identifier-start (point-marker))
150 (c-backward-syntactic-ws)
151 (if (or (bobp) (eq (preceding-char) ?,))
152 ;; Identifier is omitted, or '...'.
153 (setq type-start identifier-start
154 type-end identifier-end
157 (setq type-end (point-marker)
158 last-token-start type-end)
159 (while (and (not (bobp))
162 (unless (eq (char-after) ?,)
163 (setq last-token-start (point-marker)))))
164 (c-backward-syntactic-ws))
165 (setq type-start last-token-start))
166 (push (gnome-align--make-argument type-start type-end
167 identifier-start identifier-end)
172 (defun gnome-align-at-point (&optional identifier-start-column)
173 "Reformat argument list at point, aligning argument to the right end."
176 (let* (start-column arglist)
177 (cl-destructuring-bind (beg end)
178 (gnome-align--arglist-region-at-point (point))
180 (setq start-column (current-column))
182 (narrow-to-region beg end)
183 (setq arglist (gnome-align--parse-arglist (point-min) (point-max)))
184 (gnome-align--normalize-arglist-region (point-min) (point-max))
185 (unless identifier-start-column
186 (setq identifier-start-column
187 (gnome-align--arglist-identifier-start-column arglist 0)))
188 (dolist (argument arglist)
189 (goto-char (gnome-align--argument-type-start argument))
190 (let ((column (if (bobp) 0 start-column)))
192 (gnome-align--indent-to-column start-column))
193 (when (gnome-align--argument-identifier-start argument)
194 (setq column (+ column identifier-start-column))
195 (goto-char (gnome-align--argument-identifier-start argument))
196 (gnome-align--indent-to-column column)))))))))
198 (cl-defstruct (gnome-align--decl
200 (:constructor gnome-align--make-decl (start
209 (start nil :read-only t)
210 (end nil :read-only t)
211 (identifier-start nil :read-only t)
212 (identifier-end nil :read-only t)
213 (arglist-start nil :read-only t)
214 (arglist-end nil :read-only t)
215 (arglist nil :read-only t))
217 (defun gnome-align--decls-identifier-start-column (decls start-column)
218 (let ((column start-column)
221 (setq decl-column (+ start-column
222 (gnome-align--marker-column
223 (gnome-align--decl-identifier-start decl))))
224 (when (and (or (null gnome-align-max-column)
225 (<= decl-column gnome-align-max-column))
226 (> decl-column column))
227 (setq column decl-column)))
230 (defun gnome-align--decl-identifier-width (decl)
231 (- (gnome-align--marker-column
232 (gnome-align--decl-identifier-end decl))
233 (gnome-align--marker-column
234 (gnome-align--decl-identifier-start decl))))
236 (defun gnome-align--decls-arglist-start-column (decls start-column)
237 (let ((column start-column)
240 (+ (gnome-align--decls-arglist-identifier-start-column decls 0)
241 (gnome-align--decls-arglist-identifier-width decls)
244 (setq decl-column (+ start-column
245 (gnome-align--decl-identifier-width decl)))
246 (when (and (or (null gnome-align-max-column)
247 (<= (+ decl-column arglist-width)
248 gnome-align-max-column))
249 (> decl-column column))
250 (setq column decl-column)))
253 (defun gnome-align--decls-arglist-identifier-width (decls)
257 (setq decl-width (gnome-align--arglist-identifier-width
258 (gnome-align--decl-arglist decl)))
259 (when (> decl-width width)
260 (setq width decl-width)))
263 (defun gnome-align--decls-arglist-identifier-start-column (decls start-column)
264 (let ((column start-column)
267 (setq decl-column (gnome-align--arglist-identifier-start-column
268 (gnome-align--decl-arglist decl)
270 ;; FIXME: should wrap lines inside argument list?
271 (when (> decl-column column)
272 (setq column decl-column)))
275 (defun gnome-align--parse-decl (beg end)
276 ;; Parse at most one func declaration found in BEG END.
279 (narrow-to-region beg end)
285 (goto-char (point-min))
286 (c-forward-syntactic-ws)
288 "typedef\\|#\\|G_\\(?:DECLARE\\|DEFINE\\)")
289 (while (and (not (eobp))
290 (not (eq (char-after) ?\()))
292 (c-forward-syntactic-ws))
293 ;; Identifier is vfunc.
294 (when (looking-at "(\\s-*\\*")
296 (c-forward-syntactic-ws)
298 (when (eq (char-after) ?\()
299 (setq arglist-start (point-marker))
300 (c-backward-syntactic-ws)
301 (setq identifier-end (point-marker))
304 (c-backward-token-2))
305 (setq identifier-start (point-marker))
306 (goto-char arglist-start)
308 (setq arglist-end (point-marker))
309 (gnome-align--make-decl beg end
310 identifier-start identifier-end
311 arglist-start arglist-end
312 (gnome-align--parse-arglist
314 (1- arglist-end)))))))))
316 (defun gnome-align--normalize-decl (decl)
319 (narrow-to-region (gnome-align--decl-identifier-start decl)
320 (gnome-align--decl-arglist-end decl))
321 (goto-char (point-min))
322 (while (re-search-forward "\n" nil t)
323 (replace-match " ")))
325 (narrow-to-region (gnome-align--decl-start decl)
326 (gnome-align--decl-end decl))
327 (goto-char (point-min))
328 (while (re-search-forward "\\s-+" nil t)
329 (replace-match " ")))))
331 (defun gnome-align--arglist-region-at-point (point)
335 (c-beginning-of-statement-1)
336 (c-backward-syntactic-ws)
337 (unless (eq ?\( (preceding-char))
338 (error "No containing argument list"))
344 (error "No closing parenthesis")))
346 (list start (point)))))
349 (defun gnome-align-set-column (symbol)
350 "Set alignment column of SYMBOL."
352 (let ((symbol-name (completing-read "Symbol to change: "
355 "arglist-identifier-start")
357 (list (intern (format "gnome-align-%s-column" symbol-name)))))
358 (set symbol (current-column)))
360 (defun gnome-align--scan-decls (beg end)
363 (narrow-to-region beg end)
364 (goto-char (point-min))
367 (let (decl-start decl-end decl)
368 (c-forward-syntactic-ws)
369 (setq decl-start (point-marker))
371 (setq decl-end (point-marker))
372 (setq decl (gnome-align--parse-decl decl-start decl-end))
377 (defun gnome-align--compute-optimal-columns (beg end)
378 (let ((buffer (current-buffer))
381 (insert-buffer-substring-no-properties buffer beg end)
383 (setq decls (gnome-align--scan-decls (point-min) (point-max)))
384 (mapc #'gnome-align--normalize-decl decls)
385 (let* ((identifier-start-column
386 (gnome-align--decls-identifier-start-column
388 (arglist-start-column
389 (gnome-align--decls-arglist-start-column
390 decls identifier-start-column))
391 (arglist-identifier-start-column
392 (gnome-align--decls-arglist-identifier-start-column
393 decls (+ (length "(") arglist-start-column))))
394 (list (cons 'identifier-start-column
395 identifier-start-column)
396 (cons 'arglist-start-column
397 arglist-start-column)
398 (cons 'arglist-identifier-start-column
399 arglist-identifier-start-column))))))
402 (defun gnome-align-compute-optimal-columns (beg end)
403 "Compute the optimal alignment rule from the declarations in BEG and END.
405 This sets `gnome-align-identifier-start-column',
406 `gnome-align-arglist-start-column', and
407 `gnome-align-arglist-identifier-start-column'."
409 (let ((columns (gnome-align--compute-optimal-columns beg end)))
410 (setq gnome-align-identifier-start-column
411 (cdr (assq 'identifier-start-column columns))
412 gnome-align-arglist-start-column
413 (cdr (assq 'arglist-start-column columns))
414 gnome-align-arglist-identifier-start-column
415 (cdr (assq 'arglist-identifier-start-column columns)))
417 "identifier-start: %d, arglist-start: %d, arglist-identifier-start: %d"
418 gnome-align-identifier-start-column
419 gnome-align-arglist-start-column
420 gnome-align-arglist-identifier-start-column)))
423 (defun gnome-align-guess-columns (beg end)
424 "Guess the existing alignment rule from the declarations in BEG and END.
426 This sets `gnome-align-identifier-start-column',
427 `gnome-align-arglist-start-column', and
428 `gnome-align-arglist-identifier-start-column'."
430 (let ((decls (gnome-align--scan-decls beg end))
433 (error "No function declaration in the region"))
434 (setq arglist (gnome-align--parse-arglist
435 (1+ (gnome-align--decl-arglist-start (car decls)))
436 (1- (gnome-align--decl-arglist-end (car decls)))))
438 (error "Empty argument list"))
439 (unless (gnome-align--argument-identifier-start (car arglist))
440 (error "No identifier in the argument list"))
441 (setq gnome-align-identifier-start-column
442 (gnome-align--marker-column
443 (gnome-align--decl-identifier-start (car decls)))
444 gnome-align-arglist-start-column
445 (gnome-align--marker-column
446 (gnome-align--decl-arglist-start (car decls)))
447 gnome-align-arglist-identifier-start-column
448 (gnome-align--marker-column
449 (gnome-align--argument-identifier-start (car arglist))))
451 "identifier-start: %d, arglist-start: %d, arglist-identifier-start: %d"
452 gnome-align-identifier-start-column
453 gnome-align-arglist-start-column
454 gnome-align-arglist-identifier-start-column)))
457 (defun gnome-align-region (beg end)
458 "Reformat function declarations in the region between BEG and END."
463 (narrow-to-region beg end)
464 (unless (and gnome-align-identifier-start-column
465 gnome-align-arglist-start-column
466 gnome-align-arglist-identifier-start-column)
467 (let ((columns (gnome-align--compute-optimal-columns beg end)))
468 (unless gnome-align-identifier-start-column
469 (setq gnome-align-identifier-start-column
470 (cdr (assq 'identifier-start-column columns))))
471 (unless gnome-align-arglist-start-column
472 (setq gnome-align-arglist-start-column
473 (cdr (assq 'arglist-start-column columns))))
474 (unless gnome-align-arglist-identifier-start-column
475 (setq gnome-align-arglist-identifier-start-column
476 (cdr (assq 'arglist-identifier-start-column columns))))))
477 (setq decls (gnome-align--scan-decls beg end))
478 (mapc #'gnome-align--normalize-decl decls)
480 (goto-char (gnome-align--decl-identifier-start decl))
481 (gnome-align--indent-to-column
482 gnome-align-identifier-start-column)
483 (goto-char (gnome-align--decl-identifier-end decl))
484 (when (>= (current-column) gnome-align-arglist-start-column)
486 (goto-char (gnome-align--decl-arglist-start decl))
487 (gnome-align--indent-to-column
488 gnome-align-arglist-start-column)
490 (gnome-align-at-point
491 (- (- gnome-align-arglist-identifier-start-column
493 gnome-align-arglist-start-column)))))))
495 (provide 'gnome-align)
497 ;;; gnome-align.el ends here