0

When I am writing in c-mode, I prefer to write multi-line control statements in the following way.

if( cond1
 && cond2
 || cond3
) {
    /* stuff */
}

for( i = 0;
     i < N;
     ++i
) {
    /* stuff */
}

while( cond1
    && cond2
    || cond3
) {
    /* stuff */
}

However, emacs does not make this easy.

  • Adding a new condition line re-indents the ) { immediately following the conditions.

  • Within the ( ) of these control statements the && and || automatically re-indent whenever parenthesis are entered.

  • Each newline within the control structure's () indents to the normal indent.

What can I do to make writing these easier?

P.S. Creating a yasnippet template does not work. The ) { automatically indents on completion.

  • The ) {-part is quite easy: (add-hook 'c-mode-hook (lambda () (c-set-offset 'arglist-close 0))). – Tobias Feb 22 '18 at 1:07
0

Your problem can be solved by configuring the variable c-syntactic-indentation. This variable is by default non-nil, and the indentation of code is as usual. You need to set it nil, and in this case every line is just indented to the same level as the previous one, if you press RET. If pressing C-j, however, it will move the point to the beginning of the next line.

This can be accomplished in two ways:

  • use the code below to set it always for your .c files:

    (require 'cc-mode) (add-to-list 'c-mode-common-hook (lambda () (setq c-syntactic-indentation nil)))

  • you can toggle this variable with: M-x c-t-i RET

Tested with Emacs 25.3, the above function written in a file and Emacs started with: emacs -q -l testfile.el

  • yes I was able to read this far in the documentation as well, but I'm looking more for a way for the indentation to align like I have above once you hit enter. Using a custom line-up function (see here)[gnu.org/software/emacs/manual/html_node/ccmode/… – skyfire Feb 21 '18 at 23:55
0
+100

The Section "Customizing Indentation" of the cc-mode manual describes how to configure the indentation of c-mode. If cc-mode is properly installed on your system you can also reach the info version of that manual by M-: (info "(ccmode)Customizing Indentation").

There are several ways to adapt the indentation of c-mode to your likings:

  1. Modify c-offsets-alist with the help of the command c-set-offset in c-mode-hook. That approach is the simpler one and I will pursue that one below.
  2. Add your own style in c-style-alist and select it for c-mode in c-default-style. That one is more general. It allows you to switch your style on and off in one c-file.

The indentation rules are applied line-wise. The parser of c-mode assigns a syntactic symbol to each line. That symbol describes the context of the line. Syntactic symbols are associated with indentation offsets in c-offsets-alist and c-style-alist.

Use the command c-show-syntactic-information to determine what syntactic symbol is assigned to a line in a certain context. This command is bound to the key sequence C-c C-s in c-mode.

The closing parenthesis of a control statement gets the syntactic symbol arglist-close.

