I find myself using M-x align a lot in Emacs to line up the identifiers in blocks of C declarations or statements like the following:

struct foo {
   something x;
   int hello;
   long *blodgett;
};
 
int *q;
bazbazbaz *y;
static char fnork;
 
spoonful = sugar;
spork = 1;

After marking the region containing the declarations a quick M-x align transforms it into:

struct foo {
   something  x;
   int        hello;
   long      *blodgett;
};
 
int         *q;
bazbazbaz   *y;
static char  fnork;
 
spoonful = sugar;
spork    = 1;

Which looks much neater to my eyes. However when the declarations have initialisers the result is not at all pleasing:

int x = 5;
struct baz baz = { 1, 2 };
const int *this_is_a_pointer = NULL;

Becomes

int         x                 = 5;
struct baz  baz               = { 1, 2 };
const int  *this_is_a_pointer = NULL;

Really I only want it to align the identifiers and leave the = where they were. I’m sure I can’t be the only one with this problem but I’ve no idea what to type into Google or Stack Overflow to find the solution, and the manual doesn’t help either.

My current solution is to patch the list of alignment rules in align-rules-list with this bit of hackery:

;;; Define this in a file with `lexical-binding' set or else change the
;;; `let' below to `lexical-let'.
(defun nick-fix-c-align ()
  (let ((var-decl-regexp (alist-get 'regexp
                                    (alist-get 'c-variable-declaration
                                               align-rules-list))))
    (push `(valid . ,(lambda ()
                       (not (save-excursion
                              (end-of-line)
                              (looking-back var-decl-regexp)))))
          (alist-get 'c-assignment align-rules-list))))

The variable align-rules-list holds a list of rules for patterns like “C assignment”, “C variable declaration”, and so on. Each rule is an alist with a regex trigger, a list of modes to enable it in, an optional predicate valid to further restrict when it is run, and some other irrelevant options. The align function loops over this list and aligns any text where the regex matches and valid returns true.

The problem is C declarations with intialisers trigger both the c-variable-declaration and the c-assignment rules. There’s no way to tell it “stop processing rules if this matches” so the function above modifies the rules list to add an extra predicate to the c-assignment rule which says “do not apply this rule when the line also matches the regex for C variable declarations”.

align-load-hook is called after align.el has been loaded, which is a convienent time to patch the rules list. Afterwards M-x align lines up only the identifiers in declarations:

int         x = 5;
struct baz  baz = { 1, 2 };
const int  *this_is_a_pointer = NULL;