Emacs: remove spaces between expressions
Asked Answered
T

5

5

While writing Clojure code, I often end up with spaces between the last expression and the closing brackets. Something like

(defn myfunction
  [arg]
  (do
    (expr1)
    (expr2)|
  ))

where | is the position of the cursor. Is there a shortcut in Emacs to delete the spaces between (expr2) and the final brackets? The goal is to end up with

(defn myfunction
  [arg]
  (do
    (expr1)
    (expr2)))
Thurmanthurmann answered 20/9, 2013 at 19:37 Comment(1)
I just do C-k M-\ in such case, but maybe paredit has something better for it.Biosynthesis
C
4

M-^ (command delete-indentation) already does what you requested, at least in the example you gave (and similar). See (elisp) User-Level Deletion.

Clothe answered 20/9, 2013 at 23:29 Comment(3)
Yes, but you'd have to be somewhere in the bottom line (the one containing ))) for it to work.Clarinda
This is the correct answer and it's how Evil-mode replicates Vim's S-j behavior. To join instead with the following line, you simply give it an argument. Although I recommend stealing and binding the evil-join function I linked which will run (join-line 1) for every line you've selected.Plowshare
And you can use it from "higher line" (the one containing (expr2)). All you have to do is to run delete-indention in the other direction, with negative-argument. In short M-- M-^.negative-argument is very powerful argument, and it is bound to all modifiers C--, M-- and C-M-- which makes it extremely easy to use before any other keybinding (you can use it with anything without releasing any modifiers).Anatomize
P
3

Send the prefix argument to M-^:

C-u M-^

Without the prefix, M-^ joins the current line with the previous.

With the prefix (the C-u), M-^ joins the next line with the current.

Petry answered 5/10, 2013 at 4:28 Comment(0)
C
1

Improving on @wvxvw's comment above, you can add the following to your .emacs file. Then, C-z m (or any other key combination that you select) will do what you want. In fact, it will work if you're at any point of the line containing (expr1).

