Emacs: delete whitespaces or a word
Asked Answered
Y

5

11

How can I configure emacs to work in the same way as other modern editors where pressing Alt+D or Alt+Backspace deletes either adjacent whitespaces or a single word? By default, emacs always deletes a word.

Younker answered 30/7, 2013 at 22:13 Comment(4)
I'm trying to figure out what modern editor you had in mind when you mentioned these hotkeys... I haven't really encounter the behaviour you describe. Most things I just tried either do nothing, or something arbitrary unrelated, as deleting the whole line or navigating the history of document opening. So, I don't really know what kind of whitespace deletion you had in mind. Is it the kind like M-\ does? Or should it delete before / after the point only?Hsu
Also: emacswiki.org/emacs/DeletingWhitespace here's a large collection of user-submitted code to do all sorts of whitespace deletion.Hsu
@wvxvw sorry it should be Alt key. editors like eclipse, visual studio and sublime text.Younker
@woodings, could you post a use case with some text and cursor postion? Also, Alt-D kills the word, not just deletes it (you can yank it later).Ai
H
14

Trough some time of using Emacs I figured that even though I can alter the basic functionality, it usually doesn't pay off much in terms of efficiency. In fact, after I did it several times, I came to regret it and undid it. This is not true all of the time, some keybindings are really uncomfortable or rarely useful, but I don't think this is the case with how kill word works. In fact, I just now realized that: I did try the keybinding in Eclipse, but I've been using it with Emacs-style kebindings since forever...

Anyways, as I just said, before you are "fixing" that functionality, make sure it is really broken :) I never find myself needing the kind of function you describe, and maybe here's why:

  1. M-SPC reduces the space between words to just one space. This is what I would've used if the point was between the words and I wanted to delete the extra space separating the words.

  2. M-\ removes all horizontal space. This will join two words separated by space.

  3. If what you are trying to achieve is some kind of "sparse" formatting, as in:


int foo          = 42;
unsigned int bar = 43;

then there's M-xalign-regexp to do that.

  1. I just never happen to have a) long consequent runs of whitepsace, unless it is the indentation, and in the case it is the indentation, TAB usually handles it better. b) even if there are long consequent runs of whitespace, I so rarely move the point by one character at a time, so it's hard to think of a situation where I'd find the point surrounded by several whitespaces. Things like Artist mode, or Dot diagrams come to mind, but it doesn't happen during code editing.

  2. Finally, if you are trying to, well, let's say just edit an arbitrary text file and you want to add or remove horizontal space between words... Again, there's M-xalign-regexp to do that, or you could use commands that operate on rectangles, if those are several lines at the time. Well, Emacs will even recognize the ad hoc tabs and will try to align the text such as to match the last line before the point, when you hit TAB.

Finally, if for some reason I cannot fathom :) I really needed to do exactly what you describe, then I'd do it like so: kM-\BACKSPACE (it can be any other key instead of "k" - it is just right under your finger, so it's fast to type :) Or, if I'm lazy to think about it: M-SPCM-fM-bC-w - maybe sounds like a lot, but these are the commands you would be using all of the time anyway, so it doesn't hinder you in terms of speed.

