Skip to content
Go to file


Failed to load latest commit information.
Latest commit message
Commit time


Fast, energy-saving, and powerful code navigation solution.

It’s been tested on Linux/Windows/macOS.


Table of Content


You can install counsel-etags from MELPA with package.el (M-x package-install counsel-etags).

If Exuberant Ctags or Universal Ctags exists, this program works out of box. Universal Ctags is actively maintained right now.

Or else, customize counsel-etags-update-tags-backend to create tags file with your own CLI. Please note etags bundled with Emacs is not supported any more.

It’s reported “Exuberant Ctags” v5.8.5 is buggy.


Run M-x counsel-etags-find-tag-at-point to navigate in code files without any setup.

This command will:

  • Find project root folder and scan code automatically
  • Find correct tag automatically
  • If no tag is find, it runs ripgrep or grep automatically by calling counsel-etags-fallback-grep-function

That’s it.

Please note it takes time to parse tags file which contains long lines. It’s the known issue of Emacs Lisp. The program itself is fine.

If you prefer manually setup tags file, you only need run M-x counsel-etags-scan-code once or create tags file in your own way.

Please read Step by step guide for more details.

Run M-x counsel-etags-list-tag-in-current-file to list tags in current file.

Or just use native imenu command with below setup,

(setq imenu-create-index-function 'counsel-etags-imenu-default-create-index-function)

You can set counsel-etags-imenu-excluded-names to exclude imenu items by name.

You can set =counsel-etags-imenu-excluded-types to exclude imenu items by type.


“.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 use-package

Please place add-hook code inside :init section,

(use-package counsel-etags
  :ensure t
  :bind (("C-]" . counsel-etags-find-tag-at-point))
  (add-hook 'prog-mode-hook
        (lambda ()
          (add-hook 'after-save-hook
            'counsel-etags-virtual-update-tags 'append 'local)))
  (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 gets word at point.

User could append the extra content into tags file in counsel-etags-after-update-tags-hook.

The parameter of hook is full path of the tags file. counsel-etags-tags-line is a tool function to help user.

Configuration file

Path of the Configuration file is defined in counsel-etags-ctags-options-file. Its default 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

Any tag related API or variable is not used.

Neither tags-file-name nor tags-table-list is used.

Any commands (visit-tags-table, xref-find-references, …) mentioned by other online tutorials are not used.

I intend to keep this package is completely independent.

Specify multiple tags files

counsel-etags-extra-tags-files contains extra tags file to parse.

Set it like (setq counsel-etags-extra-tags-files '("/usr/include/TAGS" "/usr/local/include/TAGS"))

Files in counsel-etags-extra-tags-files should contain only tag with absolute path.

Here is a shell CLI to create tags file:

ctags -e -R

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 Rust programming language can be generated by 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 .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”. Here is 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 counsel-etags commands supports exclusion patterns from ivy.

You can filter the candidates with keyword1 !keyword2 keyword3. So we display only candidate containing keyword1 but neither keyword2 nor keyword3.

You can also press C-c C-o to create a buffer containing all candidates.

In summary, all functionality from powerful ivy are supported perfectly.

Grep program

If 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.

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 grepped

Grep result is sorted by string distance of current file path and candidate file path. The sorting happens 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 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)

Or create a new grep command my-grep-by-pinyin,

(defun my-grep-by-pinyin ()
  (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)


No extra setup is needed if you install Cygwin to its default location on any driver except make sure “Exuberant Ctags” has been installed through Cygwin. Or else, you could setup counsel-etags-find-program, counsel-etags-ctags-program, and counsel-etags-grep-program.


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.

Step by step guide

You need use Linux/Cygwin/MSYS2. It should be similar in macOS but I’m not sure whether the directory /usr/include exists.

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")))))
cat > hello.c <<EOF
include <stdio.h>

void fn() {

int main() {
    printf('hello world');
    return 0;
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.

Bug Report



Fast, energy-saving, and powerful code navigation solution



No packages published


You can’t perform that action at this time.