4

I'm sure emacs used to do it my way, but things appear to have changed (probably years ago, I've just been living with the pain).

I want this (to conform to corp. standards):

MULTIFORM=$(
    curl -k -A http://foo.com |
    grep -m1 multiform |
    tr '=' '\n' |
    tail -1 |
    cut -d "'" -f 2
)

But emacs-25.1 insists that this is the right way to indent:

MULTIFORM=$(
    curl -k -A http://foo.com |
        grep -m1 multiform |
        tr '=' '\n' |
        tail -1 |
        cut -d "'" -f 2
         )

... quite apart from our standards, it's ugly.

I've tried messing around with sh-learn-line-indent and it offers rules like:

close-all ")"
list-intro ";"

... but playing with them gives no joy at all.

Any ideas?

  • Looks like being worth a bug-report. – Andreas Röhler Nov 2 '16 at 7:12
  • You can see if the settings from this question (not the answer): emacs.stackexchange.com/q/26329/184 help. – T. Verron Nov 2 '16 at 8:47
  • I did read that one but it appears to be another version of emacs??? Anyway, mine doesn't have variables sh-indentation-*. And I have tried yr suggestion of moving aside .emacs .elisp.d and .config/emacs. Nothing seems to make any difference. How does other people emacs' treat the above? – wef Nov 2 '16 at 10:50
  • ... and I'll enter a bug report if I get nowhere with this - good suggestion! – wef Nov 2 '16 at 10:53
4

For the indentation after the | you should be able to get the right result by going to the incorrect line (i.e. the one that starts with grep) and then do M-x smie-config-set-indent RET before "|" RET nil RET.

But smie-config is not up to the task when it comes to tweaking the behavior of the close paren in the way you want. For that you'll need actual code. One possible approach could look like:

(defun my-sh-indent-rules ()
  (when (eq ?= (char-before))
    (skip-chars-backward "[:alnum:]_=")
    (current-column)))

(add-hook 'sh-mode-hook
          (lambda ()
            (add-hook 'smie-indent-functions
                      #'my-sh-indent-rules
                      nil 'local)))

BTW, if you want to handle the | alignment "by hand" rather than via smie-config, you could do it this way:

(defun my-sh-smie-rules (orig-fun kind token)
  (pcase (cons kind token)
   (`(:before . "|") nil)
   (_ (funcall orig-fun kind token))))
(advice-add 'sh-smie-sh-rules :around #'my-sh-smie-rules)

wef: This is the final bit of the puzzle that Stefan gave me in my muddle below - how to save the customisations for next time. As he said, I needed to teach smie about my preferences eg with M-x smie-config-set-indent then run M-x smie-config-save and look at the variable smie-config. Finally, I copied those rules (in my case, just one rule) into .emacs:

(defun my-sh-indent-rules ()
  (smie-config-local '((nil :before "|" (column . 8)))))
(add-hook 'smie-indent-functions #'my-sh-indent-rules)

There may be other ways to do this eg with the commands in sh-mode eg M-x sh-learn-line-indent - but I wasn't able to get it to work.

  • Hmmm - M-x smie-config-set-indent gave me choices for Adjust rule: of: after ";" and before ";" but it objected to: before "|" with "No match" – wef Nov 3 '16 at 0:40
  • my-sh-indent-rules appears to work!! Thank you for that. I tried upvoting the answer but I don't have reputation for that. – wef Nov 3 '16 at 0:46
  • M-x smie-config-show-indent on the 'grep' line shows: Rules used: : before "|" -> (column . 8), :elem basic -> 4, :after "|" -> nil – wef Nov 3 '16 at 6:06
  • The output you give for smie-config-show-indent looks right, so you just need to M-x smie-config-set-indent at the same place and set before "|" to nil. – Stefan Nov 3 '16 at 12:41
  • Hoo boy! that worked like a bought one! Thanks so much! Now I need to try and work out why I missed that - I thought I'd tried all the combinations and permutations. Obviously not! – wef Nov 4 '16 at 4:21
0

The following is a broken (or incomplete) answer ... I'm trying to fix my original problem using the sh-mode calls only instead of the lower-level smie calls that Stefan was kind enough to provide, just to see if it can be done. Reading those docs and running in a fresh emacs -Q session, I did this:

M-x sh-learn-line-indent RET before "|" RET nil

The style then worked for me and indented the lines ending in "|" - but not the final ")" - I'll still need Stefan's code for that. Then:

M-x sh-name-style RET bobs
M-x sh-save-styles-to-buffer RET *scratch*

and then used that code to create this:

(defun my-sh-indent-rules ()
  (setq sh-styles-alist
        '(("bobs"
           (sh-basic-offset . 4)
           (sh-first-lines-indent . 0)
           (sh-indent-after-case . +)
           (sh-indent-after-do . +)
           (sh-indent-after-done . 0)
           (sh-indent-after-else . +)
           (sh-indent-after-if . +)
           (sh-indent-after-loop-construct . +)
           (sh-indent-after-open . +)
           (sh-indent-comment . t)
           (sh-indent-for-case-alt . ++)
           (sh-indent-for-case-label . +)
           (sh-indent-for-continuation . +)
           (sh-indent-for-do . 0)
           (sh-indent-for-done . 0)
           (sh-indent-for-else . 0)
           (sh-indent-for-fi . 0)
           (sh-indent-for-then . 0))))
  (sh-load-style "bobs"))
(add-hook 'sh-mode-hook #'my-sh-indent-rules)

... note that it has no before "|" rule. Perhaps it doesn't know how to do that?

Anyway, running that in a fresh emacs -Q session it doesn't work even after a M-x sh-load-style RET bobs, so I'm reverting to the default behaviour.

I'm happy with Stefan's smie solution above, but I'd be interested to know how to make it work with sh-mode.

  • I think sh-styles-* stuff only works if you set sh-use-smie to nil. – npostavs Dec 28 '18 at 1:51
  • @npostavs - you're right, it works now, at least for indenting the pipeline. It may need more work to get the ')' to indent correctly. – wef Dec 29 '18 at 2:49

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.