counsel-etags
Fast, energy-saving, and powerful code navigation solution.
It’s been tested on Linux/Windows/macOS.
Table of Content
- counsel-etags
- Install
- Usage
- Tips (OPTIONAL)
- Jump back
- Native Windows Emacs
- “.gitignore” and “.hgignore” are respected
- Set up with use-package
- Insert extra content into tags file after it’s updated
- Configuration file
- Ignore directories and files
- Dependency on Emacs APIs is minimum
- Specify multiple tags files
- Auto update tags file
- Rust programming language
- List all tags
- Two-step tag matching using regular expression and filter
- Force update current tags file
- Open recent tag
- Ctags setup
- Search with exclusion patterns
- Grep program
- Customize grep keyword
- Windows
- ~/.ctags.exuberant
- Use Ctags to generate Imenu items
- Step by step guide
- Bug Report
Install
Please install counsel-etags
from MELPA.
If Exuberant Ctags or 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 etags bundled with Emacs is not supported anymore.
It’s reported “Exuberant Ctags” v5.8.5 is buggy.
Usage
Run M-x counsel-etags-find-tag-at-point
to navigate code without any setup.
This command will:
- Find project root folder and scan code automatically
- Find correct tag automatically
- If no tag is find,
ripgrep
orgrep
is automatically called
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 in your own way.
Please read Step by step guide for more details.
Tips (OPTIONAL)
Jump back
Run M-x pop-tag-mark
to jump back.
Native Windows Emacs
The grep program path on Native Windows Emacs uses either forward slash or backward slash. Like “C:/rg.exe” or “C:\\rg.exe”.
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.
use-package
Set up withPlease 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 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 site:github.com”. 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 commands support exclusion patterns from 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 ivy are supported.
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 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 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 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.
#!/bin/bash
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 <stdio.h>
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
.
Bug Report
Report bugs to https://github.com/redguardtoo/counsel-etags.