52

I use Emacs + evil mode.

When I position the cursor on e (in normal state) in a chunk of text such as abc_def_ghi, ciw (change inner word) changes only the def part of the string, while Vim would change the entire abc_def_ghi.

How can I force evil to treat the underscore as Vim does (ie, as part of the word)?

8 Answers 8

43

The simplest workaround: use cio (see here), or use ciW to select a whitespace-delimited word.

The bigger issue has to do with the value of the _ character in the syntax table. The issue is that _ is, by default, a symbol constituent in the syntax table, and you want to treat it as a word constituent.

If you're using emacs 24.4, you could try enabling superword-mode. I haven't tried it myself, so your mileage may vary.

An alternative is simply to modify the syntax table yourself and tell Emacs you want it to treat the _ character as a word constituent, like so:

(modify-syntax-entry ?_ "w")

After you do that, ciw works as you want it to, such that it will select all of abc_def_ghi rather than just def.

Doing it this way, however, may be overkill, especially if you only want the _ to count as part of the word for the text object. Instead, you can advise evil-inner-word as follows:

(defadvice evil-inner-word (around underscore-as-word activate)
  (let ((table (copy-syntax-table (syntax-table))))
    (modify-syntax-entry ?_ "w" table)
    (with-syntax-table table
      ad-do-it)))

Now, _ is still a symbol constituent for everything except for the inner-word text object, so ciw will do precisely what you want without touching the functionality of anything else.

You can read more about modifying syntax tables on the EmacsWiki node.

5
  • This works like a charm. To alter all the movements (w, aw, etc.), you can do the same with the following functions too: evil-forward-word-begin,evil-forward-word-end,evil-backward-word-begin,evil-backward-word-end,evil-a-word,evil-inner-word. Note: you need to change underscore-as-word into different, unique (arbitrary) values.
    – sitaktif
    Commented Oct 18, 2016 at 6:33
  • (modify-syntax-entry ?_ "w") didn't work for me for some reason. Doing it interactively didn't work either. Any idea why or how I can debug this? I'm on Spacemacs in case that's relevant. Commented Feb 17, 2018 at 13:23
  • 1
    This explains my problem. Apparently you have to refer to the target syntax table explictly. In my case I used (modify-syntax-entry ?_ "w" rust-mode-syntax-table), then it worked. Commented Feb 17, 2018 at 13:28
  • What are the unintended effects of modifying the syntax entry?
    – luntain
    Commented Feb 28, 2018 at 21:40
  • FTR: superword-mode doesn't affect movement in evil-mode. So it's not a constituent to modifying syntax tables.
    – Hi-Angel
    Commented Dec 24, 2018 at 13:10
48

The following snippet will make Evil treat an Emacs symbol as a word.

(with-eval-after-load 'evil
    (defalias #'forward-evil-word #'forward-evil-symbol)
    ;; make evil-search-word look for symbol rather than word boundaries
    (setq-default evil-symbol-word-search t))

This has the advantage that it changes depending on the language:

foo-bar

is one symbol in lisp-mode but two symbols (separated by -) in c-mode.

0
17

The spacemacs FAQ offers the following language-specific solution:

;; For python
(add-hook 'python-mode-hook #'(lambda () (modify-syntax-entry ?_ "w")))
;; For ruby
(add-hook 'ruby-mode-hook #'(lambda () (modify-syntax-entry ?_ "w")))
;; For Javascript
(add-hook 'js2-mode-hook #'(lambda () (modify-syntax-entry ?_ "w")))

It also works in regular emacs. With evil on, it works with all word-related commands (w, e, *, #, etc)

More generally, this will do it for all major modes:

(add-hook 'after-change-major-mode-hook
          (lambda ()
            (modify-syntax-entry ?_ "w")))
1
  • 2
    Is using 'prog-mode-hook an option to cover every language? Commented May 11, 2018 at 7:43
5

The evil FAQ says if you want the underscore to be recognised as a word character:

(modify-syntax-entry ?_ "w")

Alternatively, you can change the w shortcut to move by symbols instead of by words:

(defalias 'forward-evil-word 'forward-evil-symbol)
3

Emacs 24 introduced superword-mode, a minor mode that treats underscores as part of words. evil-mode respects this setting.

Here is the documentation:

(superword-mode &optional ARG)

Toggle superword movement and editing (Superword mode). With a prefix argument ARG, enable Superword mode if ARG is positive, and disable it otherwise. If called from Lisp, enable the mode if ARG is omitted or nil.

Superword mode is a buffer-local minor mode. Enabling it changes the definition of words such that symbols characters are treated as parts of words: e.g., in superword-mode, "this_is_a_symbol" counts as one word.

1
  • 1
    If you are using evil-mode, unfortunately, there is a bug when superword-mode is enable. It causes some erratic behavior when evil-forward-word-begin is called: github.com/emacs-evil/evil/issues/721
    – modulitos
    Commented Aug 2, 2019 at 21:53
2

Doesn't quite answer the OP's question but thought I'd put this here for completeness. I also like evil's default behavior (esp. for python and lisp-dialects where I might only want to change part of a name) UNLESS I'm searching for a word under the cursor, i.e. with "*" or "#", in which case I'd like to search for the symbol under the cursor. You can easily modify evil to adhere to this by setting evil-symbol-word-search, i.e.:

(setq evil-symbol-word-search t)
2
  • This is the right way to do it. In addition to this setting, I also like to have W (capital rather than lowercase) refer to symbols rather than WORDs, for which you can use this: (defalias #'forward-evil-WORD #'forward-evil-symbol). With these two settings, your # and * search for symbols, w moves by word stopping at underscores, hyphens and spaces, while W moves by symbol, stopping at meaningful points for the code.
    – mindthief
    Commented Dec 2, 2020 at 0:31
  • But this makes /foo/bar to also be a word in many modes 😩
    – Hi-Angel
    Commented Mar 27, 2023 at 11:37
1

Not being satisfied with side-effects of evil-symbol-word-search or having to hardcode (modify-syntax-entry) for every mode, I found that apparently minor mode hooks are run after major mode syntax table is created. So you can add a hook to modify syntax table to evil-mode hook and it should just work.

(defun myhook-evil-mode ()
  ;; I want underscore be part of word syntax table, but not in regexp-replace buffer
  ;; where I'm more comfortable having more verbose navigation with underscore not
  ;; being a part of a word. To achieve this I check if current mode has a syntax
  ;; table different from the global one. The `(eq)' is a lightweight test of whether
  ;; the args point to the same object.
  (unless (eq (standard-syntax-table) (syntax-table))
    ;; make underscore part of a word
    (modify-syntax-entry ?_ "w")))
(add-hook 'evil-local-mode-hook 'myhook-evil-mode)

Maybe not optimal as it's executed every time a new buffer is created, but shouldn't matter in this case. What does though is that this allowed me to get rid of a humongous 49 lines paragraph that was modifying syntax table of every possible mode I got to work with.

0

I actually quite like this behavior since I might want to just edit a part of the word and keep the other parts intact. If I want to go over the whole word I can always use E, W or B, the uppercase variants. This gives me more fine-grained control.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.