Javascript code navigation in counsel-etags
Javascript code navigation is supported by counsel-etags out of box.
It can support new javascript syntax like arrow function easily because counsel-etags
is just frontend.
It reads tags file created by backend CLI program Ctags. Ctags uses regular expression to extract tag name from source code.
But there are some syntax which regular expression could not help.
For example, json object path can't be extracted by regular expression.
Given an object a
in file A,
var a = {
b: {
c: 3,
}
};
File B has code let v1 = a.b.c;
, how can we jump to the definition of the field c
from json path a.b.c
?
The solution is to use Lisp to parse code in file A and generate extra navigation data which could be appended to tags file generated by Ctags.
The algorithm is simple,
- Traverse all the field of object
a
in file A. Use APIjs2-print-json-path
fromjs2-mode
to get json path of current field. - The json path could be regarded as tags name. We've already got file name and line number. So there is enough information to create navigation data for tags file. Here is tags file format.
Necessary utilities are already provided by counsel-etags v1.8.7
,
- After tags files is generated by Ctags, the hook
counsel-etags-after-update-tags-hook
is executed. Users can append tags file in this hook -
(counsel-etags-tag-line code-snippet tag-name line-number byte-offset)
return a line which could be appended into tags file
My current project uses a technology called styled-components which has an advanced feature theming.
It could dynamically change web application's appearance and is a critical technology for our application to support multiple customer. Application's theme is basically a file containing a huge json object. So it's important that developers can jump to the corresponding json object's field by json path.
Screencast
Code
(require 'counsel-etags)
(defun my-manual-update-tag-file (code-file tags-file)
(let* ((dir (file-name-directory tags-file))
(path (concat dir code-file))
curline
jp
tagstr)
(unless (featurep 'js2-mode) (require 'js2-mode))
(with-temp-buffer
(insert-file-contents path)
(js2-init-scanner)
(js2-do-parse)
(goto-char (point-min))
;; find all js object property names
(while (re-search-forward "\"?[a-z][a-zA-Z0-9]*\"?:" (point-max) t)
(when (setq jp (js2-print-json-path))
(setq curline (string-trim (buffer-substring-no-properties (line-beginning-position)
(line-end-position))))
(setq tagstr (concat tagstr
(counsel-etags-tag-line curline
jp
(count-lines 1 (point))
(point)))))
;; move focus to next search
(goto-char (line-end-position))))
(when tagstr
(with-temp-buffer
(insert-file-contents tags-file)
(goto-char (line-end-position))
(insert (format "\n\014\n%s,%d\n%s" code-file 0 tagstr))
(write-region (point-min) (point-max) tags-file nil :silent)))))
(defun counsel-etags-after-update-tags-hook-setup (tags-file)
(my-manual-update-tag-file "frontend/theming/themes/darkTheme.js" tags-file)
(my-manual-update-tag-file "frontend/theming/themes/lightTheme.js" tags-file))
(add-hook 'counsel-etags-after-update-tags-hook 'counsel-etags-after-update-tags-hook-setup)