Vim matchit.vim by Benji Fisher is ported into Emacs.
Press “%” to jump between matched tags (“<div>” and “</div>” in html, etc).
Built-in supported languages and documents:
- HTML
- Python
- Java
- C++/C
- Javascript
- Typescript
- React JSX (rjsx-mode, react-mode)
- JSON
- OCaml
- Perl
- Yaml
- Latex
- MATLAB/Octave
- CMake
- Markdown
- Org (matching tags of other languages embedded in org file is also supported!)
- Ruby
- Elixir
- Bash
- Lua
- PHP
- Fortran
- SQL
- Laravel Blade Templating
- Vim script
- Verilog
- Diff/Patch
- Shell/Terminal bundled in Emacs
- Emacs email (message-mode)
- VCS (Git/Subversion/Perforce …) merge conflicts
If EVIL is installed, this package use EVIL as its vi layer.
If EVIL is not installed, most commands still work. So EVIL is only optional dependency.
Tested on Emacs 27, 28, 29
- Support any modern languages (html/java/c/c++/python/latex/javascript …)
- Powerful. If you mix jsp, freemarker, html, jquery template or any weird syntax into one file, it still works!
- Extendable. Write a plugin for it takes only 5 minutes
It’s already uploaded to http://melpa.org/.
Insert below code into ~/.emacs
to setup key bindings:
(global-evil-matchit-mode 1)
Alternatively, you can enable evil-matchit-mode
along a major mode by adding turn-on-evil-matchit-mode
to the mode hook.
No setup is required.
You can press “%” or M-x evilmi-jump-items
to jump between tag pair in normal mode or visual mode (you press “v” to switch to visual mode).
Please note evil-matchit is smart enough to detect the tag automatically.
Tag pair could be open/closed html tag, or character pair like “{}” “[]” “()”, or the single/double quote(s) at the two ends of the string.
Inner/outer text object “%” is also created. It roughly equals the region when you press “%” from evil-matchit.
Press “va%” to select line(s) wrapped by tags including tags themselves. M-x evilmi-select-items
does the same thing.
Press “da%” to delete line(s) wrapped by tags including tags themselves. M-x evilmi-delete-items
does the same thing.
All commands support numeric argument like “3%”, “5va%” or “9da%”
Pressing “3%” jumps to a line 3 percentage down the file. It’s the default behavior in original evil-mode
. You can (setq evilmi-may-jump-by-percentage nil)
to turn off this feature. Then “3%” will jump 3 times.
If you need visually select lines, you could use evilmi-select-items
.
This is actually an advantage of Emacs, you can tweak the select region without go into visual state at all.
Use evilmi-jump-items-native
to replace evilmi-jump-items
. Evil text object “%” is de-activated.
But all the other commands like evilmi-delete-items
and evilmi-select-items
still work.
It’s reported some mode is not compatible with this package.
You can use evilmi-jump-hook
to turn off the mode before jumping to the matched tag.
Then turn on it after the jump using the same hook.
Here is an example to toggle global-tree-sitter-mode
,
(add-hook 'evilmi-jump-hook
(lambda (before-jump-p)
(global-tree-sitter-mode (not before-jump-p))))
In order to apply three matching rules evilmi-template
, evilmi-simple
, and evilmi-html
on mhtml-mode
, please insert below code after your evil-matchit setup:
(evilmi-load-plugin-rules '(mhtml-mode) '(template simple html))
evilmi-select-items
is more robust and provides more functionality. It works even when evil-mode
is not loaded.
So you’d better stick to evilmi-select-item
if possible.
Use ruby as an example.
If you want to add more tags into ruby, you can do two things:
- You need define the regular expression to extract keyword
- You need define the open/middle/closed tags
Open evil-matchit-ruby.el whole structure is like,
(defvar evilmi-ruby-extract-keyword-howtos '())
(defvar evilmi-ruby-match-tags '())
;; more code here ...
(provide 'evil-matchit-ruby)
So you configuration in ~/.emacs
is as below:
(with-eval-after-load "evil-matchit-ruby"
(push '("^[ \t]*\\([a-z]+\\)\\( .*\\| *\\)$" 1) evilmi-ruby-extract-keyword-howtos)
(push '(("unless" "if") ("elsif" "else") "end")) evilmi-ruby-match-tags)
All you need to do is to define function evilmi-customize-keybinding
before turning on evil-matchit-mode
:
The shortcut %
is defined in evilmi-shortcut
. It’s the name of text object and shortcut of evilmi-jump-items
. Some people prefer set it
to “m”.
Change keybinding of evilmi-jump-items
and name of the text object,
(setq evilmi-shortcut "m")
(global-evil-matchit-mode 1)
Change keybinding only,
(defun evilmi-customize-keybinding ()
(evil-define-key 'normal evil-matchit-mode-map
"%" 'evilmi-jump-items))
(global-evil-matchit-mode 1)
Please note the definition of “string” could be customized by user.
For example, we could treat C comment as string wrapper by “/”.
Here is the setup to jump between the two ends of the C comment:
(setq evilmi-quote-chars (string-to-list "'\"/"))
It’s decided by the Emacs global variable “case-fold-search”. You need not care about it because the major mode will set this flag automatically.
You can turn on evilmi-always-simple-jump
to match brackets at first.
Thus, you disable our advanced algorithm which I highly recommend.
Some people may prefer simpler algorithm in python-mode
.
Simple. You only need define two functions and tell evil-matchit in which major mode they should be used.
A complete setup to insert into “~/.emacs”:
;; detect tag in current line and return the result in variable rlt
;; the rlt will be used by evilmi-mylang-jump as the first parameter.
;; if NO tag found, the rlt SHOULD be nil
;;
;; @return the data to be used by evilmi-mylang-jump which should be a list
;; the first element of the list is the position of cursor before jump
;; we use it to select/delete tag. The other elements of the list could
;; be any data type
(defun evilmi-mylang-get-tag ()
(list position-of-open-end "anything-you-like" "anything-you-like"))
;; @parama rlt result from evilmi-mylang-get-tag
;; @param NUM numeric argument when user press "%" to match tag
;; @return the matching tag position in theory, useful only for
;; selecting or deleting text between matching tags and tags
(defun evilmi-mylang-jump (info num)
(message "info=%s" info)
;; if we need select region between tags (including tags itself)
;; we get the beginning of region by reading the first element of
;; info
(push-mark (nth 0 info) t t)
;; say 999 is the where we jump to
(goto-char 999)
;; If you need know where is the end of the region for region operation,
;; you need return the end of region at the end of function
;; region operation means selection/deletion of region.
888)
;; Notify evil-matchit how to use above functions
(evilmi-load-plugin-rules '(mylang-mode) '(mylang))
Place above code into your ~/.emacs
, after the line “(global-evil-matchit-mode 1)”
For example, it only takes 3 steps to create a new rule script
to match tags in script like Ruby/Lua/Bash/VimScript,
Step 1, create evil-matchit-script.el
,
(require 'evil-matchit-sdk)
;; ruby/bash/lua/vimrc
(defvar evilmi-script-match-tags
'((("unless" "if") ("elif" "elsif" "elseif" "else") ( "end" "fi" "endif"))
("begin" ("rescue" "ensure") "end")
("case" ("when" "else") ("esac" "end"))
(("fun!" "function!" "class" "def" "while" "function" "do") () ("end" "endfun" "endfunction"))
("repeat" () "until"))
"The table we look up match tags. This is a three column table.
The first column contains the open tag(s).
The second column contains the middle tag(s).
The third column contains the closed tags(s).
The forth *optional* column defines the relationship between open and close tags. It could be MONOGAMY
")
;;;###autoload
(defun evilmi-script-get-tag ()
(evilmi-sdk-get-tag evilmi-script-match-tags
evilmi-sdk-extract-keyword-howtos))
;;;###autoload
(defun evilmi-script-jump (rlt num)
(evilmi-sdk-jump rlt
num
evilmi-script-match-tags
evilmi-sdk-extract-keyword-howtos))
(provide 'evil-matchit-script)
Step 2, make sure the directory of evil-matchit-script.el
is added into load-path
.
Step 3, add below code to ~/.emacs.
,
(evilmi-load-plugin-rules '(ruby-mode lua-mode) '(script))
Use evilmi-add-one-plugin-rule
to add one new plugin rule for specific MAJOR-MODE.
For example, to integrate rjsx-jump-tag
into this package is as simple as,
(evilmi-add-one-plugin-rule 'rjsx-mode #'rjsx-jump-tag)
Or,
(defun my-rjsx-jump-tag ()
(when (string-match "</?>" (string-trim (evilmi-sdk-curline)))
(rjsx-jump-tag)))
(evilmi-add-one-plugin-rule 'rjsx-mode #'my-rjsx-jump-tag)
Or,
(defun my-rjsx-get-tag()
(let ((info ("some info")))
info))
(defun my-rjsx-jump-tag (info num)
(rjsx-jump-tag)
(point))
(defvar append-p t)
(evilmi-add-one-plugin-rule 'rjsx-mode #'my-rjsx-jump-tag #'my-rjsx-get-tag append-p)
It’s easy to support such language (Python, Yaml, …).
Here is a minimum example to support yaml,
(require 'evil-matchit-indent)
(defun evilmi-yaml-get-tag ()
(evilmi-indent-get-tag))
(defun evilmi-yaml-jump (info num)
(let* ((evilmi-spaces-per-tab 2))
(evilmi-indent-jump info)))
(evilmi-load-plugin-rules '(yaml-mode) '(yaml))
- evilmi-load-plugin-rules
Report bugs at https://github.com/redguardtoo/evil-matchit.
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.