Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/redguardtoo/counsel-etags

Fast, energy-saving, and powerful code navigation solution
https://github.com/redguardtoo/counsel-etags

Last synced: about 2 months ago
JSON representation

Fast, energy-saving, and powerful code navigation solution

Awesome Lists containing this project

README

        

* counsel-etags
[[https://github.com/redguardtoo/counsel-etags/actions/workflows/test.yml][https://github.com/redguardtoo/counsel-etags/actions/workflows/test.yml/badge.svg]]
[[http://melpa.org/#/counsel-etags][file:http://melpa.org/packages/counsel-etags-badge.svg]]
[[http://stable.melpa.org/#/counsel-etags][file:http://stable.melpa.org/packages/counsel-etags-badge.svg]]

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)]]
- [[#jump-back][Jump back]]
- [[#native-windows-emacs][Native Windows Emacs]]
- [[#gitignore-and-hgignore-are-respected][".gitignore" and ".hgignore" are respected]]
- [[#set-up-with-use-package][Set up with use-package]]
- [[#insert-extra-content-into-tags-file-after-its-updated][Insert extra content into tags file after it's updated]]
- [[#configuration-file][Configuration file]]
- [[#ignore-directories-and-files][Ignore directories and files]]
- [[#dependency-on-emacs-apis-is-minimum][Dependency on Emacs APIs is minimum]]
- [[#specify-multiple-tags-files][Specify multiple tags files]]
- [[#auto-update-tags-file][Auto update tags file]]
- [[#rust-programming-language][Rust programming language]]
- [[#list-all-tags][List all tags]]
- [[#two-step-tag-matching-using-regular-expression-and-filter][Two-step tag matching using regular expression and filter]]
- [[#force-update-current-tags-file][Force update current tags file]]
- [[#open-recent-tag][Open recent tag]]
- [[#ctags-setup][Ctags setup]]
- [[#search-with-exclusion-patterns][Search with exclusion patterns]]
- [[#grep-program][Grep program]]
- [[#customize-grep-keyword][Customize grep keyword]]
- [[#windows][Windows]]
- [[#ctagsexuberant][~/.ctags.exuberant]]
- [[#use-ctags-to-generate-imenu-items][Use Ctags to generate Imenu items]]
- [[#step-by-step-guide][Step by step guide]]
- [[#step-1-a-toy-c-project][Step 1, a toy C project]]
- [[#step-2-navigate-code][Step 2, navigate code]]
- [[#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.

* 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= or =grep= 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 [[https://www.emacswiki.org/emacs/BuildTags][in your own way]].

Please read [[#step-by-step-guide][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.
** Set up with [[https://github.com/jwiegley/use-package][use-package]]
Please place =add-hook= code inside =:init= section,
#+begin_src elisp
(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))
#+end_src
** 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,
#+begin_src elisp
(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)
#+end_src
** 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=,
#+begin_src elisp
(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))
#+end_src
** 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,
#+begin_src elisp
(setq counsel-etags-extra-tags-files '("/usr/include/TAGS" "/usr/local/include/TAGS"))
#+end_src

Files in =counsel-etags-extra-tags-files= should have symbols with absolute path only.
** Auto update tags file
#+begin_src elisp
;; 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)))
#+end_src
You can change callback =counsel-etags-update-tags-backend= to update tags file using your own solution,
#+begin_src elisp
(setq counsel-etags-update-tags-backend (lambda (src-dir) (shell-command "/usr/bin/ctags -e -R")))
#+end_src
** 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,
#+begin_src elisp
((nil . ((counsel-etags-update-tags-backend . (lambda (src-dir) (shell-command "rusty-tags emacs")))
(counsel-etags-tags-file-name . "rusty-tags.emacs"))))
#+end_src
** 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,
#+begin_src sh
ctags --options="$HOME/.ctags" -e -R
#+end_src

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]],
#+begin_src elisp
(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)))
#+end_src

Or create a new grep command =my-grep-by-pinyin=,
#+begin_src elisp
(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)))
#+end_src
** 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,
#+begin_src elisp
(setq imenu-create-index-function 'counsel-etags-imenu-default-create-index-function)
#+end_src

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.
#+begin_src bash
#!/bin/bash
mkdir -p ~/proj1 && cd ~/proj1
cat > .dir-locals.el < hello.c <

void fn() {
}

int main() {
printf('hello world');
fn();
return 0;
}
EOF
mkdir -p include && cd include && find /usr/include | ctags -e -L -
#+end_src

** 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]].