5

I really like rx but I can't use it interactively.

Has someone already tried to extend isearch to support rx syntax?

Malabarba
  • 22,878
  • 6
  • 78
  • 163
knarf
  • 313
  • 2
  • 8
  • 3
    Sort of a duplicate: http://emacs.stackexchange.com/q/869/115 if you simply want a solution to get started writing regex interactively. Note that this does not bring `rx` into picture but you will start understanding and writing emacs regex. – Kaushal Modi Feb 09 '15 at 20:27

4 Answers4

5

With isearch you can search almost everything.

(defun isearch-rx-forward ()
  "Do incremental search forward for rx expression."
  (interactive)
  (let ((isearch-message-prefix-add "[RX]")
        (isearch-search-fun-function
         (lambda ()
           (lambda (form &optional bound noerror)
             (funcall (isearch-search-fun-default)
                      (condition-case nil
                          (rx-to-string (read form))
                        (error
                         (signal 'invalid-regexp (list "Invalid "))))
                      bound noerror)))))
    (isearch-forward t)))
politza
  • 3,316
  • 14
  • 16
  • This is great! Note that unlike the other answers, here you don't type the surrounding `(rx ...)`. – Omar Feb 10 '15 at 23:15
4

OK, here goes. This might sound convoluted, but it just puts together a few ordinary Emacs things that you probably use everyday. (If you don't use them everyday, perhaps you should. ;-))

  1. Set option enable-recursive-minibuffers to non-nil (at least temporarily). E.g.:

    M-x set-variable RET enable-recursive-minibuffers RET t RET
    
  2. Start regexp search: C-M-s.

  3. Edit the (as yet empty) search string: M-e. This puts you in a normal editing mode, in the minibuffer.

  4. Evaluate an rx sexp in a recursive minibuffer for eval-expression.

    C-u M-: (rx (and line-start (1+ (in "a-z"))) RET
    
  5. You now have the regexp you want, "^[a-z]+", in the minibuffer, but it is surrounded by double-quote chars. So remove those chars. You now see this in the minibuffer:

    Regexp I-search: ^[a-z]+
    
  6. End editing and resume searching, by hitting C-s.

The bottom line is really that it is not true that you cannot use something interactively when in Isearch. You just need to (a) use M-e to get into editing mode, and then (b) use M-: to interactively evaluate a Lisp sexp.

Drew
  • 75,699
  • 9
  • 109
  • 225
  • I guess that's a solution. I was looking for an elisp replacement or something more integrated. – knarf Feb 10 '15 at 10:47
  • The point is that if you want Isearch to evaluate a sexp, instead of just search for it as text, then you need to hit some key to tell it to do that. `M-:` is such a key - and it works generally for Emacs. The rest are details. Even if you define a command & key for `isearch-mode-map` to do what `M-:` plus the rest presented here does (which is not difficult - maybe I'll do that for [**Isearch+**](http://www.emacswiki.org/emacs/IsearchPlus)), you still *need to hit a key* to say: **evaluate this and use the result as the search string**. – Drew Feb 10 '15 at 14:14
  • I'd use a variant of this: instead of recursive minibuffers, write a function that replaces the previous s-expression with its value. I find that function very useful. – Omar Feb 10 '15 at 16:50
  • @OmarAntolín-Camarena: Sure, that's the same idea actually. *You still need to use a key*, to let Isearch know to evaluate the preceding search-pattern text as a Lisp sexp and replace it by its value. – Drew Feb 10 '15 at 17:02
  • Re: "Sure, that's the same idea actually". I wasn't suggesting my idea was any different; maybe I should have said "very minor variation" instead of "variant"? – Omar Feb 10 '15 at 17:31
  • @OmarAntolín-Camarena No, it's a *good* suggestion. I didn't mean to belittle it, at all. – Drew Feb 10 '15 at 18:08
4

You talk about extending isearch, which suggests you also want to search incrementally, not just interactively (interactive only meaning that you call the function from a key binding or from M-x). I'm not sure that incremental is such a good fit for rx: unless you enter closing parenthesis at the same time as opening ones, the vast majority of the time a partially entered rx expression will be ill-formed (unlike normal regex syntax, which is often syntactically valid at several intermediate points).

Well, to answer the question for merely interactive searching: just make a wrapper for re-search-forward that prompts for an expression, evaluates it and searches for the resulting regexp:

(defun eval-re-search-forward (re)
  (interactive "Xrx: ")
  (re-search-forward re))

(The first "X" in the argument to interactive specifies that when used interactively the function should prompt for a Lisp expression, and evaluate it before binding it to the argument re.)

The prompt says "rx: ", because that's the intended usage, but you can enter any Lisp expression that evaluates to a string.


Also, @Drew gave you a reasonable suggestion, but I want to offer a very minor variation: instead of recursive minibuffers, write a function that replaces an s-expression by its value (without the quotes if said value is a string). I use this function all the time:

(defun eval-replace-last-sexp ()
  (interactive)
  (let ((sexp (preceding-sexp)))
    (backward-kill-sexp)
    (let ((result (eval sexp)))
      (when result
        (insert (format "%s" result))))))

(By choosing "%s" as the format, if result is a string it gets inserted without the quotes. I find this useful because I typically use this function to generate little parts of my files. Also, I test the result so if it's nil I don't wind up inserting the word "nil" in the buffer, as that's almost never what I want.)

With this function bound to a key of your choosing, you can run M-x re-search-forward, type the rx expression and hit the key you choose for eval-replace-last-sexp. If you want to further edit the rx form, you can undo the eval-replace-last-sexp. If you really want to use this incrementally, i.e., with isearch, just use M-e first, as @Drew said.

Omar
  • 4,732
  • 1
  • 17
  • 32
1

If you use Isearch+ then you can just use M-: to do what you request. It prompts you for a Lisp sexp, evaluates it, and appends the value to the search string. You can use an rx expression as the sexp.

For example:

C-M-s M-: (rx (and line-start (1+ (in "("))))

searches using the result of that ‘rx’ sexp, which is ^(+.

With Isearch+ you can also use C-u M-: after M-e, to insert the sexp value into the minibuffer, where you are editing the search string.

(M-e lets you edit the search string, and with Isearch+ it lets you use a recursive minibuffer, so you can use M-:, which itself reads from a minibuffer. In the minibuffer, M-: is bound to its usual command, and for that, C-u inserts the value returned at point - which in this case is at point in the parent minibuffer, from M-e.)

So use M-e followed by C-u M-: when you do not want to simply append the sexp value to the search string, but instead you want to do some editing of it or the rest of the search string.

Drew
  • 75,699
  • 9
  • 109
  • 225