limp_chimp
limp_chimp

Reputation: 15163

Ctrl-backspace in emacs deletes too much

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...

Upvotes: 9

Views: 3311

Answers (7)

prosoitos
prosoitos

Reputation: 7347

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)

Upvotes: 1

Ryan Marcus
Ryan Marcus

Reputation: 1026

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)))

Upvotes: 5

user503582
user503582

Reputation: 349

@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

Upvotes: 0

Aborn Jiang
Aborn Jiang

Reputation: 1059

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)

Upvotes: 6

Christian Lynbech
Christian Lynbech

Reputation: 401

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.

Upvotes: 1

Nick
Nick

Reputation: 425

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.

Upvotes: 2

Sue D. Nymme
Sue D. Nymme

Reputation: 946

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.

Upvotes: 4

Related Questions