emacsalignmentelispcc-mode

Adjusting Alignment Rules for UCFS-Chains in D


Is there a way to tweak the alignment rules in c-derived modes, in my case d-mode to indent D-style UFCS-Chains such as

    foreach (file; dirPath.expandTilde()
                          .buildNormalizedPath() 
                          .dirEntries(SpanMode.shallow)()

In this case I would like to align on the point, that is.

For details see https://github.com/Emacs-D-Mode-Maintainers/Emacs-D-Mode/issues/26


Solution

  • You have to modify the value of arglist-cont-nonempty key in your c-offsets-alist. Also, you will probably want to modify the statement-cont key to enable the same indentation in general statements (e.g. assignments) as well:

    (add-to-list 'c-offsets-alist '(arglist-cont-nonempty . c-lineup-cascaded-calls))
    (add-to-list 'c-offsets-alist '(statement-cont . c-lineup-cascaded-calls))
    

    Obviously, you can use something like:

    (add-hook 'd-mode-hook
                     '(lambda ()
                        (add-to-list 'c-offsets-alist '(arglist-cont-nonempty . c-lineup-cascaded-calls))
                        (add-to-list 'c-offsets-alist '(statement-cont . c-lineup-cascaded-calls))))
    

    to enable this alignment in every d-mode buffer.

    If you want to account for optional parenthesis, I believe you'll have to write your own "lining-up" function, since I can't think of a built-in solution. Here's a dirty rewrite of c-lineup-cascaded-calls:

    (defun d-lineup-cascaded-calls (langelem)
      "This is a modified `c-lineup-cascaded-calls' function for the
    D programming language which accounts for optional parenthesis
    and compile-time parameters in function calls."
    
      (if (and (eq (c-langelem-sym langelem) 'arglist-cont-nonempty)
               (not (eq (c-langelem-2nd-pos c-syntactic-element)
                        (c-most-enclosing-brace (c-parse-state)))))
          ;; The innermost open paren is not our one, so don't do
          ;; anything.  This can occur for arglist-cont-nonempty with
          ;; nested arglist starts on the same line.
          nil
    
        (save-excursion
          (back-to-indentation)
          (let ((operator (and (looking-at "\\.")
                               (regexp-quote (match-string 0))))
                (stmt-start (c-langelem-pos langelem)) col)
    
            (when (and operator
                       (looking-at operator)
                       (or (and
                            (zerop (c-backward-token-2 1 t stmt-start))
                            (eq (char-after) ?\()
                            (zerop (c-backward-token-2 2 t stmt-start))
                            (looking-at operator))
                           (and
                            (zerop (c-backward-token-2 1 t stmt-start))
                            (looking-at operator))
                           (and
                            (zerop (c-backward-token-2 1 t stmt-start))
                            (looking-at operator))
                           )
                       )
              (setq col (current-column))
    
              (while (or (and
                          (zerop (c-backward-token-2 1 t stmt-start))
                          (eq (char-after) ?\()
                          (zerop (c-backward-token-2 2 t stmt-start))
                          (looking-at operator))
                         (and
                          (zerop (c-backward-token-2 1 t stmt-start))
                          (looking-at operator))
                         (and
                          (zerop (c-backward-token-2 1 t stmt-start))
                          (looking-at operator))
                         )
                (setq col (current-column)))
    
              (vector col))))))
    

    It seems to work for the optional parenthesis and for double parenthesis call (i.e. call!(type)(arg) ) as well. But maybe there are some corner cases, so don't count on it too much, it's just an idea to work with.