15

I'm modifying some elisp code from linum.el:

(custom-set-variables '(linum-format 'dynamic))
(defadvice linum-update-window (around linum-dynamic activate)
   (let* ((w (length (number-to-string
      (+ (count-lines (point-min) (point-max)) 1))))
         (linum-format (concat " %" (number-to-string w) "d ")))
            ad-do-it))

I was able to fix a bug where the indentation was off-by-one by modifying (count-lines (point-min) (point-max)) to (+ (count-lines (point-min) (point-max)) 1). That was easy.

But now I want to modify it so that the minimum width is 2 by adding an if-conditional where (concat " %" (number-to-string w) "2d ") if the line number count is < 10.

This should be easy! Add one conditional and copy/paste the concat. Piece of cake, right? I mean, I know what I'm supposed to do but I rarely touch elisp and I'm always daunted when I have to modify anything with lots of parenthesis.

The "correct" style, from what I understand, is to structure the code based on indentation and wrap the trailing parenthesis at the end of a line rather than on its own. Coming from other 'C' style languages, I struggle with both reading and writing code in this way. So my question is: what am I doing wrong? How am I supposed to edit elisp and navigate around the code such that I don't have to sit there and count every parenthesis?

When I work with something in elisp that gets too deep I have to shut my door, pull the blinds, and start positioning the parenthesis K&R-style so that I can not only read but modify the darn thing without freaking out.

Obviously I'm doing this all wrong. How can I touch elisp like this without fear?

Please note that my question is how to navigate and edit elisp not as a question on style. I am using the following as a style guide already: https://github.com/bbatsov/emacs-lisp-style-guide

Updates:

How to properly format elisp before embarassing yourself on emacs.stackexchange:

Mark your elisp and perform M-x indent-region.

The Problem Solution:

For those wanting to know how to perform a right-justify for linum with a minimum width of two, here is the solution:

(defadvice linum-update-window (around linum-dynamic activate)
  (let* ((w (length (number-to-string
                     (+ (count-lines (point-min) (point-max)) 1))))
         (linum-format (if (> w 1)
                           (concat " %" (number-to-string w) "d ")
                         " %2d ")))
    ad-do-it))
Zhro
  • 359
  • 3
  • 11
  • 1
    Everyone struggles with reading lisp that comes from C. It just takes time, but eventually it becomes extremely natural. Also you can probably solve your problem by changing linum-format to a format string like "%2d ". – Jordon Biondo Aug 05 '15 at 15:30
  • I can't just use `%2d` because once the width rolls over to 3 or more characters then it moves from bring right-justified to left-justified. – Zhro Aug 05 '15 at 15:36
  • There are a few libraries that highlight matching parentheses that are invaluable (in my opinion), e.g., `highlight-parentheses`; `rainbow-delimiters`; etc. Here is my own simplified version of `highlight-parentheses` that permits scrolling without removing the parentheses that were last colorized: http://stackoverflow.com/a/23998965/2112489 In the future, it's one question per customer/thread. – lawlist Aug 05 '15 at 15:39
  • 1
    It sounds a lot like your problem is really _reading_, not editing Elisp code. You sound like you have problems understanding the structure of Lisp, and that impairs your editing. – PythonNut Aug 05 '15 at 16:19
  • 1
    In your defense, that's a pretty badly indented piece of code. I read it wrong myself the first 3 times because of that. Also, [paredit](http://emacsrocks.com/e14.html). – Malabarba Aug 06 '15 at 16:06

9 Answers9

12

There are a number of add-on packages that might help, such as paredit, smartparens, and lispy. These packages make it easier to navigate and manipulate lisp code so that you can think in s-expressions and let the editor worry about balancing parens.

Emacs also has lots of built-in commands for dealing with sexps and lists that are worth learning and may help you get more used to the structure of lisp code. These are generally bound with a C-M- prefix, such as:

  • C-M-f/C-M-b to move forward/backward by sexp
  • C-M-n/C-M-p to move forward/backward by list
  • C-M-u/C-M-d to move up/down one level
  • C-M-t to swap the two sexps around point
  • C-M-k to kill a sexp
  • C-M-SPC to mark a sexp
  • C-M-q to re-indent a sexp

