12

I have the following

(defun isearch-del-fail-or-char ()
  "Delete failed isearch text, or if there is none, a single character."
  (interactive)
  (if (isearch-fail-pos)
      (delete-region (isearch-fail-pos) (point))
    (isearch-del-char)))

(define-key isearch-mode-map (kbd "DEL") 'isearch-del-fail-or-char)

The purpose of the code is to make delete in isearch delete the entire failed string (or if there is no failed string just a single character).

However, delete-region is deleting text from the buffer isearch is searching in, not the isearch buffer itself.

What is the proper way to do this? Critique on the rest of my emacs lisp style is also welcome :)

Drew
  • 75,699
  • 9
  • 109
  • 225
asmeurer
  • 1,552
  • 12
  • 30

1 Answers1

13

Ah yes. Isearch reads the keys you type, looks them up in isearch-mode-map, and invokes them in the current buffer.

Isearch does not, in spite of appearances, use the minibuffer. It uses the echo area. That is, what you see there is actually output messages, including echoes of the characters you type.

This should do what you ask:

(defun mydelete ()
  "Delete the failed portion of the search string, or the last char if successful."
  (interactive)
  (with-isearch-suspended
      (setq isearch-new-string
            (substring
             isearch-string 0 (or (isearch-fail-pos) (1- (length isearch-string))))
            isearch-new-message
            (mapconcat 'isearch-text-char-description isearch-new-string ""))))

(define-key isearch-mode-map (kbd "DEL") 'mydelete)

(BTW, your question says the delete character, but you wrote DEL, which is the backspace character.)


As @Malabarba points out in a comment, when you set the new search string to "" (empty string), with-isearch-suspended resumes by searching for the last search string, instead starting with an empty search string.

This is a "feature" of with-isearch-suspended, in general. But because you sometimes might really want to empty the search string for resumption, in the version of with-isearch-suspended in isearch+.el I've added variable isearchp-if-empty-prefer-resuming-with-last, to control this. If you bind that to nil and you set isearch-new-string to "" then search resumes with an empty search string.

So with Isearch+ you can do what you want with this definition:

(defun mydelete ()
  "Delete the failed portion of the search string, or the last char if successful."
  (interactive)
  (let ((isearchp-if-empty-prefer-resuming-with-last  nil))
    (with-isearch-suspended
        (setq isearch-new-string
              (substring
               isearch-string 0 (or (isearch-fail-pos) (1- (length isearch-string))))
              isearch-new-message
              (mapconcat 'isearch-text-char-description isearch-new-string "")))))

I notice too now that Emacs 24.4 introduced a regression, which I've filed Emacs bug #20466 for, which means that binding DEL in isearch-mode-map is not sufficient. They added a separate binding for <backspace>, in addition to one for DEL. That means that <backspace> no longer gets translated to DEL, for Isearch (but it does still get so translated for Emacs generally).

So if you want the Backspace key to do what you asked in Emacs 24.4 or later then you cannot just bind DEL to mydelete. You need to bind <backspace> to mydelete. Dumb, AFAICT, mais on n'arrete pas le progres...


I've added a similar command to Isearch+ and bound it to C-M-l (the same key used to remove a completion mismatch in Icicles).

Be aware too that C-g in Isearch will also, when there is a mismatch, remove the mismatched text. (But C-g also has an effect when search is successful.)


I should have mentioned that Isearch+ has also had an optional behavior along similar lines for quite a while now. M-k during Isearch toggles among 3 behaviors, which are controlled by the value of option isearchp-drop-mismatch:

  • replace-last - Your current input replaces the last mismatched text. You can always see your last input, even if it is a mismatch. And it is available for editing using M-e.

  • nil - Your current input is appended, even if the previous input has a mismatched portion.

  • anything else - Your current input is ignored (removed) if it causes a mismatch. The search string always has successful matches.

Drew
  • 75,699
  • 9
  • 109
  • 225
  • BTW, I've thought about adding a similar command to [**Isearch+**](http://www.emacswiki.org/emacs/IsearchPlus) ever since I added highlighting of the failed portion (years ago, before I added it to GNU Emacs), but I never got around to it. I've [done that now](http://www.emacswiki.org/emacs/IsearchPlus#isearchp-remove-failed-part). (I didn't include deletion of a character, however. The command I added is a no-op if there is no failed portion.) – Drew Mar 27 '15 at 21:54
  • I'm on OS X. I meant backwards delete. – asmeurer Mar 30 '15 at 18:26
  • The isearch+ replace-last behavior looks pretty nice. Is any way to make it not ring the bell every time the search fails, though? – asmeurer Mar 30 '15 at 23:23
  • Also it's very janky with `replace-last`, because it prints "I-search:" and "Failing I-search" immediately after one another. – asmeurer Mar 30 '15 at 23:27
  • Now that I play with it, it looks like the best behavior is this code *and* `replace-last`. That way, I can see the failing string, but pressing delete (or backspace, whatever) to delete it, as my reflexes are wont to do, still works. – asmeurer Mar 30 '15 at 23:30
  • 2
    I uploaded a new version of [`isearch+.el`](http://www.emacswiki.org/emacs/download/isearch%2b.el). It (a) fixes automatic mismatch removal so that wraparound works OK. And it adds an option, **`isearchp-ring-bell-function`**, which you can use to suppress the bell during search. – Drew Mar 31 '15 at 16:31
  • I don't see the `isearchp-ring-bell-function` in the source you linked to. – asmeurer Mar 31 '15 at 19:00
  • Well, it's there. Perhaps you need to clear your browser cache or something. – Drew Mar 31 '15 at 23:57
  • Hi Drew, I have a bug in your `mydelete` function. If you search for a single character, and then delete this character, the isearch prompt will get populated with the previous search. – Malabarba Apr 27 '15 at 09:11
  • 3
    Malabarba, interesting. I took a shot at the bug and ended up with [this](https://gist.github.com/johnmastro/508fb22a2b4e1ce754e0). Mostly cargo-culted from `isearch-del-char` and Drew's answer but it seems to work as intended. The `with-isearch-suspended` macro was the culprit. – jbm Apr 30 '15 at 02:26
  • @Malabarba & jbm: See my edit about improving `with-isearch-suspended` in this regard for [Isearch+](http://www.emacswiki.org/emacs/IsearchPlus). – Drew Apr 30 '15 at 05:39
  • Coming back to this, the version with `isearchp-if-empty-prefer-resuming-with-last` doesn't seem to work, but the function in @jbm's gist does. – asmeurer Nov 18 '15 at 22:30
  • @asmeurer: Sorry, but "*doesn't seem to work*" means nothing to me. And I don't even know just what you mean by "*the version with...*" (what value of that variable, etc.). Please give a recipe, specifying what you see and what you expected to see instead. You can send this to me by email, if you like: `M-x customize-group isearch-plus`, then click link `Send Bug Report`. Thx. – Drew Nov 18 '15 at 23:04
  • @Drew I'm referring to the code you wrote in this answer. – asmeurer Nov 18 '15 at 23:24
  • @asmeurer: *Recipe, please.* It works for me. (Note that you might need to use `(kbd "")` instead of `(kbd "DEL")`.) But both jbm's gist and the code above work for me. Please be specific about what you are doing, and why you think it "*doesn't seem to work*". – Drew Nov 19 '15 at 16:23
  • @Drew, this is the code I have in my .emacs https://gist.github.com/asmeurer/e82697ba2f7c16f47e87. I should look *very* familiar. When I do an isearch with it and delete the last character, it goes back to the previous search. When I replace `mydelete` with `isearch-delete-something` from @jbm's gist and do the same thing, it does what I want (doesn't revert to a previous search). – asmeurer Nov 19 '15 at 17:11
  • Sorry, I wasn't thinking. I think I understand now, and I think it is fixed. Please try the latest [`isearch+.el`](http://www.emacswiki.org/emacs/download/isearch%2b.el). `C-` is bound to a command that I think does what you want. You might want to bind that command to `` instead. Let me know what you think. – Drew Nov 24 '15 at 03:59
  • @Drew Hi, Drew! Would there be any way to behave like the default isearch (jump back to previous states) but if deleting a failed input, delete it like in you answer? By that I mean if I go start of the scratch buffer with default `initial-scratch-message` and search for "isiqqq" then when I press DEL the first time delete the failed "qqq" part and if press DEL next time behave like usual (jump back from "visit" to "This")? – clemera May 07 '20 at 17:39
  • @Drew I figured it out the solution was simple: `(while (progn (isearch-delete-char) (isearch-fail-pos)))` – clemera May 07 '20 at 20:03