3

When writing Lisp, I often encounter a long line such as the following.

(map function (|apply append long-list-name1 long-list-name2 long-list-name3))

(where '|' denotes the point).

This can be a problem for readability especially if the long line ends up being nested at a deeper level. Is there a package available that will transform this into the following (and also perform the inverse operation)?

(map function (|apply append
                      long-list-name1 
                      long-list-name2 
                      long-list-name3))
  • multiple-cursors: select the first space after append and mark next like this and keep going until the final fake cursor is before the last element of the list, then control-q control-j, then either tab or space -- I have two-spaces on a keyboard shortcut (e.g., M-SPC) so I would just hit that a few times until the indentation was to my liking. If delete selection mode is not enabled, then delete the extra space before creating new lines or move the cursors one to the left if so desired. – lawlist Nov 1 '16 at 21:15
  • Thanks for the comment but that is a lot of keystrokes for a common task. I'm looking for something that can accomplish this with a single command along the lines of sexp-manipulation commands in packages like smart-parens (which AFAICT doesn't cover this case). – Qudit Nov 1 '16 at 22:10
  • 1
    If you can tolerate putting the append also on the next line, then just use RET after apply and then use C-M-q before (map. – Drew Nov 1 '16 at 23:16
  • if you use xah-elisp-mode and xah-fly-keys, press m 1 ' TAB will do. m calls xah-backward-left-bracket will move cursor to left parenthesis. 1 is xah-extend-selection that select the sexp. ' is xah-reformat-lines which is like fill region. TAB is xah-elisp-complete-or-indent, it reformat whole sexp cursor is in. Am working on just need to press TAB. – Xah Lee Nov 2 '16 at 1:40
1

I "translate" manual steps into Emacs Lisp. It works in the following demos but it is not reliable (that is, it will produce unexpected results or even block your Emacs, if you run the commands at the wrong position).

(foo| bar baz)
  ->
(foo|
 bar
 baz)

(defun indent-sexp-after-point ()
  (interactive)
  (save-excursion
    (while (condition-case nil
               (prog1 t
                 (forward-sexp)
                 (backward-sexp)
                 (newline-and-indent)
                 (forward-sexp))
             (error nil)))))

(foo|
 bar
 baz)
  ->
(foo| bar baz)

(defun join-sexp-after-point ()
  (interactive)
  (save-excursion
    (while (condition-case nil
               (prog1 t
                 (forward-sexp)
                 (backward-sexp)
                 (delete-indentation)
                 (forward-sexp))
             (error nil)))))
1

You can use the command special-lispy-alt-multiline from abo-abo's lispy. In lispy-mode, it is bound to M. There is also a special-lispy-oneline command bound to O.

If you are not familiar with lispy, the key bindings might surprise you (they're just letters!): the idea in lispy, is that since you are quite unlikely to want to put a letter immediately to the left of an opening parenthesis, or immediately to the right of a closing one, letter keys can be made to do double duty: they insert the letter away from those locations and do something else just outside a parenthesized expression.

Also, I should point out that while this command comes close to what you want it doesn't do exactly what you asked for: pressing M will convert

(map function |(apply append long-list-name1 long-list-name2 long-list-name3))

to

(map function (apply
               append
               long-list-name1
               long-list-name2
               long-list-name3))
0

Thanks xuchunyang - below a variant of this solution:

indent-sexp-after-point should indent according to column of symbol before.

(foo bar| baz)
  ->
(foo bar|
     baz)

(defun indent-sexp-after-point ()
  (interactive "*")
  (save-excursion
    (let ((indent (save-excursion (backward-sexp)
                  (current-column))))
      (while (ignore-errors
           (prog1 t
        (forward-sexp)
        (backward-sexp)
        (newline)
        (indent-to indent)
        (forward-sexp)))))))

join-sexp-after-point should be protected against eternal loop:

(foo|
 bar
 baz)
  ->
(foo| bar baz)

(defun join-sexp-after-point ()
  (interactive "*")
  (save-excursion
    (let (orig)
      (while (ignore-errors
           (progn
         (forward-sexp)
         (setq orig (copy-marker (point)))
         (backward-sexp)
         (delete-indentation)
         (forward-sexp)
         (<= orig (point))))))))

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.