patternMinor
Emacs Etags Shortcut Functions
Viewed 0 times
etagsshortcutfunctionsemacs
Problem
A few functions to let me manage TAGS files more easily. Typically, my projects contain at least one sub-folder. I got sick of manually updating, so I wrote this to help me update a single TAGS file per project (always in the projects' root directory).
Please share your thoughts (aside from "Why are you using Emacs?", you're not likely to change my mind).
One thing I've noticed that could trip me up is that it only tags files of one language (and if I had a project that had both Haskell and JS code, for example, I could tag one or the other). I would particularly appreciate advice on better ways to write find-parent-tags than just building each path from available sub-strings.
``
(shell-command (concat "find -name \"*." file-type "\" -print | etags -")))))
(defun find-parent-tags (dir)
"Traverses the directory tree up to /home/[user]/ or / whichever comes first.
Returns either nil or the directory containing the first TAGS file it finds."
(interactive (list default-directory))
(find-parent-tags-rec (build-tag-paths dir)))
(defun find-parent-tags-rec (list-of-filepath)
(cond ((null list-of-filepath) nil)
((file-exists-p (car list-of-filepath)) (car list-of-filepath))
(t (find-parent-tags-rec (cdr list-of-filepath)))))
(defun build-tag-paths (dir-string)
(build-tag-paths-rec (remove-if #'empty-string? (split-string dir-string "/")) (list "/")))
(defun build-tag-paths-rec (steps acc)
(if (null steps)
Please share your thoughts (aside from "Why are you using Emacs?", you're not likely to change my mind).
One thing I've noticed that could trip me up is that it only tags files of one language (and if I had a project that had both Haskell and JS code, for example, I could tag one or the other). I would particularly appreciate advice on better ways to write find-parent-tags than just building each path from available sub-strings.
``
(defun create-tag-table ()
"This will recursively tag all files of a given type starting with the current buffers' directory.
It overwrites the old TAGS file, if one exists.
Haskell files assume you've installed hasktags."
(interactive)
(let ((file-type (get-taggable-extension))
(cur-dir default-directory)
(tags-file (find-parent-tags default-directory)))
(if (equalp file-type "hs")
(shell-command "hasktags --ignore-close-implementation --etags find . -type f -name \".hs\"`")(shell-command (concat "find -name \"*." file-type "\" -print | etags -")))))
(defun find-parent-tags (dir)
"Traverses the directory tree up to /home/[user]/ or / whichever comes first.
Returns either nil or the directory containing the first TAGS file it finds."
(interactive (list default-directory))
(find-parent-tags-rec (build-tag-paths dir)))
(defun find-parent-tags-rec (list-of-filepath)
(cond ((null list-of-filepath) nil)
((file-exists-p (car list-of-filepath)) (car list-of-filepath))
(t (find-parent-tags-rec (cdr list-of-filepath)))))
(defun build-tag-paths (dir-string)
(build-tag-paths-rec (remove-if #'empty-string? (split-string dir-string "/")) (list "/")))
(defun build-tag-paths-rec (steps acc)
(if (null steps)
Solution
General notes
Finding the tags file
Emacs 23 has a function
Look up the directory hierarchy from
Stop at the first parent directory containing a file
and return the directory. Return
Your
The code you posted does nothing with the
From file type to shell commands
Rather than build in an exception for Haskell, the usual style would be to define an alist from extensions to commands.
However, there's a better approach than separate commands for each type; see below.
The shell commands
Your
A second problem is that if there are too many files, the command you wrote will die of a command line too long error; the one I just showed will run
An improvement to the
Above I mentioned a different approach to choosing the tag generation command. Your approach assumes that a given project will be in a single language. It's easy enough to cater for mixed-language projects: build a single tags file containing the output generated by
- Always prefix your functions with the name of your package or project, e.g.
tagariffic-create-tag-tableand so on. Emacs has no package scoping.
- Whenever possible (i.e. in the absence of mutual recursion), define your functions before using them. It's a lot clearer for the reader. It's also necessary if you ever define macros.
- You're using some functions from the
clpackage, so you should start with(require 'cl).
- The usual style for predicates in Lisp is
name-p, notname?(that's Scheme).
- Emacs has functions to manipulate file and directory names. Use them rather than going around concatenating slashes; this way your code will work on exotic platforms such as Windows.
Finding the tags file
Emacs 23 has a function
locate-dominating-file (in files.el).Look up the directory hierarchy from
FILE for a file named NAME.Stop at the first parent directory containing a file
NAME,and return the directory. Return
nil if not found.Your
(find-parent-tags default-directory) is almost equivalent to (locate-dominating-file (buffer-file-name) "TAGS"). If you want to use the current directory rather than the directory of the current buffer's file (they're almost always the same), use (locate-dominating-file (concat (default-directory) ".") "TAGS"). locate-dominating-file stops at your home directory, too.The code you posted does nothing with the
tags-file variable. Did you mean to run the tag generation command in that directory, or failing that in the current directory? If so, this should do it:(let ((default-directory (if tags-file (file-name-directory tags-file) default-directory)))
(shell-command …))From file type to shell commands
Rather than build in an exception for Haskell, the usual style would be to define an alist from extensions to commands.
(defvar tagariffic-command-alist
'(("hs" . "… hasktags …")
(t . "… etags …")))However, there's a better approach than separate commands for each type; see below.
The shell commands
Your
hasktags command will choke on file or directory names that contain shell special characters: \[?* and whitespace. This isn't very common in source trees, but it could happen. Generally speaking, never use command substitution to generate a list of file names. Instead, let find call the command.find . -type f -name '*.hs' -exec hasktags --ignore-close-implementation --etags {} +A second problem is that if there are too many files, the command you wrote will die of a command line too long error; the one I just showed will run
hasktags several times with command lines of a permitted size, causing the tag file to be overwritten. As a workaround, start by creating an empty tags file and tell hasktags to append to the tags file.: >TAGS
find . -type f -name '*.hs' -exec hasktags --ignore-close-implementation -e -a {} +An improvement to the
find commands would be to ignore certain directories belonging to version control systems. Especially with svn, this could speed things up.find . -type d \( -name .bzr -o -name .git -o -name .hg -o -name .svn -o -name CVS -o -name _darcs \) -prune -o \
-type f …Above I mentioned a different approach to choosing the tag generation command. Your approach assumes that a given project will be in a single language. It's easy enough to cater for mixed-language projects: build a single tags file containing the output generated by
etags and hasktags.: >TAGS
find . -type d \( -name .bzr -o … \) -prune -o \
-type f -name '*.hs' -exec hasktags -e -a {} + -o \
-type f \( -name '*.[CScls]' -o -name '*.el' -o … \) -exec etags {} +Code Snippets
(let ((default-directory (if tags-file (file-name-directory tags-file) default-directory)))
(shell-command …))(defvar tagariffic-command-alist
'(("hs" . "… hasktags …")
(t . "… etags …")))find . -type f -name '*.hs' -exec hasktags --ignore-close-implementation --etags {} +: >TAGS
find . -type f -name '*.hs' -exec hasktags --ignore-close-implementation -e -a {} +find . -type d \( -name .bzr -o -name .git -o -name .hg -o -name .svn -o -name CVS -o -name _darcs \) -prune -o \
-type f …Context
StackExchange Code Review Q#45, answer score: 6
Revisions (0)
No revisions yet.