11

I am trying to create a new text object in Evil. For example, the text object iw will only select subsets of strings containing hyphens. I want the new text object to match words with any non-space characters. What I got so far is:

(evil-define-text-object evil-inner-space (count &optional beg end type)
  "Select inner space."
  :extend-selection nil
  (evil-regexp-range count beg end type "[ \n]" " " t))

(define-key evil-inner-text-objects-map " " 'evil-inner-space)

This code fails to correctly match words at the end of a line. It works for words at the beginning of a line thanks to \n.

I tried many things that did not work. The trouble for me is that when something does not work, I don't know if it is due to a wrong regexp or to limitations in evil-regexp-range. For example, using \s- reports an error, which seems to be a limitation in Evil.

Lionel Henry
  • 6,652
  • 27
  • 33

3 Answers3

21

Update: evil-regexp-range was recently replaced with evil-select-paren. This works on current evil and has the same usage as the old one:

(defmacro define-and-bind-text-object (key start-regex end-regex)
  (let ((inner-name (make-symbol "inner-name"))
        (outer-name (make-symbol "outer-name")))
    `(progn
       (evil-define-text-object ,inner-name (count &optional beg end type)
         (evil-select-paren ,start-regex ,end-regex beg end type count nil))
       (evil-define-text-object ,outer-name (count &optional beg end type)
         (evil-select-paren ,start-regex ,end-regex beg end type count t))
       (define-key evil-inner-text-objects-map ,key (quote ,inner-name))
       (define-key evil-outer-text-objects-map ,key (quote ,outer-name)))))

Original Answer:

If you end up defining more than one new text object, the repetition can get annoying, especially if you want to bind both inner and outer objects. If you hit that barrier, try this:

(defmacro define-and-bind-text-object (key start-regex end-regex)
  (let ((inner-name (make-symbol "inner-name"))
        (outer-name (make-symbol "outer-name")))
    `(progn
      (evil-define-text-object ,inner-name (count &optional beg end type)
        (evil-regexp-range count beg end type ,start-regex ,end-regex t))
      (evil-define-text-object ,outer-name (count &optional beg end type)
        (evil-regexp-range count beg end type ,start-regex ,end-regex nil))
      (define-key evil-inner-text-objects-map ,key (quote ,inner-name))
      (define-key evil-outer-text-objects-map ,key (quote ,outer-name)))))

Usage:

; between dollar signs:
(define-and-bind-text-object "$" "\\$" "\\$")

; between pipe characters:
(define-and-bind-text-object "|" "|" "|")

; from regex "b" up to regex "c", bound to k (invoke with "vik" or "vak"):
(define-and-bind-text-object "k" "b" "c")

(This is more than you wanted, but I'll leave it here in case it helps someone :)

akdom
  • 32,264
  • 27
  • 73
  • 79
Gordon Gustafson
  • 40,133
  • 25
  • 115
  • 157
  • 1
    Thank you for this! I often found myself wishing I had a text object to e.g. match C++ declaration without chomping parens -- e.g. matching `_foo::bar::baz_()`, including `:` but ignoring `()`. Your solution made this very easy: `(define-and-bind-text-object "k" "[[:space:]\(\)]" "[[:space:]\(\)]")`. Muchos gracias! – Kevin Ushey Aug 31 '14 at 06:34
  • 1
    It looks like `evil-regexp-range` has been removed from the most recent versions of Evil -- I believe we now need to use `evil – Kevin Ushey Dec 01 '14 at 18:56
10

I don't think you need to create a new text object. Evil includes the symbol text object. The symbol syntax is defined by the major-mode and usually includes hyphens, numbers, underscores and other non-whitespace characters.

The symbol text object is bound to o. The definition is at evil-maps.el at Bitbucket

Yuri Steinschreiber
  • 2,648
  • 2
  • 12
  • 19
Joe
  • 3,370
  • 4
  • 33
  • 56
  • 1
    hmm no. The text object that I was looking for is indeed implemented in vim/evil, but as @PythonNut said in a comment, it is bound to `W`. And for some cases, this implementation works better than the regexp I posted. – Lionel Henry Aug 04 '14 at 08:56
  • lionel is correct, this does not exactly answer the question - but `o` is very useful, thanks JoeS! – Croad Langshan Jun 26 '16 at 19:18
1

The correct regexp is " \|.?$". The following code implements a new text object matching any nonspace character.

(evil-define-text-object evil-inner-space (count &optional beg end type)
  "Select inner space."
  :extend-selection nil
  (evil-regexp-range count beg end type "[ \n]" " \\|.?$" t))
Lionel Henry
  • 6,652
  • 27
  • 33