Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ Templates and Emacs: Customizing Indentation

Tags:

c++

emacs

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.

like image 703
bstamour Avatar asked Oct 20 '11 02:10

bstamour


2 Answers

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.

like image 115
m-renaud Avatar answered Oct 11 '22 06:10

m-renaud


Comments

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.

Usage

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)

Code

(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)))))
like image 22
assem Avatar answered Oct 11 '22 06:10

assem