(global-set-key "\C-zm" 'join-lines-removing-spaces)
(defun join-lines-removing-spaces ()
  "Join the current line with the next, removing all whitespace at this point."
  (move-end-of-line nil)
  (kill-line)
  (delete-horizontal-space))
Clarinda answered 20/9, 2013 at 19:59 Comment(0)
Y
1

Here's my view of the solution to this problem: in your example what you probably want to do at that moment is to exit the list. So why not attach the whitespace cleanup to list exit?

(global-set-key (kbd "C-M-e") 'up-list-robust)

(defun up-list-robust ()
  (interactive)
  (remove-gaps)
  (let ((s (syntax-ppss)))
      (when (nth 3 s)
        (goto-char (nth 8 s)))
      (ignore-errors (up-list))
      (remove-gaps)))

(defun remove-gaps ()
  (when (looking-back ")\\([ \t\n]+\\))")
    (delete-region (match-beginning 1)
                   (match-end 1))))

So now, any time you exit a list, the closest white space, trapped between two parens is removed.

I've just written it and suggestions to improve are welcome, but I've used it for a few minutes and I like it. You also might want to bind this to a better shortcut, C-M-e was the default for up-list.

Yemane answered 21/9, 2013 at 13:40 Comment(0)
P
0

Here is a function I wrote to solve this specific problem for myself.

(defun delete-surrounding-whitespace ()
  (interactive)
  (let ((skip-chars "\t\n\r "))
    (skip-chars-backward skip-chars)
    (let* ((start (point))
           (end (progn
                  (skip-chars-forward skip-chars)
                  (point))))
      (delete-region start end))))

In the spirit of teaching fishing over providing fish, I'll share how to discover this from inside Emacs.

If you know the anything about the name of the function or variable that you want more information about, then you can use apropos to dig deeper. But what if you don't know the what the command might be called?

For example, I might use apropos and search for del.*white, zap.*space, del.*space, etc... and never come across a helpful whitespace function like just-one-space.

To widen the range of your search, you can search the documentation of Emacs from inside of Emacs by pressing C-h i to get to the Texinfo docs. Press mEmacs to enter the Emacs-specific part of the documentation (there will also be sections for some packages). Once in the Emacs section, press s to search and perform the search for something like delete.*white and you'll be taken to the Deletion section of the docs where you'll see all sorts of helpful methods of deletion.

12.1.1 Deletion
---------------

Deletion means erasing text and not saving it in the kill ring.  For the
most part, the Emacs commands that delete text are those that erase just
one character or only whitespace.

‘<DEL>’
‘<BACKSPACE>’
     Delete the previous character, or the text in the region if it is
     active (‘delete-backward-char’).

‘<Delete>’
     Delete the next character, or the text in the region if it is
     active (‘delete-forward-char’).

‘C-d’
     Delete the next character (‘delete-char’).

‘M-\’
     Delete spaces and tabs around point (‘delete-horizontal-space’).
‘M-<SPC>’
     Delete spaces and tabs around point, leaving one space
     (‘just-one-space’).
‘C-x C-o’
     Delete blank lines around the current line (‘delete-blank-lines’).
‘M-^’
     Join two lines by deleting the intervening newline, along with any
     indentation following it (‘delete-indentation’).

I didn't see anything there that did exactly what I was looking for. But, by using apropos to search and pull up the help buffers for some of the functions, I could see how they were implemented and use those same techniques to write the exact function I need.

Looking in simple.el.gz at the function just-one-space, I saw a nearby function called cycle-spacing that looked like it was close to having the functionality that I needed.

(defun cycle-spacing (&optional n preserve-nl-back mode)
  "Manipulate whitespace around point in a smart way.
In interactive use, this function behaves differently in successive
consecutive calls.

The first call in a sequence acts like `just-one-space'.
It deletes all spaces and tabs around point, leaving one space
\(or N spaces).  N is the prefix argument.  If N is negative,
it deletes newlines as well, leaving -N spaces.
\(If PRESERVE-NL-BACK is non-nil, it does not delete newlines before point.)

The second call in a sequence deletes all spaces.

The third call in a sequence restores the original whitespace (and point).

If MODE is `single-shot', it only performs the first step in the sequence.
If MODE is `fast' and the first step would not result in any change
\(i.e., there are exactly (abs N) spaces around point),
the function goes straight to the second step.

Repeatedly calling the function with different values of N starts a
new sequence each time."
  (interactive "*p")
  (let ((orig-pos    (point))
    (skip-characters (if (and n (< n 0)) " \t\n\r" " \t"))
    (num         (abs (or n 1))))
    (skip-chars-backward (if preserve-nl-back " \t" skip-characters))
    (constrain-to-field nil orig-pos)
    (cond
     ;; Command run for the first time, single-shot mode or different argument
     ((or (eq 'single-shot mode)
      (not (equal last-command this-command))
      (not cycle-spacing--context)
      (not (eq (car cycle-spacing--context) n)))
      (let* ((start (point))
         (num   (- num (skip-chars-forward " " (+ num (point)))))
         (mid   (point))
         (end   (progn
              (skip-chars-forward skip-characters)
              (constrain-to-field nil orig-pos t))))
    (setq cycle-spacing--context  ;; Save for later.
          ;; Special handling for case where there was no space at all.
          (unless (= start end)
                (cons n (cons orig-pos (buffer-substring start (point))))))
    ;; If this run causes no change in buffer content, delete all spaces,
    ;; otherwise delete all excess spaces.
    (delete-region (if (and (eq mode 'fast) (zerop num) (= mid end))
               start mid) end)
        (insert (make-string num ?\s))))

     ;; Command run for the second time.
     ((not (equal orig-pos (point)))
      (delete-region (point) orig-pos))

     ;; Command run for the third time.
     (t
      (insert (cddr cycle-spacing--context))
      (goto-char (cadr cycle-spacing--context))
      (setq cycle-spacing--context nil)))))

I was able to simplify it a bit since I didn't need to handle conditionally removing newlines and leaving n number of remaining spaces.

Preamble answered 18/3, 2020 at 15:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.