Hsu answered 31/7, 2013 at 7:22 Comment(0)
C
11
(defvar movement-syntax-table
  (let ((st (make-syntax-table)))
    ;; ` default = punctuation
    ;; ' default = punctuation
    ;; , default = punctuation
    ;; ; default = punctuation
    (modify-syntax-entry ?{ "." st)  ;; { = punctuation
    (modify-syntax-entry ?} "." st)  ;; } = punctuation
    (modify-syntax-entry ?\" "." st) ;; " = punctuation
    (modify-syntax-entry ?\\ "_" st) ;; \ = symbol
    (modify-syntax-entry ?\$ "_" st) ;; $ = symbol
    (modify-syntax-entry ?\% "_" st) ;; % = symbol
    st)
  "Syntax table used while executing custom movement functions.")

(defun delete-word-or-whitespace (&optional arg)
"https://mcmap.net/q/961178/-emacs-delete-whitespaces-or-a-word"
(interactive "P")
  (with-syntax-table movement-syntax-table
    (let* (
        beg
        end
        (word-regexp "\\sw")
        (punctuation-regexp "\\s.")
        (symbol-regexp "\\s_\\|\\s(\\|\\s)"))
      (cond
        ;; Condition # 1
        ;; right of cursor = word or punctuation or symbol
        ((or
            (save-excursion (< 0 (skip-syntax-forward "w")))
            (save-excursion (< 0 (skip-syntax-forward ".")))
            (save-excursion (< 0 (skip-syntax-forward "_()"))))
          ;; Condition #1 -- Step 1 of 2
          (cond
            ;; right of cursor = word
            ((save-excursion (< 0 (skip-syntax-forward "w")))
              (skip-syntax-forward "w")
              (setq end (point))
              (while (looking-back word-regexp)
                (backward-char))
              (setq beg (point))
              (delete-region beg end))
            ;; right of cursor = punctuation
            ((save-excursion (< 0 (skip-syntax-forward ".")))
              (skip-syntax-forward ".")
              (setq end (point))
              (while (looking-back punctuation-regexp)
                (backward-char))
              (setq beg (point))
              (delete-region beg end))
            ;; right of cursor = symbol
            ((save-excursion (< 0 (skip-syntax-forward "_()")))
              (skip-syntax-forward "_()")
              (setq end (point))
              (while (looking-back symbol-regexp)
                (backward-char))
              (setq beg (point))
              (delete-region beg end)))
          ;; Condition #1 -- Step 2 of 2
          (cond
            ;; right of cursor = whitespace
            ;; left of cursor = not word / not symbol / not punctuation = whitespace or bol
            ((and
                (save-excursion (< 0 (skip-chars-forward "\s\t")))
                (not (save-excursion (> 0 (skip-syntax-backward "w"))))
                (not (save-excursion (> 0 (skip-syntax-backward "."))))
                (not (save-excursion (> 0 (skip-syntax-backward "_()")))))
              (setq beg (point))
              (skip-chars-forward "\s\t")
              (setq end (point))
              (delete-region beg end))
            ;; right of cursor = whitespace
            ;; left of cursor = word or symbol or punctuation
            ((and
                (save-excursion (< 0 (skip-chars-forward "\s\t")))
                (or
                  (save-excursion (> 0 (skip-syntax-backward "w")))
                  (save-excursion (> 0 (skip-syntax-backward ".")))
                  (save-excursion (> 0 (skip-syntax-backward "_()")))))
              (fixup-whitespace))))
        ;; Condition # 2
        ;; right of cursor = whitespace
        ;; left of cursor = bol | left of cursor = whitespace | right of cursor = whitespace + eol
        ((and 
            (save-excursion (< 0 (skip-chars-forward "\s\t")))
            (or
              (bolp)
              (save-excursion (> 0 (skip-chars-backward "\s\t")))
              (save-excursion (< 0 (skip-chars-forward "\s\t")) (eolp))))
          (setq beg (point))
          (skip-chars-forward "\s\t")
          (setq end (point))
          (delete-region beg end))
        ;; Condition # 3
        ;; right of cursor = whitespace or eol
        ;; left of cursor = word or symbol or punctuation
        ;; not bol + word or symbol or punctuation
        ;; not bol + whitespace + word or symbol or punctuation
        ((and 
            (or (save-excursion (< 0 (skip-chars-forward "\s\t"))) (eolp))
            (or
              (save-excursion (> 0 (skip-syntax-backward "w")))
              (save-excursion (> 0 (skip-syntax-backward ".")))
              (save-excursion (> 0 (skip-syntax-backward "_()"))))
            (not (save-excursion (> 0 (skip-syntax-backward "w")) (bolp)))
            (not (save-excursion (> 0 (skip-syntax-backward ".")) (bolp)))
            (not (save-excursion (> 0 (skip-syntax-backward "_()")) (bolp)))
            (not (save-excursion (and (> 0 (skip-syntax-backward "w")) (> 0 (skip-chars-backward "\s\t")) (bolp))))
            (not (save-excursion (and (> 0 (skip-syntax-backward ".")) (> 0 (skip-chars-backward "\s\t")) (bolp))))
            (not (save-excursion (and (> 0 (skip-syntax-backward "_()")) (> 0 (skip-chars-backward "\s\t")) (bolp)))))
          (setq end (point))
          (cond
            ((save-excursion (> 0 (skip-syntax-backward "w")))
              (while (looking-back word-regexp)
                (backward-char)))
            ((save-excursion (> 0 (skip-syntax-backward ".")))
              (while (looking-back punctuation-regexp)
                (backward-char)))
            ((save-excursion (> 0 (skip-syntax-backward "_()")))
              (while (looking-back symbol-regexp)
                (backward-char))))
          (setq beg (point))
          (when (save-excursion (> 0 (skip-chars-backward "\s\t")))
            (skip-chars-backward "\s\t")
            (setq beg (point)))
          (delete-region beg end)
          (skip-chars-forward "\s\t"))
        ;; Condition # 4
        ;; not bol = eol
        ;; left of cursor = bol + word or symbol or punctuation | bol + whitespace + word or symbol or punctuation
        ((and
            (not (and (bolp) (eolp)))
            (or
              (save-excursion (> 0 (skip-syntax-backward "w")) (bolp))
              (save-excursion (> 0 (skip-syntax-backward ".")) (bolp))
              (save-excursion (> 0 (skip-syntax-backward "_()")) (bolp))
              (save-excursion (and (> 0 (skip-syntax-backward "w")) (> 0 (skip-chars-backward "\s\t")) (bolp)))
              (save-excursion (and (> 0 (skip-syntax-backward ".")) (> 0 (skip-chars-backward "\s\t")) (bolp)))
              (save-excursion (and (> 0 (skip-syntax-backward "_()")) (> 0 (skip-chars-backward "\s\t")) (bolp)))))
          (skip-chars-forward "\s\t")
          (setq end (point))
          (setq beg (point-at-bol))
          (delete-region beg end))
        ;; Condition # 5
        ;; point = eol
        ;; not an empty line
        ;; whitespace to the left of eol
        ((and
            (not (and (bolp) (eolp)))
            (eolp)
            (save-excursion (> 0 (skip-chars-backward "\s\t"))))
          (setq end (point))
          (skip-chars-backward "\s\t")
          (setq beg (point))
          (delete-region beg end))
        ;; Condition # 6
        ;; point = not eob
        ;; point = bolp and eolp
        ;; universal argument = C-u = '(4)
        ((and
            (not (eobp))
            (and (bolp) (eolp))
            (equal arg '(4)))
          (delete-forward-char 1))) )))
Cy answered 30/7, 2013 at 22:14 Comment(8)
I like the function in general, but I found that it wasn't quite 'universal'. I.e 1) Once you delete to the beggining of a line, it doesn't wrap around to delete the previous line, it just 'stops'Cassation
and if you have a line with trailing white spaces, it doesn't start deleting characters at all?Cassation
@Leo Ufimtsev -- I added a condition to deal with whitespace at the end of line. When point is at end of line and whitespace is to the left, then delete all whitespace to the left. This is the only situation where whitespace to the left is deleted. Thank you for helping me to improve this function. I would like to give some thought as to whether the scope of the function should be increased to delete lines forwards or backwards after the current paragraph has been deleted. The reason I hesitate is that many users, including myself, sometimes get overzealous with repeating the function.Cy
perfect. Life is now more sweet. I'd be down for a forward version also, e.g with optional 'forward' paramater.Cassation
btw, if you post a link to this answer the emacs question: emacs.stackexchange.com/questions/10644/… I'll happily accept it.Cassation
@LeoUfimtsev -- I added an additional condition for a situation when point is at an empty line, but not at the end of the buffer. If there is one universal argument in that situation -- i.e., C-u -- then the function delete-forward-character fires one time. At this time, I prefer to keep the forward deletion of new lines to require an extra step -- e.g., one C-u. For those functions that I am passionate about, I like to continuously improve upon them by editing the threads. I strive to limit the editing to just one thread -- even if that prevents me from answering a related question.Cy
Nice nice. You should make a package out of that X-DCassation
Awesome function. Best control-backspace implementation so far. The only thing that bothers me though is that it does not remove blank lines up until the next word. Could you give a hint how to achieve this?Pollie
R
6

This has most likely been solved before, but instead of looking for code, we can write our own. So much fun!

This is how I would do it, hope it helps.

(defun kill-whitespace-or-word ()
  (interactive)
  (if (looking-at "[ \t\n]")
      (let ((p (point)))
        (re-search-forward "[^ \t\n]" nil :no-error)
        (backward-char)
        (kill-region p (point)))
    (kill-word 1)))

Then bind it to a key:

(global-set-key (kbd "M-d") 'kill-whitespace-or-word)
Reconsider answered 30/7, 2013 at 23:37 Comment(1)
I'm looking for a kill-whitespace-or-word-backward functionality mostly. Delete words backwards but stop on \n mostly. I feel like this code could be adapted for that, but I'm too clueless at elisp to figure it out :/Crore
S
1

If you are using a CC-Mode based buffer, you are probably looking for the Hungry Delete Mode minor mode.

Try C-c DEL and C-c DELETE in several places to get a feel for the difference.

If you like the way it works, you can toggle hungry deletion to work for the standard keys by doing M-x c-toggle-hungry-state or just rebind the hungry deletion functions to your preferred binding.

If you still think you need to piggyback one key to do forward kill word or whitespace, then you can do something similar to c-hungry-delete-forward, or just temporarily rebind c-delete-function and call it.

(defun c-hungry-delete-forward-word ()
  "Delete the following word or all following whitespace
up to the next non-whitespace character.
See also \\[c-hungry-delete-backwards]."
  (interactive)
  (let ((c-delete-function (function kill-word)))
    (c-hungry-delete-forward)))

Check out the Info page (ccmode) Hungry WS Deletion for more.

Storyteller answered 31/7, 2013 at 0:4 Comment(0)
C
0

Here is a solution to delete white spaces, or a special character, or a word, depending on what is behind (backward function) or in front of (forward function) the cursor.

Unlike some solutions given, it doesn't break at the beginning or end of a line.

Backward deletion:

(defun my-backward-kill-spaces-or-char-or-word ()
  (interactive)
  (cond
   ((looking-back (rx (char word)) 1)
    (backward-kill-word 1))
   ((looking-back (rx (char blank)) 1)
    (delete-horizontal-space t))
   (t
    (backward-delete-char 1))))

(global-set-key (kbd "<C-backspace>") 'my-backward-kill-char-or-word)

Forward deletion:

(defun my-forward-kill-spaces-or-char-or-word ()
  (interactive)
  (cond
   ((looking-at (rx (char word)) 1)
    (kill-word 1))
   ((looking-at (rx (char blank)) 1)
    (delete-horizontal-space))
   (t
    (delete-forward-char 1))))

(global-set-key (kbd "M-d") 'my-forward-kill-char-or-word)
Colony answered 5/1 at 1:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.