Navigate/Select html tags in Emacs

  |   Source

UPDATED: <2017-11-03 Fri>

Navigate/select html tags is kind of difficult. I've not found any plugin which could match Vim's matchit.

The most close one in Emacs is smartparens. You can check this discussion on Google Plus to get general impression.

I use sp-select-next-thing from smarparens to select html tags. But I'm not satisfied with this command because it's picky on the location of my cursor and selection of multiple html tags is buggy.

So here is my fix, the new command my-sp-select-next-thing which fix all these issues.

Screen cast: my-sp-select-next-thing.gif

Here is the code to insert it into your ~/.emacs:

;; make sp-select-next-thing works even the cusor is in the open/close tag
;; like matchit in vim
;; @return t => start from open tag; nil start from close tag
(defun my-sp-select-next-thing (&optional NUM)
  (interactive "p")
  (let* ((b (line-beginning-position))
         (e (line-end-position))
         (char (following-char))
         (p (point))
         rbeg
         rend
         (rlt t))
    ;; "<" char code is 60
    ;; search backward
    (if (not (= char 60))
        (save-excursion
          (while (and (<= b (point)) (not (= char 60)))
            (setq char (following-char))
            (setq p (point))
            (backward-char))))
    ;; search forward
    (if (not (= char 60))
        (save-excursion
          (while (and (>= e (point)) (not (= char 60)))
            (setq char (following-char))
            (setq p (point))
            (forward-char))))
    ;; do the real thing
    (when (and (= char 60) (< p e))
      (goto-char p)
      (forward-char)
      (if (= (following-char) 47)
          (progn
            ;; </
            (backward-char)
            (setq rlt nil))
        (progn
          ;; < , looks fine
          (backward-char)
          (setq rlt t)))
      (sp-select-next-thing)
      (setq rbeg (region-beginning))
      (setq rend (region-end))

      (while (> NUM 1)
        ;; well, sp-select-next-thing is kind of wierd
        (re-search-forward "<[^!]")
        (backward-char 2)
        (sp-select-next-thing)
        (setq rend (region-end))
        (setq NUM (1- NUM)))
      (push-mark rbeg t t)
      (goto-char (1-rend)))
    rlt))

Navigation is easy. After selecting the tags, press C-x C-x to move the focus. That's it.

For evil-mode, I write some code which simulate the famous matchit in vi:

(require 'evil)

;; {{ evil-matchit
(defun my-evil-jump-item-enhanced-for-html ()
  (interactive)
  (if (or (eq major-mode 'html-mode)
          (eq major-mode 'xml-mode)
          (eq major-mode 'nxml-mode))
      (progn
        (if (not (my-sp-select-next-thing 1)) (exchange-point-and-mark))
        (deactivate-mark))
    (progn
      (evil-jump-item))))
(define-key evil-normal-state-map "%" 'my-evil-jump-item-enhanced-for-html)
;; }}

Now you can press % in evil to jump between tags!

Requirement:

  • smartparens-1.5
  • evil-1.0.7
  • emacs-24.2.1

BTW, I also tried the web-mode-tag-match in web-mode which provided similar tag match feature.

At least now (2nd October,2013) web-mode does not support freemarker syntax. But smartparens is more tolerant to these template syntax.

UPDATE (6th Nov, 2013): I started a new project evil-matchit which is not dependent on smartparens. Please check it out. But you can still use my old code because it support more languages.

UPDATED (13rd Jan, 2014): evil-matchit is now powerful enough to replace my old tricks.

Comments powered by Disqus