Fast, energy-saving, and powerful code navigation solution.
It's been tested on Linux/Windows/macOS.
[[file:demo.png]]
Table of Content :noexport:TOC:
[[#counsel-etags][counsel-etags]]
[[#install][Install]]
[[#usage][Usage]]
[[#tips-optional][Tips (OPTIONAL)]]
[[#step-by-step-guide][Step by step guide]]
[[#bug-report][Bug Report]]
Install Please install =counsel-etags= from [[https://melpa.org/#/counsel-etags][MELPA]].
If [[http://ctags.sourceforge.net/][Exuberant Ctags]] or [[https://ctags.io/][Universal Ctags]] exists, this program works out of box.
Universal Ctags is actively maintained and strongly recommended.
Or else, customize =counsel-etags-update-tags-backend= to create tags file with your own CLI. Please note [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Create-Tags-Table.html#Create-Tags-Table][etags]] bundled with Emacs is not supported anymore.
[[https://github.com/redguardtoo/emacs.d/issues/697#issuecomment-394141015][It's reported]] "Exuberant Ctags" v5.8.5 is buggy.
This command will:
Please note it takes time to parse tags file contains long lines. It's the known issue of Emacs Lisp.
You could run =M-x counsel-etags-scan-code= only once and create tags file [[https://www.emacswiki.org/emacs/BuildTags][in your own way]].
Please read [[#step-by-step-guide][Step by step guide]] for more details.
If grep program path is added to environment variable PATH, you don't need worry about slash problem. ** ".gitignore" and ".hgignore" are respected The variable =counsel-etags-ignore-config-file= specifies the paths of ignore configuration files (".gitignore", ".hgignore", etc).
The path is either absolute or relative to the tags file.
Set =counsel-etags-ignore-config-files= to nil to turn off this feature. ** Set up with [[https://github.com/jwiegley/use-package][use-package]] Please place =add-hook= code inside =:init= section,
(use-package counsel-etags :ensure t :bind (("C-]" . counsel-etags-find-tag-at-point)) :init (add-hook 'prog-mode-hook (lambda () (add-hook 'after-save-hook 'counsel-etags-virtual-update-tags 'append 'local))) :config (setq counsel-etags-update-interval 60) (push "build" counsel-etags-ignore-directories))
** Insert extra content into tags file after it's updated =counsel-etags-find-tag-name-function= finds tag name at point. If it returns nil, =find-tag-default= is used. =counsel-etags-word-at-point= returns the word at point.
User could append the extra content into tags file in =counsel-etags-after-update-tags-hook=.
The parameter of hook function is full path of the tags file.
=counsel-etags-tag-line= and =counsel-etags-append-to-tags-file= are helper functions to update tags file in the hook,
Sample code to append native javascript API "addEventListener", "dispatchEvent", "removeEventListener" into tags file,
(defun my-update-tags-file (tags-file) "Update TAGS-FILE." (when (memq major-mode '(js-mode typescript-mode js2-mode)) (let ((s3 (mapconcat (lambda (tagname) (counsel-etags-tag-line tagname tagname 0)) '(addEventListener dispatchEvent removeEventListener) ""))) (counsel-etags-append-to-tags-file (list (cons "https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/%s" s3)) tags-file)))) (add-hook 'counsel-etags-after-update-tags-hook 'my-update-tags-file)
** Configuration file Path of the configuration file is defined in =counsel-etags-ctags-options-file= whose value is =~/.ctags=.
Exuberant Ctags actually can NOT open configuration file ".ctags" through cli option.
We use Emacs Lisp to load =~/.ctags= to workaround this issue.
Please use file name like =ctags.cnf= instead =.ctags= when customize this variable for Exuberant Ctags.
Universal Ctags does NOT have this problem. ** Ignore directories and files You can set up =counsel-etags-ignore-directories= and =counsel-etags-ignore-filenames=,
(with-eval-after-load 'counsel-etags ;; counsel-etags-ignore-directories does NOT support wildcast (push "build_clang" counsel-etags-ignore-directories) (push "build_clang" counsel-etags-ignore-directories) ;; counsel-etags-ignore-filenames supports wildcast (push "TAGS" counsel-etags-ignore-filenames) (push "*.json" counsel-etags-ignore-filenames))
** Dependency on Emacs APIs is minimum I intend to keep this package completely independent.
Many native tag API or variable (=tags-file-name=, =tags-table-list=, =visit-tags-table=, =xref-find-references=, etc) are not used.
** Specify multiple tags files =counsel-etags-extra-tags-files= contains extra tags file to parse.
Sample setup,
(setq counsel-etags-extra-tags-files '("/usr/include/TAGS" "/usr/local/include/TAGS"))
Files in =counsel-etags-extra-tags-files= should have symbols with absolute path only. ** Auto update tags file
;; Don't ask before rereading the TAGS files if they have changed (setq tags-revert-without-query t) ;; Don't warn when TAGS files are large (setq large-file-warning-threshold nil) ;; Setup auto update now (add-hook 'prog-mode-hook (lambda () (add-hook 'after-save-hook 'counsel-etags-virtual-update-tags 'append 'local)))
You can change callback =counsel-etags-update-tags-backend= to update tags file using your own solution,
(setq counsel-etags-update-tags-backend (lambda (src-dir) (shell-command "/usr/bin/ctags -e -R")))
** Rust programming language Tags file for [[https://www.rust-lang.org/][Rust programming language]] can be generated by [[https://github.com/dan-t/rusty-tags][rusty-tags]].
Run =rusty-tags emacs= in shell to generate tags file. You also need =(setq counsel-etags-tags-file-name "rusty-tags.emacs")=.
The easiest way to set up rusty-tags per project is to create [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Directory-Variables.html][.dir-locals.el]] in project root,
((nil . ((counsel-etags-update-tags-backend . (lambda (src-dir) (shell-command "rusty-tags emacs"))) (counsel-etags-tags-file-name . "rusty-tags.emacs"))))
List all tags =M-x counsel-etags-list-tag= Two-step tag matching using regular expression and filter =M-x counsel-etags-find-tag= Force update current tags file Run =counsel-etags-update-tags-force=. Tags file in project root should exist before running this command. Open recent tag =M-x counsel-etags-recent-tag= ** Ctags setup Google "filetype:ctags site:github.com". Here is [[https://gist.github.com/redguardtoo/b12ddae3b8010a276e9b][my configuration for Exuberant Ctags]].
Please note there is some trivial difference between Exuberant Ctags configuration and Universal Ctags.
If you are using Universal Ctags with my configuration for Exuberant Ctags, run below CLI in shell and fixed all the warning by modifying the =~/.ctags= first,
ctags --options="$HOME/.ctags" -e -R
You may need configure environment variable "HOME" on Windows because Ctags looks for "%HOME%/.ctags" by default. ** Search with exclusion patterns All commands support exclusion patterns from [[https://github.com/abo-abo/swiper][ivy]].
You can filter the candidates with =keyword1 !keyword2 keyword3=. So only candidate containing =keyword1= but neither =keyword2= nor =keyword3= are displayed.
You can press =C-c C-o= or =M-x ivy-occur= to export candidates to a buffer.
In summary, all functionalities from [[https://github.com/abo-abo/swiper][ivy]] are supported. ** Grep program If [[https://github.com/BurntSushi/ripgrep][ripgrep]] is installed, it's used as faster grep program. Or else we fallback to =grep=.
Use =M-x counsel-etags-grep= to grep in project root which is automatically detected. If current file is org file, current node or parent node's property =GREP_PROJECT_ROOT= is read to get the root directory to grep.
Set =counsel-etags-grep-extra-arguments= to add extra arguments for grep.
Use =M-x counsel-etags-grep-current-directory= to grep current directory.
Use =C-u num M-x counsel-etags-grep-current-directory= to grep NUM level up of current directory. If NUM is nil or 0, current directory is searched.
Grep result is sorted by string distance of current file path and candidate file path. The sorting is enabled in Emacs 27+.
You can set =counsel-etags-sort-grep-result-p= to =nil= to disable sorting. ** Customize grep keyword Users could set =counsel-etags-convert-grep-keyword= to customize grep keyword.
For example, below setup enable =counsel-etags-grep= to search Chinese using [[https://github.com/cute-jumper/pinyinlib.el][pinyinlib]],
(unless (featurep 'pinyinlib) (require 'pinyinlib)) (setq counsel-etags-convert-grep-keyword (lambda (keyword) (if (and keyword (> (length keyword) 0)) (pinyinlib-build-regexp-string keyword t) keyword)))
Or create a new grep command =my-grep-by-pinyin=,
(defun my-grep-by-pinyin () (interactive) (unless (featurep 'pinyinlib) (require 'pinyinlib)) (let* ((counsel-etags-convert-grep-keyword (lambda (keyword) (if (and keyword (> (length keyword) 0)) (pinyinlib-build-regexp-string keyword t) keyword)))) (counsel-etags-grep)))
** Windows Installing Cygwin and its package Ctags on any driver is all you need to do. No extra setup is required.
But you could still set up =counsel-etags-find-program=, =counsel-etags-ctags-program=, and =counsel-etags-grep-program= to specify the command line program path. ** ~/.ctags.exuberant If base configuration file "~/.ctags.exuberant" exists, it's used to generate "~/.ctags" automatically.
"~/.ctags.exuberant" is in Exuberant Ctags format, but the "~/.ctags" could be in Universal Ctags format if Universal Ctags is used.
You can customize =counsel-etags-ctags-options-base= to change the path of base configuration file. ** Use Ctags to generate Imenu items Run =M-x counsel-etags-list-tag-in-current-file= to list tags in current file.
You can also use native imenu command with below setup,
(setq imenu-create-index-function 'counsel-etags-imenu-default-create-index-function)
Set =counsel-etags-imenu-excluded-names= to exclude imenu items by name.
Set =counsel-etags-imenu-excluded-types to exclude imenu items by type.
** Step 1, a toy C project Run below script in Bash shell to create a toy project.
mkdir -p ~/proj1 && cd ~/proj1
cat > .dir-locals.el <<EOF
((nil . ((counsel-etags-project-root . "$PWD")
(counsel-etags-extra-tags-files . ("./include/TAGS")))))
EOF
cat > hello.c <<EOF
include
void fn() { }
int main() { printf('hello world'); fn(); return 0; } EOF mkdir -p include && cd include && find /usr/include | ctags -e -L -
** Step 2, navigate code Open =hello.c= in Emacs (say "YES" if Emacs ask any question), move focus over symbol "fn" or "printf", run =counsel-etags-find-tag-at-point=.