The example code you provided might be clearer if you fix the indentation: put point at the beginning of the (defadvice... and hit C-M-q to re-indent the entire expression. To replace the (concat... I would start by putting point at the start of that sexp and hitting C-M-o to split the line while preserving the indentation. Then add your (if..., and use the commands above to jump to the end of a list to add another closing paren, back to the beginning to re-indent, etc.

You might also want to turn on show-paren-mode, which will highlight the corresponding paren when point is at the start or end of a list. You may prefer to highlight the whole expression rather than the matching paren, so try customizing show-paren-style.

As @Tyler mentioned in the comments on another answer, you should take a look at the manual to learn more about these and related commands.

glucas
  • 20,175
  • 1
  • 51
  • 83
  • Note that `C-M-q` can be called with a prefix argument (`C-u C-M-q`) to pretty-print the expression at point. This will break everything on to separate lines and indent so that the nesting is very obvious. You generally don't want your lisp code to look like that, but it might be helpful to understand a bit of code without manually going full K&R on it. (And you can always `C-x u` to undo). – glucas Aug 05 '15 at 17:59
6

A good way to edit LISP is by manipulating the AST, instead of the individual characters or lines. I've been doing that with my package lispy that I started 2 years ago. I think that it can actually take quite a bit of practice to learn how to do this well, but even using just the basics should already help you.

I can explain how I would go about making this change:

Step 1

The starting position is usually to have the point before the top-level sexp. Here, | is the point.

|(defadvice linum-update-window (around linum-dynamic activate)
   (let* ((w (length (number-to-string
      (+ (count-lines (point-min) (point-max)) 1))))
         (linum-format (concat " %" (number-to-string w) "d ")))
            ad-do-it))

You'll want to have the code properly indented, so press i once. It will re-indent it nicely.

Step 2

You'll want to mark "d " with a region, since a manipulate-able object in lispy is either a list, which doesn't need to be marked, or a sequence of symbols or/and lists which needs to be marked with a region.

Press at to do so. The a command allows to mark any single symbol within the parent list, and t is the index of this particular symbol.

Step 3

  1. To wrap the current region with (), press (.
  2. Press C-f to move forward one char and insert if.
  3. Press ( again to insert ().
  4. Insert > w 10.
  5. C-f C-m to move the string to a new line.

Step 4

To clone the string:

  1. Mark the symbol-or-string-at-point with M-m.
  2. Press c.

If you want to deactivate the region in a fancy way and put the point at the very start of the string, you can press idm:

  1. i will select the inner contents of the string.
  2. d will swap point and mark.
  3. m will deactivate the region

Or you could do m C-b C-b C-b in this case, or C-g C-b C-b C-b. I think idm is better, since it's the same regardless of the length of the string. I think C-x C-x C-f C-g would also work regardless of the string length.

Finally, insert 2 to obtain the end code:

(defadvice linum-update-window (around linum-dynamic activate)
  (let* ((w (length (number-to-string
                     (+ (count-lines (point-min) (point-max)) 1))))
         (linum-format (concat " %" (number-to-string w) (if (> w 10)
                                                             "2|d "
                                                           "d "))))
    ad-do-it))

Summary

The full sequence was: at( C-f, if, (, > w 10, C-f C-m M-m cidm, 2.

Note that if you count out the code insertion, the only keys not related to AST manipulation were two C-f and one C-m.

abo-abo
  • 13,943
  • 1
  • 29
  • 43
  • 3
    Lispy sounds interesting! To the OP: you can develop similar workflows with paredit: http://emacsrocks.com/e14.html and expand region: https://github.com/magnars/expand-region.el , or just playing around with the built-in parenthesis matching commands: https://www.gnu.org/software/emacs/manual/html_node/emacs/Parentheses.html – Tyler Aug 05 '15 at 17:08
  • I'm sure you were guessing when you tied to solve the problem for me; thank you for the attempt. I managed to muddle my way through it using your code as an example. See my update for the correct solution. – Zhro Aug 27 '15 at 06:03
6

You'll get used to it over time, but of course there's lots you can do to help speed it up:

Indentation

There's a method to this madness. In lisp you can do this:

(a-fun (another-fun (magic-macro 1)))

Suppose that 1 is really a large expression, and you want to indent it on its own line.

(a-fun (another-fun (magic-macro 
  1)))

This is misleading. 1 is indented only one slot, even though it's an entire three levels of nesting higher! Better put it by its parent.

(a-fun (another-fun (magic-macro 
                      1)))

If we use this scheme on your own code, we get this:

(custom-set-variables '(linum-format 'dynamic))
(defadvice linum-update-window (around linum-dynamic activate)
  (let* ((w (length (number-to-string
                      (+ (count-lines (point-min) (point-max)) 1))))       
          (linum-format 
            (concat " %" (number-to-string w) "d ")))
     ad-do-it))

Note the strong correlation between indentation and the level of nesting. Lines with a greater level of nesting are always indented more than lines with less.

This alone will help a lot. You can easily see the "shape" of the code, and most other languages have trained you to associate indentation with blocks and blocks with some form of nesting (either explicit or implied).

As an exercise, I've removed the nonessential parentheses from the code. You should be able to both read the code and supply the missing parentheses, given basic knowledge of Elisp (namely, what things are functions and values, and the structure of let*).

custom-set-variables '(linum-format 'dynamic)
defadvice linum-update-window (around linum-dynamic activate)
  let* w length number-to-string
                  + 
                    count-lines (point-min) (point-max) 
                    1       
         linum-format 
           concat " %" (number-to-string w) "d "
     ad-do-it
PythonNut
  • 10,243
  • 2
  • 29
  • 75
  • 1
    Note that in Emacs, hitting tab will almost always set the indentation correctly as described here - you should never do it by adding spaces by hand! – Tyler Aug 05 '15 at 17:02
  • Bind "C-m" to `newline-and-indent` for emacs-lisp-mode and lisp-interaction-mode solves your first example, PythonNut. And TAB will do the job the rest of the time. – Davor Cubranic Aug 05 '15 at 17:26
5

I'm including this as a different answer.

Sometimes, indentation will fail you. Your recourse then, is to use something like rainbow-delimiters:

enter image description here

This makes the nesting level of any paren explicit and easily scanned.

There is one pretty big problem though: the parens are ludicrously distracting. There's a balance of emphasis here. rainbow-delimiters will normally put a lot of emphasis on the parens at the expense of the rest of your code! But if you tone the rainbow faces down, they become progressively harder to scan.

If you can find one set of faces that balances this well, then by all means use it.

That said, one solution (the one that makes me cackle with glee), is to have two sets of faces, and switch between them on the fly. When you're doing other stuff, the faces are dilute and fade into the background:

enter image description here

But when you put the point on a paren, it punches the parens so you can see the nesting levels:

enter image description here

And here is the code to do that:

(defvar rainbow-delimiters-switch nil
  "t if rainbow-delimiters are currently punched")
(defvar rainbow-delimiters-face-cookies nil
  "a list of face-remap-add-relative cookies to reset")

(make-variable-buffer-local 'rainbow-delimiters-switch)
(make-variable-buffer-local 'rainbow-delimiters-face-cookies)

(add-hook 'prog-mode-hook #'rainbow-delimiters-mode)
(add-hook 'text-mode-hook #'rainbow-delimiters-mode)

(with-eval-after-load 'rainbow-delimiters
  (set-face-foreground 'rainbow-delimiters-depth-1-face "#889899")
  (set-face-foreground 'rainbow-delimiters-depth-2-face "#9b7b6b")
  (set-face-foreground 'rainbow-delimiters-depth-3-face "#7b88a5")
  (set-face-foreground 'rainbow-delimiters-depth-4-face "#889899")
  (set-face-foreground 'rainbow-delimiters-depth-5-face "#839564")
  (set-face-foreground 'rainbow-delimiters-depth-6-face "#6391aa")
  (set-face-foreground 'rainbow-delimiters-depth-7-face "#9d748f")
  (set-face-foreground 'rainbow-delimiters-depth-8-face "#7b88a5")
  (set-face-foreground 'rainbow-delimiters-depth-9-face "#659896")

  (defun rainbow-delimiters-focus-on ()
    "Punch the rainbow-delimiters"
    (setq rainbow-delimiters-face-cookies
      (list
        (face-remap-add-relative 'rainbow-delimiters-depth-1-face
          '((:foreground "#3B9399") rainbow-delimiters-depth-1-face))
        (face-remap-add-relative 'rainbow-delimiters-depth-2-face
          '((:foreground "#9B471D") rainbow-delimiters-depth-2-face))
        (face-remap-add-relative 'rainbow-delimiters-depth-3-face
          '((:foreground "#284FA5") rainbow-delimiters-depth-3-face))
        (face-remap-add-relative 'rainbow-delimiters-depth-4-face
          '((:foreground "#3B9399") rainbow-delimiters-depth-4-face))
            (face-remap-add-relative 'rainbow-delimiters-depth-5-face
          '((:foreground "#679519") rainbow-delimiters-depth-5-face))
        (face-remap-add-relative 'rainbow-delimiters-depth-6-face
          '((:foreground "#0E73AA") rainbow-delimiters-depth-6-face))
        (face-remap-add-relative 'rainbow-delimiters-depth-7-face
          '((:foreground "#9D2574") rainbow-delimiters-depth-7-face))
        (face-remap-add-relative 'rainbow-delimiters-depth-8-face
          '((:foreground "#284FA5") rainbow-delimiters-depth-8-face))
        (face-remap-add-relative 'rainbow-delimiters-depth-9-face
          '((:foreground "#199893") rainbow-delimiters-depth-9-face)))
      rainbow-delimiters-switch t))

  (defun rainbow-delimiters-focus-off ()
    "Reset the rainbow-delimiters faces"
    (mapc #'face-remap-remove-relative rainbow-delimiters-face-cookies)
    (setq rainbow-delimiters-switch nil))

  (defun rainbow-delimiters-focus-on-maybe ()
    "Punch the rainbow-delimiters if the point is on a paren"
    (when (looking-at "[][(){}]")
      (unless (or rainbow-delimiters-switch (minibufferp))
        (rainbow-delimiters-focus-on))))

  (defun rainbow-delimiters-focus-off-maybe ()
    "Reset the rainbow-delimiters if the point is not on a paren"
    (unless (looking-at "[][(){}]")
      (when rainbow-delimiters-switch
        (rainbow-delimiters-focus-off))))

  (run-with-idle-timer 0.6 t 'rainbow-delimiters-focus-on-maybe)
  (run-with-idle-timer 0.1 t 'rainbow-delimiters-focus-off-maybe))
PythonNut
  • 10,243
  • 2
  • 29
  • 75
  • This is cool! Any reason you chose rainbow-delimiters over highlight-parentheses to start with? – Tyler Aug 05 '15 at 18:37
  • @Tyler no particular reason, although I'd still stick with rainbow delimiters now (but that may just be because I'm used to it). I simply hadn't heard of highlight-parenthesis until very recently. – PythonNut Aug 05 '15 at 19:00
4

If you indent your code, then you really need to indent it properly. Lisp developers have expectations what properly indented should look like. This does not mean the code looks all the same. There are still different ways to format code.

  • how long should a line be?
  • how to distribute a function/macro call over lines?
  • how many spaces of indentation should there be?
(defadvice linum-update-window (around linum-dynamic activate)
   (let* ((w (length (number-to-string
      (+ (count-lines (point-min) (point-max)) 1))))
         (linum-format (concat " %" (number-to-string w) "d ")))
            ad-do-it))

I would expect that indentation shows containment in some way. But not in your code.

By default your code might be indented like this. Other answers described how you can do that with the help of Emacs:

(defadvice linum-update-window (around linum-dynamic activate)
  (let* ((w (length (number-to-string
                     (+ (count-lines (point-min) (point-max)) 1))))
         (linum-format (concat " %" (number-to-string w) "d ")))
    ad-do-it))

I would expect that the let bound variables start on the same vertical column. I would also expect that the body of the let is less indented. We learn how a let should be indented. Once you train that a bit, it's a visual pattern that's easy to recognize.

The main visual building blocks in the code are the defadvice, let* and a bunch of function calls. The def in the name tells me that it is a definition followed by a name, an argument list and a body.

Thus I like to see

defadvice name arglist
  body

The let* would be: let*, a list of bindings and a body.

Thus I like to see:

let*  list of bindings
  body

More detailed:

let*   binding1
       binding2
       binding3
  body

For a function call I like to see:

FUNCTIONNAME ARG1 ARG2 ARG3

or

FUNCTIONNAME ARG1
             ARG2
             ARG3

or

FUNCTIONNAME
  ARG1
  ARG2
  ARG3

Which one gets used depends on how long the arguments are. We don't want to make lines too long and each arg on its own line allows us to have more structure.

So you need to write your code using those patterns and you need to make sure that the reader can easily identify those patterns in your code.

Parentheses should directly enclose their constructs.

(sin 10)

(sin
  10)

Avoid whitespace examples:

(  sin 10  )

or

(
   sin
)

or

(sin 10
)

In Lisp, the parentheses have no language syntactic function, they are a part of the syntax of the list data structure (s-expressions). Thus we write lists and format the lists. For formatting the list contents we use knowledge about the Lisp programming language. A let expression should be formatted different than a function call. Still both are lists and the parentheses should directly enclose the lists. There are some examples where it makes sense to have a single parenthesis on a line, but use that rarely.

Use the parentheses to support you finding the expression boundaries. Let them help you to move from list to list, to edit lists, to cut&copy lists, to replace lists, to indent/format lists, select lists,...

Rainer Joswig
  • 252
  • 1
  • 7
2

For basic help with indentation, use TAB and "C-M-q" (indent-pp-sexp); binding "C-m" to newline-and-indent will save you having to press TAB after a newline.

You can navigate by sexp with "C-M-left/right/up/down/home/end", which is very useful.

If you're worried about maintaining parenthesis balance, use something like smartparens (it's a bit more forgiving that paredit, which can initially feel like wearing a straitjacket). It also comes with lots of bindings for navigating and editing at the sexp level.

Lastly, for highlighting parens, start by turning the option on (Options menu->Highlight matching parentheses, or show-paren-mode.) You can also use one of the packages mentioned by lawlist in his comment, like "highlight-parenthesis" or "rainbow-delimiters".

Davor Cubranic
  • 700
  • 4
  • 9
2

As an addition to what others have given as answers, here are my 2 cents:

  • The automatic indentation provided by emacs-lisp-mode is essential.
  • blink-matching-paren is on (non-nil) by default. Leave it so.
  • I use show-paren-mode, to highlight the left paren corresponding to the right paren before the cursor.
  • I temporarily delete and retype the right paren before the cursor, if I want to see better where the matching left paren is.

I do not use electric pairing or rainbow anything or any other such "help". To me those are a bother, not an aid.

In particular, electric-pair-mode is, to me, an inconvenient bother. But some people love it. And I imagine that it might be helpful in some other pairing contexts besides Lisp parentheses (I imagine so, but I'm not convinced for such use cases either).

You will find what works best for you. The main things I want to say are: (1) automatic indentation is your friend, as is some way to spot the left paren that matches the right paren before point.

In particular, seeing what automatic indentation does for you teaches you immediately that you never again want to put right parens on a line by themselves. That style of coding, which is ubiquitous in other languages, is just noisy.

Besides the automatic indentation you get with RET and TAB, get comfortable using C-M-q. (Use C-h k in emacs-lisp-mode to find out what these keys do.)

Drew
  • 75,699
  • 9
  • 109
  • 225
  • When you say that you do not use electric pairing, do you not use paredit too? – Lone Learner Dec 05 '20 at 16:34
  • @LoneLearner: Correct. I find automatic insertion of a closing delimiter to be limiting to editing (and, yes, even to understanding). It necessitates a bunch of other commands (slurping etc.) that are totally unnecessary if the closing delimiter isn't present. That's a case of making (claiming) a virtue out of necessity. I use only `show-paren-mode` and `blink-matching-paren` - they are largely sufficient (for me), to tell me which is the matching paren. – Drew Dec 05 '20 at 18:20
1

I noticed some people already mentioned rainbow-delimiters, that's also my favorite mode when editing lisp code.

Actually I love parentheses, the best thing about Lisp is those parentheses, it's fun to dealing with them if you know howto:

  • install evil-mode and its plugin evil-surround, so you can edit parentheses easily, for example, press ci( will remove everything inside (), va( will select the thing wrapped by "()" and the "()" themselves. and you can press % to jump between the matched parentheses

  • use flycheck or flymake, unmatched parentheses will be underlined in real time

chen bin
  • 4,781
  • 18
  • 36
0

The difficulty here is that you want to add some code before and after a certain sexp. In this situation the sexp is (concat " %" (number-to-string w) "d "). You want to insert the if-statement (if (> w 1) before it and the else-statement " %2d ") after it.

A major part of the difficulty is to isolate this sexp, personally I use the below command for isolating a sexp

(defun isolate-sexp () (interactive)
       (save-excursion (forward-sexp) (if (not (eolp-almost))
               (split-line) nil)))
(defun eolp-almost () (interactive)
(save-excursion (while (equal (char-after) ? ) (forward-char 1))
        (if (eolp) t nil )      ))

Just put the cursor at the beginning of (concat " %" (number-to-string w) "d "), i.e., at the first (, then apply the above isolate-sexp. You obtain

(defadvice linum-update-window (around linum-dynamic activate)
   (let* ((w (length (number-to-string
      (+ (count-lines (point-min) (point-max)) 1))))
         (linum-format (concat " %" (number-to-string w) "d ")
                                  ))
            ad-do-it))

Then add the appropriate code before and after this sexp, i.e.

(defadvice linum-update-window (around linum-dynamic activate)
   (let* ((w (length (number-to-string
      (+ (count-lines (point-min) (point-max)) 1))))
         (linum-format (if (> w 1) (concat " %" (number-to-string w) "d ") " %2d ")
                                  ))
            ad-do-it))

And voila. The obtained code in this way is perhaps not beautiful but the chance of getting lost is smaller.

Name
  • 7,689
  • 4
  • 38
  • 84