As far as I know in emacs, there is no way of customizing the indentation level of the closing '>' character of a template list in C++. Currently my emacs indentation scheme does this:
template <
typename T1,
typename T2,
typename T3
>
class X;
What I want is something like this:
template <
typename T1,
typename T2,
typename T3
>
class X;
Setting the indent variable template-args-cont to zero will indent the '>' character properly, but at the cost of unindenting the actual body of the template argument list.
Any suggestions from the emacs gurus out there?
EDIT:
I got it somewhat working with the following hack:
(defun indent-templates (elem)
(c-langelem-col elem t)
(let ((current-line
(buffer-substring-no-properties
(point-at-bol) (point-at-eol))))
(if (string-match-p "^\\s-*>" current-line)
0
'+)))
And then setting template-args-cont to indent-templates in my custom theme, ala:
(c-add-style "my-style"
'("stroustrup"
;; ... Other stuff ...
(template-args-cont . indent-templates))))
But it's still pretty buggy. It works most of the time, but sometimes emacs gets confused at thinks a template list is an arglist, and then hilarity ensues.
The best solution that I have found is writing a custom (and relatively straightforward) indentation function.
The Code
(defun c++-template-args-cont (langelem)
"Control indentation of template parameters handling the special case of '>'.
Possible Values:
0 : The first non-ws character is '>'. Line it up under 'template'.
nil : Otherwise, return nil and run next lineup function."
(save-excursion
(beginning-of-line)
(if (re-search-forward "^[\t ]*>" (line-end-position) t)
0)))
(add-hook 'c++-mode-hook
(lambda ()
(c-set-offset 'template-args-cont
'(c++-template-args-cont c-lineup-template-args +))))
This handles all of the cases that I have come across even with templates nested several levels deep.
How It Works
For indenting code, if a list of indentation functions is provided, then Emacs will try them in order and if the one currently being executed returns nil
, it will invoke the next one. What I have done is added a new indentation function to the beginning of the list that detects whether the first non-whitespace character on the line is '>', and if it is, set the indentation to position 0 (which will line it up with the opening template). This also covers the case where you have template-template parameters as follows:
template <
template <
typename T,
typename U,
typename... Args
> class... CS
>
because it doesn't care what's after the '>'. So as a result of how the list of indentation functions works, if '>' is not the first character, the function returns nil
and the usual indentation function gets invoked.
I think part of the problem you experience is that when you instantiate templates, emacs CC mode views it with the same template-args-cont
structure. So, taking this into account, I expanded on your original idea and tried to make it suit my liking; I made the code verbose so that hopefully everyone can understand my intention. :) This should not cause problems when you instantiate, and it also appears to work for template template parameters! Try this out until someone with more Elisp skills can provide a better solution!
If you experience any 'fighting' (i.e. alternating or broken indentation), try reloading the cpp file C-xC-vEnter and indenting again. Sometimes with template template parameters emacs shows the inner arguments as arglist-cont-nonempty
and even alternates back and forth with template-args-const
, but the reload always restored state.
To do what you want try this out by using the code below and adding to your c-offsets-alist
an entry:
(template-args-cont . brian-c-lineup-template-args)
and set the variable
(setq brian-c-lineup-template-closebracket t)
I actually prefer a slightly different alignment:
(setq brian-c-lineup-template-closebracket 'under)
(defvar brian-c-lineup-template-closebracket 'under
"Control the indentation of the closing template bracket, >.
Possible values and consequences:
'under : Align directly under (same column) the opening bracket.
t : Align at the beginning of the line (or current indentation level.
nil : Align at the same column of previous types (e.g. col of class T).")
(defun brian-c-lineup-template--closebracket-p ()
"Return t if the line contains only a template close bracket, >."
(save-excursion
(beginning-of-line)
;; Check if this line is empty except for the trailing bracket, >
(looking-at (rx (zero-or-more blank)
">"
(zero-or-more blank)))))
(defun brian-c-lineup-template--pos-to-col (pos)
(save-excursion
(goto-char pos)
(current-column)))
(defun brian-c-lineup-template--calc-open-bracket-pos (langelem)
"Calculate the position of a template declaration opening bracket via LANGELEM."
(save-excursion
(c-with-syntax-table c++-template-syntax-table
(goto-char (c-langelem-pos langelem))
(1- (re-search-forward "<" (point-max) 'move)))))
(defun brian-c-lineup-template--calc-indent-offset (ob-pos)
"Calculate the indentation offset for lining up types given the opening
bracket position, OB-POS."
(save-excursion
(c-with-syntax-table c++-template-syntax-table
;; Move past the opening bracket, and check for types (basically not space)
;; if types are on the same line, use their starting column for indentation.
(goto-char (1+ ob-pos))
(cond ((re-search-forward (rx
(or "class"
"typename"
(one-or-more (not blank))))
(c-point 'eol)
'move)
(goto-char (match-beginning 0))
(current-column))
(t
(back-to-indentation)
(+ c-basic-offset (current-column)))))))
(defun brian-c-lineup-template-args (langelem)
"Align template arguments and the closing bracket in a semi-custom manner."
(let* ((ob-pos (brian-c-lineup-template--calc-open-bracket-pos langelem))
(ob-col (brian-c-lineup-template--pos-to-col ob-pos))
(offset (brian-c-lineup-template--calc-indent-offset ob-pos)))
;; Optional check for a line consisting of only a closebracket and
;; line it up either at the start of indentation, or underneath the
;; column of the opening bracket
(cond ((and brian-c-lineup-template-closebracket
(brian-c-lineup-template--closebracket-p))
(cond ((eq brian-c-lineup-template-closebracket 'under)
(vector ob-col))
(t
0)))
(t
(vector offset)))))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With