Ctrl-backspace in emacs deletes too much
Asked Answered
J

7

8

It's good that ctrl-backspace in emacs will delete all whitespace. However, it doesn't stop there! It seems to only stop after it's deleted at least one word. This means, for example, that using it here:

foo(bar)
    <cursor>

Results in

foo(<cursor>

Which is really dumb (IMHO)! The behavior I would want is something akin to the following:

  1. if there's whitespace preceding the cursor, delete all of the whitespace (then stop!).
  2. If there's a word preceding the cursor, delete the word.
  3. Otherwise, delete all adjacent repetitions of whatever character precedes the cursor.

This seems like a much more reasonable Ctrl-Backspace, but honestly, if I could just get (1), it would be a huge improvement. Is there a package for this, or a setting? I don't really know emacs lisp but maybe pointing me to where the relevant APIs are...

Junji answered 29/1, 2015 at 17:37 Comment(1)
Here is a link to a custom version of delete-word-or-whitespace that I use -- feel free to modify the code to suit your precise needs -- I have designed it to not place anything in the kill-ring: https://mcmap.net/q/961178/-emacs-delete-whitespaces-or-a-wordShipowner
T
6

I use following intellij-style smart backward-kill-word

(defun aborn/backward-kill-word ()
  "Customize/Smart backward-kill-word."
  (interactive)
  (let* ((cp (point))
         (backword)
         (end)
         (space-pos)
         (backword-char (if (bobp)
                            ""           ;; cursor in begin of buffer
                          (buffer-substring cp (- cp 1)))))
    (if (equal (length backword-char) (string-width backword-char))
        (progn
          (save-excursion
            (setq backword (buffer-substring (point) (progn (forward-word -1) (point)))))
          (setq ab/debug backword)
          (save-excursion
            (when (and backword          ;; when backword contains space
                       (s-contains? " " backword))
              (setq space-pos (ignore-errors (search-backward " ")))))
          (save-excursion
            (let* ((pos (ignore-errors (search-backward-regexp "\n")))
                   (substr (when pos (buffer-substring pos cp))))
              (when (or (and substr (s-blank? (s-trim substr)))
                        (s-contains? "\n" backword))
                (setq end pos))))
          (if end
              (kill-region cp end)
            (if space-pos
                (kill-region cp space-pos)
              (backward-kill-word 1))))
      (kill-region cp (- cp 1)))         ;; word is non-english word
    ))

(global-set-key  [C-backspace]
            'aborn/backward-kill-word)
Turbulence answered 11/9, 2016 at 16:13 Comment(1)
Do you have maybe aborn/forward-kill-word implemented?Calends
S
4

Ctrl-backspace runs the command backward-kill-word, which as its name implies, will always try to delete the previous word.

Perhaps you want M-\ (delete-horizontal-space), which deletes all whitespace around the cursor. That will satisfy your first requirement, anyhow.

Smedley answered 29/1, 2015 at 20:16 Comment(0)
S
4

The behavior I wanted was for C-Backspace to:

  1. If the character before the cursor is whitespace, delete all whitespace before the cursor (including newlines).
  2. If the character before the cursor is not whitespace, do the normal kill word behavior.

Here's my implementation:

(defun ryanmarcus/backward-kill-word ()
  "Remove all whitespace if the character behind the cursor is whitespace, otherwise remove a word."
  (interactive)
  (if (looking-back "[ \n]")
      ;; delete horizontal space before us and then check to see if we
      ;; are looking at a newline
      (progn (delete-horizontal-space 't)
             (while (looking-back "[ \n]")
               (backward-delete-char 1)))
    ;; otherwise, just do the normal kill word.
    (backward-kill-word 1)))
Schaaff answered 24/3, 2020 at 7:1 Comment(1)
This is an excellent solution! It's exactly what I've been looking for. Do you have an equivalent for forward deletion?Smilacaceous
B
2

I encountered the same issue a while back and overrode C-backspace with this.

;;Eclipse-like C-backspace
(defun my-kill-back ()
  (interactive)
  (if (bolp)  ; beginnning of line, just delete 1
      (backward-delete-char 1)
    (if (string-match "[^[:space:]]" (buffer-substring (point-at-bol) (point)))
        ; There's a word on the line, delete it
        (backward-kill-word 1)
      (delete-region (point-at-bol) (point))))) ; all whitespace, delete it

(global-set-key [C-backspace] 'my-kill-back)

It's a bit buggy. for instance if all the exists on the line is semicolons, backward-kill-word can delete too much. Still, gets the job done for the most part.

Biquadrate answered 30/1, 2015 at 1:13 Comment(0)
G
1

In the particular case cited, one might have used 'delete-indentation' (normally bound to M-^) which will delete all whitespace at the beginning of the line and the preceding newline, in effect moving the current line up to the line above. I find this command very helpfull, even if perhaps nto solving the exact problem stated.

Gaud answered 31/1, 2015 at 16:42 Comment(0)
L
1

This is an old question, but here is a solution that has served me very well for years. I like it because it is not greedy and I prefer having to type C-backspace several times than deleting too much at once and having to undo. The non-greedy behaviour works well in all situations.

At each call, it deletes either of:

  • all white spaces if there are white spaces before the cursor,
  • the end of line if there is one before the cursor,
  • a word if there is one before the cursor.
(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)

I don't deserve credit for it as I found it somewhere many years ago and—unfortunately—I don't recall who the author is.

And since people asked for a similar function for forward deletion in several comments, I just adapted it to work forward:

(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)
Lux answered 5/1 at 1:9 Comment(0)
S
0

@AbornJiang Thank you. I made a small change to give it behavior similar to the default way that Atom deletes words backward.

(when (and backword  ;; when backword contains space
           (s-contains? " " backword))
  (setq space-pos (1+ (ignore-errors (search-backward-regexp "[[:space:]][[:word:]]")))))

This regex selects a beginning pos which deletes any number of spaces to the beginning of the next word. This is the same behavior in MacOS, Google Docs, Sublime, and Atom.

https://gist.github.com/jclosure/d838a672ba77482f2dcc1fc4df3368de#file-emacs-el-L447

Savannasavannah answered 6/7, 2019 at 21:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.