branch: elpa/evil-matchit commit 130941b1a436f100cdab0f28b3f67d0f85224d4f Author: Chen Bin <chenbin...@gmail.com> Commit: Chen Bin <chenbin...@gmail.com>
can jump from end of html self closing tag --- evil-matchit-html.el | 60 +++++++++++++++----------- evil-matchit-javascript.el | 2 +- evil-matchit-sdk.el | 68 ++++++++++++++++++++++-------- evil-matchit-simple.el | 21 ++++++---- evil-matchit.el | 102 +++++++++++---------------------------------- 5 files changed, 125 insertions(+), 128 deletions(-) diff --git a/evil-matchit-html.el b/evil-matchit-html.el index 33ae906795..c4dd715dbd 100644 --- a/evil-matchit-html.el +++ b/evil-matchit-html.el @@ -39,24 +39,27 @@ It starts from POSITION and possibly ends at line end." (buffer-substring position (line-end-position))))) (car (split-string partial-line "[ \t]+")))) -;;;###autoload -(defun evilmi-html-get-tag () - "Get current tag." - (let* ((b (line-beginning-position)) - (e (line-end-position)) +(defun evilmi-html--detect-self-closing-tag-end (char position) + "Use CHAR at POSITION to test if it's the end of self closing tag. +If at the end of self closing tag, " + (when (or (and (eq char ?>) + (eq (evilmi-sdk-get-char (1- position)) ?/)) + (and (eq char ?/) + (eq (evilmi-sdk-get-char (1+ position)) ?>))) + (list (if (eq char ?>) position (1+ position)) 1 ""))) + +(defun evilmi-html--detect-normal-tags (char position) + "Test one of matched tags or beginning of self closing tag." + (let* ((begin (line-beginning-position)) + (end (line-end-position)) (looping t) - (char (following-char)) - (p (point)) (found_tag -1)) - - (if evilmi-debug (message "evilmi-html-get-tag called. p=%s" p)) (save-excursion ;; search backward for "<" (unless (eq char ?<) - (while (and looping (<= b (point)) (not (eq char ?<))) + (while (and looping (<= begin (point)) (not (eq char ?<))) (setq char (following-char)) - (setq p (point)) - (if (eq p (point-min)) + (if (eq (setq position (point)) (point-min)) ;; need get out of loop anyway (setq looping nil) (backward-char)))) @@ -64,20 +67,20 @@ It starts from POSITION and possibly ends at line end." ;; search forward for "<" (unless (eq char ?<) (save-excursion - (while (and (>= e (point)) (not (eq char ?<))) + (while (and (>= end (point)) (not (eq char ?<))) (setq char (following-char)) - (setq p (point)) + (setq position (point)) (forward-char)))) ;; a valid html tag should be like <[^;] (unless (and (eq char ?<) ;; html tags should not contain " ,;" - (string-match "^<[^ ;,]+$" (evilmi-html--open-tag-candidate p))) + (string-match "^<[^ ;,]+$" (evilmi-html--open-tag-candidate position))) (setq char nil)) ;; is end tag? - (when (and (eq char ?<) (< p e)) - (goto-char p) + (when (and (eq char ?<) (< position end)) + (goto-char position) (forward-char) (cond ((eq (following-char) ?/) @@ -89,24 +92,33 @@ It starts from POSITION and possibly ends at line end." ;; < , looks fine (backward-char) (setq found_tag 0))) - (setq p (point)))) + (setq position (point)))) (when (eq found_tag 0) - ;; sgml-skip-tag-forward can't handle the open html tag whose attribute containing "<" or ">" character - ;; UNLESS the start position is above "<" character - (goto-char p) ; move to the closest "<" + ;; `sgml-skip-tag-forward' can't handle the open html tag whose attribute containing "<" or ">" character + ;; unless the start position is above "<" character + (goto-char position) ; move to the closest "<" (when (or (evilmi-among-fonts-p (point) evilmi-ignored-fonts) ;; In `rjsx-mode', the attribute value's font face could be nil ;; like <Component a1={3 > 2} /> (and (eq ?< (following-char)) ;; if it's html tag, the character next "<" should ;; have some font face - (not (get-text-property (1+ p) 'face)))) + (not (get-text-property (1+ position) 'face)))) ;; since current "<" is not part of open html tag, ;; skip backward to move cursor over the "<" of open html tag (sgml-skip-tag-backward 1) - (setq p (point)))) - (list p found_tag ""))) + (setq position (point)))) + (list position found_tag ""))) + +;;;###autoload +(defun evilmi-html-get-tag () + "Get current tag." + (let* ((char (following-char)) + (position (point))) + (if evilmi-debug (message "evilmi-html-get-tag called. position" position)) + (or (evilmi-html--detect-self-closing-tag-end char position) + (evilmi-html--detect-normal-tags char position)))) ;;;###autoload (defun evilmi-html-jump (info num) diff --git a/evil-matchit-javascript.el b/evil-matchit-javascript.el index 3a8477361a..3949502b37 100644 --- a/evil-matchit-javascript.el +++ b/evil-matchit-javascript.el @@ -100,7 +100,7 @@ evilmi-javascript-match-tags evilmi-javascript-extract-keyword-howtos)) (t - (evilmi--simple-jump) + (evilmi-sdk-simple-jump) (let* ((cur-line (evilmi-sdk-curline))) ;; hack for javascript (if (or (string-match "^[ \t]*}\)\(.*\)\; *$" cur-line) diff --git a/evil-matchit-sdk.el b/evil-matchit-sdk.el index 1526cc3627..0f22e387a2 100644 --- a/evil-matchit-sdk.el +++ b/evil-matchit-sdk.el @@ -1,6 +1,10 @@ (defvar evilmi-debug nil "Debug flag.") +(defvar evilmi-forward-chars (string-to-list "[{(")) +(defvar evilmi-backward-chars (string-to-list "]})")) +(defvar evilmi-quote-chars (string-to-list "'\"/")) + (defvar evilmi-ignored-fonts '(web-mode-html-attr-value-face font-lock-string-face @@ -17,15 +21,41 @@ expression to match the current line. The second is the index of sub-matches to extract the keyword which starts from one. The sub-match is the match defined between '\\(' and '\\)' in regular expression.") -;; slower but I don't care -;; @see http://ergoemacs.org/emacs/modernization_elisp_lib_problem.html -(defun evilmi-sdk-trim-string (string) - (replace-regexp-in-string "\\`[ \t\n]*" "" (replace-regexp-in-string "[ \t\n]*\\'" "" string))) - -(defun evilmi-sdk-keyword (info) - (nth 3 info)) - -(defun evilmi-sdk-tags-is-matched (level orig-tag-info cur-tag-info match-tags) +(defmacro evilmi-sdk-keyword (info) + "Get keyword from INFO." + `(nth 3 ,info)) + +(defun evilmi-sdk-jump-forward-p () + "Return: (forward-direction font-face-under-cursor character-under-cursor). +If font-face-under-cursor is NOT nil, the quoted string is being processed." + (let* ((ch (following-char)) + (p (point)) + ff + (rlt t)) + (cond + ((memq ch evilmi-backward-chars) + (setq rlt nil)) + ((and (memq ch evilmi-quote-chars)) + (setq rlt (eq (setq ff (get-text-property p 'face)) + (get-text-property (+ 1 p) 'face))))) + + (when evilmi-debug + (message "evilmi-sdk-jump-forward-p => (%s %s %s)" rlt ff (string ch))) + (list rlt ff ch))) + +(defun evilmi-sdk-simple-jump () + "Alternative for `evil-jump-item'." + (if evilmi-debug (message "evilmi-sdk-simple-jump called (point)=%d" (point))) + (let* ((tmp (evilmi-sdk-jump-forward-p)) + (jump-forward (car tmp)) + ;; if ff is not nil, it's jump between quotes + ;; so we should not use (scan-sexps) + (ff (nth 1 tmp)) + (ch (nth 2 tmp))) + (goto-char (evilmi--find-position-to-jump ff jump-forward ch)) + (evilmi--tweak-selected-region ff jump-forward))) + +(defun evilmi-sdk-tags-matched-p (level orig-tag-info cur-tag-info match-tags) (let* (rlt (orig-keyword (evilmi-sdk-keyword orig-tag-info)) (cur-keyword (evilmi-sdk-keyword cur-tag-info)) @@ -131,14 +161,14 @@ is-function-exit-point could be unknown status" rlt)) (defun evilmi--sdk-extract-keyword (cur-line match-tags howtos) - "Extract keyword from CUR-LINE. Keyword is defined in MATCH-TAGS." + "Extract keyword from CUR-LINE. Keyword is defined in MATCH-TAGS. +Rule is looked up in HOWTOS." (let* (keyword howto (i 0)) (while (and (not keyword) (< i (length howtos))) (setq howto (nth i howtos)) (when (string-match (nth 0 howto) cur-line) ;; keyword should be trimmed because FORTRAN use "else if" - (setq keyword (evilmi-sdk-trim-string (match-string (nth 1 howto) - cur-line))) + (setq keyword (string-trim (match-string (nth 1 howto) cur-line))) ;; keep search keyword by using next howto (regex and match-string index) (unless (evilmi-sdk-member keyword match-tags) (setq keyword nil))) (setq i (1+ i))) @@ -166,6 +196,10 @@ is-function-exit-point could be unknown status" (setq rlt t))) rlt)) +(defmacro evilmi-sdk-get-char (position) + "Get character at POSITION." + `(char-after ,position)) + ;;;###autoload (defun evilmi-sdk-get-tag (match-tags howtos) "Return '(start-point ((row column is-function-exit-point keyword))." @@ -214,14 +248,14 @@ after calling this function." ;; handle open tag ;; open (0) -> mid (1) found when level is one else ignore ((and (= orig-tag-type 0) (= cur-tag-type 1)) - (when (evilmi-sdk-tags-is-matched level orig-tag-info cur-tag-info match-tags) + (when (evilmi-sdk-tags-matched-p level orig-tag-info cur-tag-info match-tags) (back-to-indentation) (setq ideal-dest (1- (line-beginning-position))) (setq found t))) ;; open (0) -> closed (2) found when level is zero, level-- ((and (= orig-tag-type 0) (= cur-tag-type 2)) - (when (evilmi-sdk-tags-is-matched level orig-tag-info cur-tag-info match-tags) + (when (evilmi-sdk-tags-matched-p level orig-tag-info cur-tag-info match-tags) (goto-char (line-end-position)) (setq ideal-dest (line-end-position)) (setq found t)) @@ -239,14 +273,14 @@ after calling this function." ;; level is one means we are not in some embedded loop/conditional statements ((and (= orig-tag-type 1) (= cur-tag-type 1)) - (when (evilmi-sdk-tags-is-matched level orig-tag-info cur-tag-info match-tags) + (when (evilmi-sdk-tags-matched-p level orig-tag-info cur-tag-info match-tags) (back-to-indentation) (setq ideal-dest (1- (line-beginning-position))) (setq found t))) ;; mid (1) -> closed (2) found when level is zero, level -- ((and (= orig-tag-type 1) (= cur-tag-type 2)) - (when (evilmi-sdk-tags-is-matched level orig-tag-info cur-tag-info match-tags) + (when (evilmi-sdk-tags-matched-p level orig-tag-info cur-tag-info match-tags) (goto-char (line-end-position)) (setq ideal-dest (line-end-position)) (setq found t)) @@ -266,7 +300,7 @@ after calling this function." ;; closed (2) -> open (0) found when level is zero, level-- ((and (= orig-tag-type 2) (= cur-tag-type 0)) - (when (evilmi-sdk-tags-is-matched level orig-tag-info cur-tag-info match-tags) + (when (evilmi-sdk-tags-matched-p level orig-tag-info cur-tag-info match-tags) (setq ideal-dest (line-beginning-position)) (back-to-indentation) (setq found t)) diff --git a/evil-matchit-simple.el b/evil-matchit-simple.el index 9e2f972239..3a2808efe9 100644 --- a/evil-matchit-simple.el +++ b/evil-matchit-simple.el @@ -65,13 +65,9 @@ (defun evilmi-simple-get-tag () "Get current tag in simple language." (let* (forward-line-num - ;; Only handle open tag - (tmp (evilmi--get-char-under-cursor)) - (ch (if tmp (car tmp))) + (ch (following-char)) rlt) - (if evilmi-debug (message "evilmi-simple-get-tag called => ch=%s (point)=%d" ch (point))) - (cond ;; In evil-visual-state, the (preceding-char) is actually the character under cursor ((not (evilmi--char-is-simple ch)) @@ -82,16 +78,23 @@ (forward-line (1- forward-line-num)) (search-forward "{" nil nil) (backward-char))) + ((and (memq ch evilmi-quote-chars) + (eq ch ?/) + (not (eq ?* (evilmi-sdk-get-char (1- (point))))) + (not (eq ?* (evilmi-sdk-get-char (1+ (point)))))) + ;; character at point is not "/*" or "*/" + (setq rlt nil)) (t - ;; use evil's own evilmi--simple-jump + ;; use evil's own evilmi-sdk-simple-jump (setq rlt (list (point))))) - (if (and evilmi-debug rlt) (message "evilmi-simple-get-tag called rlt=%s" rlt)) + (if (and evilmi-debug rlt) (message "evilmi-simple-get-tag called char=%s => %s" ch rlt)) rlt)) ;;;###autoload (defun evilmi-simple-jump (info num) - "Use INFO of current tag ot jump to matching tag. NUM is ignored." + "Use INFO of current tag to jump to matching tag. NUM is ignored." + (ignore num) (when info (if evilmi-debug (message "evilmi-simple-jump called (point)=%d" (point))) @@ -101,7 +104,7 @@ ((memq major-mode '(latex-mode)) (evil-jump-item)) (t - (evilmi--simple-jump))) + (evilmi-sdk-simple-jump))) ;; hack for javascript (cond diff --git a/evil-matchit.el b/evil-matchit.el index 81d56f14d8..6d2e9ef0c3 100644 --- a/evil-matchit.el +++ b/evil-matchit.el @@ -67,64 +67,26 @@ Some people prefer using \"m\" instead.") "`major-mode' like `python-mode' use optimized algorithm by default. If it's t, use simple jump.") -(defvar evilmi-forward-chars (string-to-list "[{(")) -(defvar evilmi-backward-chars (string-to-list "]})")) -(defvar evilmi-quote-chars (string-to-list "'\"/")) - (defun evilmi--char-is-simple (ch) "Special handling of character CH for `python-mode'." - (let* (rlt) - (cond - ((and (not evilmi-always-simple-jump) - (memq major-mode '(python-mode)) - ;; in evil-visual-state, (point) could equal to (line-end-position) - (>= (point) (1- (line-end-position)))) - ;; handle follow python code, - ;; - ;; if true: - ;; a = "hello world" - ;; - ;; If current cursor is at end of line, rlt should be nil! - ;; or else, matching algorithm can't work in above python sample - (setq rlt nil)) - (t - (setq rlt (or (memq ch evilmi-forward-chars) - (memq ch evilmi-backward-chars) - ;; sorry we could not jump between ends of string in python-mode - (memq ch evilmi-quote-chars))))) - rlt)) - -(defun evilmi--get-char-at-position (pos) - "Get character at POS." - (let* ((ch (char-after pos))) - (if evilmi-debug (message "evilmi--get-char-at-position called. Return: %s" (string ch))) - ch)) - -(defun evilmi--get-char-under-cursor () - "Return: (character position)." - (let* ((ch (following-char)) - (p (point))) - (if evilmi-debug (message "evilmi--get-char-under-cursor called. Return: (%d %s)" ch p)) - (list ch p))) - -(defun evilmi--is-jump-forward () - "Return: (forward-direction font-face-under-cursor character-under-cursor). -If font-face-under-cursor is NOT nil, the quoted string is being processed." - (if evilmi-debug (message "evilmi--is-jump-forward called")) - (let* ((tmp (evilmi--get-char-under-cursor)) - (ch (car tmp)) - (p (cadr tmp)) - ff - (rlt t)) - (cond - ((memq ch evilmi-backward-chars) - (setq rlt nil)) - ((memq ch evilmi-quote-chars) - (setq rlt (eq (setq ff (get-text-property p 'face)) - (get-text-property (+ 1 p) 'face))))) - - (if evilmi-debug (message "evilmi--is-jump-forward return (%s %s %s)" rlt ff (string ch))) - (list rlt ff ch))) + (cond + ((and (not evilmi-always-simple-jump) + (memq major-mode '(python-mode)) + ;; in evil-visual-state, (point) could equal to (line-end-position) + (>= (point) (1- (line-end-position)))) + ;; handle follow python code, + ;; + ;; if true: + ;; a = "hello world" + ;; + ;; If current cursor is at end of line, rlt should be nil! + ;; or else, matching algorithm can't work in above python sample + nil) + (t + (or (memq ch evilmi-forward-chars) + (memq ch evilmi-backward-chars) + ;; sorry we could not jump between ends of string in python-mode + (memq ch evilmi-quote-chars))))) (defun evilmi-in-comment-p (pos) "Check character at POS is comment by comparing font face." @@ -132,11 +94,11 @@ If font-face-under-cursor is NOT nil, the quoted string is being processed." ;; @see https://github.com/redguardtoo/evil-matchit/issues/92 ((eq major-mode 'tuareg-mode) (evilmi-among-fonts-p pos '(font-lock-comment-face - font-lock-comment-delimiter-face - font-lock-doc-face))) + font-lock-comment-delimiter-face + font-lock-doc-face))) (t (evilmi-among-fonts-p pos '(font-lock-comment-face - font-lock-comment-delimiter-face))))) + font-lock-comment-delimiter-face))))) (defun evilmi--scan-sexps (is-forward) "Get the position of matching tag. @@ -195,7 +157,7 @@ If IS-FORWARD is t, jump forward; or else jump backward." (while (not got) (cond ((or (= pos end) - (and (= char (evilmi--get-char-at-position (- pos delta))) + (and (= char (evilmi-sdk-get-char (- pos delta))) (not (eq font-face (get-text-property pos 'face))))) (setq rlt (if is-forward pos (+ 1 pos))) (setq got t)) @@ -213,11 +175,10 @@ If IS-FORWARD is t, jump forward; or else jump backward." ;; @see http://emacs.stackexchange.com/questions/13222/a-elisp-function-to-jump-between-matched-pair (defun evilmi--find-position-to-jump (ff is-forward ch) - (if evilmi-debug (message "evilmi--find-position-to-jump called => %s %s %s %d" ff is-forward ch (point))) "Non-nil ff means jumping between quotes" (let* ((rlt (if ff (evilmi--find-the-other-quote-char ff is-forward ch) (evilmi--scan-sexps is-forward)))) - (if evilmi-debug (message "evilmi--find-position-to-jump return %s" (evilmi--adjust-jumpto is-forward rlt))) + (if evilmi-debug (message "evilmi--find-position-to-jump => %s" (evilmi--adjust-jumpto is-forward rlt))) (evilmi--adjust-jumpto is-forward rlt))) (defun evilmi--tweak-selected-region (font-face jump-forward) @@ -228,19 +189,6 @@ If IS-FORWARD is t, jump forward; or else jump backward." ;; so hack to workaround scan-sexps is NOT necessary (evil-backward-char))) -(defun evilmi--simple-jump () - "Alternative for `evil-jump-item'." - (interactive) - (if evilmi-debug (message "evilmi--simple-jump called (point)=%d" (point))) - (let* ((tmp (evilmi--is-jump-forward)) - (jump-forward (car tmp)) - ;; if ff is not nil, it's jump between quotes - ;; so we should not use (scan-sexps) - (ff (nth 1 tmp)) - (ch (nth 2 tmp))) - (goto-char (evilmi--find-position-to-jump ff jump-forward ch)) - (evilmi--tweak-selected-region ff jump-forward))) - (defun evilmi--operate-on-item (num &optional func) "Jump NUM times and apply function FUNC." (when evilmi-debug @@ -268,10 +216,10 @@ If IS-FORWARD is t, jump forward; or else jump backward." (when evilmi-debug (message "rlt=%s rule=%s p=%s jumped=%s" rlt rule (point) jumped)))) - ;; give `evilmi--simple-jump' a second chance + ;; give `evilmi-sdk-simple-jump' a second chance (unless jumped (if func (funcall func (list (point)))) - (evilmi--simple-jump) + (evilmi-sdk-simple-jump) (setq ideal-dest (point))) (if evilmi-debug (message "evilmi--operate-on-item called. Return: %s" ideal-dest))