Your specification for the alignment of ) { with respect to control statements can be set by assigning the offset 0 to that symbol, i.e. by (c-set-offset 'arglist-close 0). The relative indentation offset is zero and therefore the closing parenthesis is aligned to the keyword of the control statement or function.

The parser assigns the syntactic symbol arglist-cont-nonempty to lines that start with || or && within function or control operator arguments. The factory default indentation style gnu associates the lineup function c-lineup-arglist to that syntactic symbol.

The following elisp code sets c-mode up with the syntactic indentation as you specified it.

There we replace that default associated offset of arglist-cont-nonempty with a new function c-lineup-arglist-log-ops that treats lines starting with || or && specially. It calculates the relative offset of the first argument in the argument list w.r.t. the anchor position and returns that offset reduced by length of the prefix composed of the logical operator and the following white space as new offset.

If the current line does not start with a logical operator c-lineup-arglist-log-ops falls back to c-lineup-arglist.

(defun c-col-after-pos (pos &optional avoid)
  "Return start column of next relevant thing after POS.
If AVOID is non-nil it should be the beginning position
of a line to be avoided. If the next relevant thing is
on that line just return nil."
  (save-excursion
    (goto-char (min (point-max) (1+ pos)))
    (c-forward-syntactic-ws)
    (if (null (eq (line-beginning-position) avoid))
    (current-column))))

(defun c-anchor-column (langelem)
  "Return column of anchor of LANGELEM."
  (save-excursion
    (goto-char (c-langelem-pos langelem))
    (current-column)))

(defun c-lineup-arglist-log-ops (langelem)
  "At lines with && or || line up logical expressions behind that operators.
Line up according to `c-lineup-arglist' otherwise."
  (save-excursion
    (forward-line 0)
    (let (paren-pos
      length-log-op
      (line-beg (point)))
      (or
       (and (eq (c-langelem-sym langelem) 'arglist-cont-nonempty)
        (looking-at "^\\s-*\\(\\(?:&&\\|||\\)\\s-*\\)")
        (setq length-log-op (length (match-string 1)))
        (setq paren-pos (c-langelem-2nd-pos c-syntactic-element))
        (setq col (c-col-after-pos paren-pos line-beg))
        (- col length-log-op (c-anchor-column langelem)))
       #'c-lineup-arglist))))

(defun my-c-offset-setup ()
  (c-set-offset 'arglist-close 0)
  (c-set-offset 'arglist-cont-nonempty #'c-lineup-arglist-log-ops))

(add-hook 'c-mode-hook #'my-c-offset-setup)

Extended test with:

  • emacs-version 25.1.50.2
  • test file "/tmp/test.c" with the contents listed below; The listed code is already the result of indentation.
  • Result of C-h v c-indentation-style in the file buffer of "/tmp/test.c" is "gnu" (corresponding to the entry (other . "gnu") in c-default-style)
  • indentation generated with C-x h tab; (That does not change anymore the indentation of the listed code because it is already indented.)
#include <stdio.h>

int somefun() {
  return 1;
}

int main() {
  size_t i,N;
  int x,y,y1,z,w,u;

  for( i = 0;
       i < N;
       ++i
  ) {
    /* stuff */
  }

  if ( x==1
    && ( y1==2
      && z==3
      || w!=4 )
    || z==3
  ) {
    /* some comment
       multi-line
       comment
    */
    x += y;
    x += y;
  } else if ( x==2
           && u!=3
           && ( y1==3
             && z==4
             || w!=5 )
           || z==6
  ) {
    y += x;
  }

  while(
        somefun(x)
  ) {
    /* stuff */
  }

  return 0;
}

Some notes about the test result:

  • The indentation of else if does work exactly as that one of if. (That was under discussion in the comments.)
  • The indentation of grouped logical expressions is exactly as specified (That was also under discussion in the comments.):
    • The group itself is a logical expression so its opening parenthesis is aligned to the previous logical expression.
    • The logical expressions within the group combined by && and || are also aligned according to the specification.
  • tested this a little bit in spacemacs. Was not having great success :/ this may or may not be due to something else in spacemacs. Won't be able to fully test until the weekend. Some things worked but for example else if would not, and upon insertion of () to group logical conditions, regular indenting came back. I'll see what I can come up with over the weekend but if you have any ideas, please let me know. – skyfire Feb 22 '18 at 21:39
  • Ok I tested a bit more and it seems to mostly work. But I could not get the line else if( (ch == ' ' && position != 0) || (position >= length) ) to type out easily without backtracking to align it manually. The && kept wanting to align with the ==. Ideally I want to hit RET once inside the if( cond and have the cursor start underneath the f. If that is not possible, maybe we can trigger a reindent upon entering && or || on lines following the initial if( line --similiar to how typing () triggers a reindent? – skyfire Feb 24 '18 at 14:43
  • @skyfire Thanks for the test. If you want to have additional features please complete the specification in the top question text. The wanted indentation is lost in the comment. Please clearly mark the specification add-ons as such in the question text. Actually, the answer already answers what was demanded by the original specification. I will see what I can do but answering a question shouldn't be an engineering job with full development cycle;-). From your comment below Ian's answer I had the impression that you would be fit for the job yourself;-)). – Tobias Feb 24 '18 at 15:26
  • much appreciated, your right I am a bit at fault for not giving a more complete description -- sorry about that :( anything you do beyond what was originally asked is extra, I'm giving you the bounty. But I will still appreciate any further help, whether that is the extra bit I asked or any relevant info you know that could help me program that last bit. Thank you! – skyfire Feb 24 '18 at 16:00
  • @skyfire I referenced the info node ccmode and added a description of the code. I know that you don't need that. But the questions and answers are supposed to be of use for other less experienced emacs users. – Tobias Feb 25 '18 at 1:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.