Commit acd1dd5d authored by Lev Lamberov's avatar Lev Lamberov

New upstream version 1.3

parents
language: generic
sudo: false
before_install:
- curl -fsSkL https://gist.github.com/rejeep/ebcd57c3af83b049833b/raw > x.sh && source ./x.sh
- evm install $EVM_EMACS --use --skip
- cask
env:
- EVM_EMACS=emacs-24.4-travis
- EVM_EMACS=emacs-24.5-travis
- EVM_EMACS=emacs-25.1-travis
- EVM_EMACS=emacs-git-snapshot-travis
script:
- emacs --version
- make test
- ./bench.sh
notifications:
email: false
matrix:
fast_finish: true
allow_failures:
- env: EVM_EMACS=emacs-git-snapshot-travis
v1.3:
* Refs buffers now have names of the form `*refs: foo*`.
* Fixed an issue with a dependency on a loop.el version that doesn't
exist.
v1.2:
* You can now filter search results to a directory. This is useful
when working on large elisp codebases, and it's faster too.
* Results buffers now include a link to describe the thing being
searched for.
v1.1:
* Rebranded to elisp-refs.
* Commands are now autoloaded.
* Added examples to the readme of cases that we can't detect.
* Sharp-quoted function references are now highlighted with context.
* Give more feedback on first run, when we're decompressing .el.gz
files.
* Searches now default to the symbol at point.
v1.0: Initial release.
(source melpa)
(package-file "elisp-refs.el")
(development
(depends-on "shut-up")
(depends-on "ert-runner")
(depends-on "undercover")
(depends-on "f"))
CASK ?= cask
EMACS ?= emacs
all: test
test: unit
unit:
${CASK} exec ert-runner
install:
${CASK} install
bench:
./bench.sh
# elisp-refs
[![Build Status](https://travis-ci.org/Wilfred/elisp-refs.svg?branch=master)](https://travis-ci.org/Wilfred/elisp-refs)
[![Coverage Status](https://coveralls.io/repos/github/Wilfred/elisp-refs/badge.svg?branch=master)](https://coveralls.io/github/Wilfred/elisp-refs?branch=master)
[![MELPA](http://melpa.org/packages/elisp-refs-badge.svg)](http://melpa.org/#/elisp-refs)
elisp-refs is an intelligent code search for Emacs lisp.
It can find references to functions, macros or variables. Unlike a
dumb text search, elisp-refs actually parses the code, so it's never
confused by comments or variables with the same name as functions.
![screenshot](refs_screenshot.png)
This is particularly useful for finding all the places a function is
used, or finding examples of usage.
Interested readers may enjoy my blog post:
[Searching A Million Lines Of Lisp](http://www.wilfred.me.uk/blog/2016/09/30/searching-a-million-lines-of-lisp/).
## Installation
Install from MELPA (recommended) or just add elisp-refs to your `load-path`.
## Commands available
* `elisp-refs-function` (find function calls)
* `elisp-refs-macro` (find macro calls)
* `elisp-refs-variable` (find variable references)
* `elisp-refs-special` (find special form calls)
* `elisp-refs-symbol` (find all references to a symbol)
These command search all the files currently loaded in your Emacs
instance.
If called with a prefix, you can limit search results to specific
directories. For example:
`C-u M-x elisp-refs-macro RET pcase RET ~/.emacs.d/elpa/magit-20160927.510 RET`
will search for uses of `pcase` in magit:
![filtering screenshot](refs_filtered.png)
## Semantic analysis
elisp-refs has *street smarts*: given `(defun foo (bar) (baz))`, it
understands that `bar` is a variable and `baz` is a function.
elisp-refs understands the following forms:
* `defun` `defsubst` `defmacro` `cl-defun`
* `lambda`
* `let` `let*`
* `funcall` `apply`
* sharp quoted expressions (e.g. `#'some-func`)
## Limitations
elisp-refs understands elisp special forms, and a few common
macros. However, it **cannot understand arbitrary macros**.
Therefore elisp-refs will assume that `(other-macro (foo bar))` is a
function call to `foo`. If this is incorrect, you may wish to use the
command `elisp-refs-symbol` to find all references to the `foo` symbol.
If `other-macro` is a common macro, please consider submitting a patch
to `elisp-refs--function-p` to make elisp-refs smarter.
elisp-refs also does not support **indirect calls**.
``` emacs-lisp
;; Since we do a simple syntax tree walk, this isn't treated as a
;; call to foo.
(let ((x (symbol-function 'foo)))
(funcall x))
;; Similarly, indirect function calls are not treated as
;; function calls.
(defun call-func (x)
(funcall x))
(call-func 'foo)
;; However, if you use sharp quoting, elisp-refs knows it's a function
reference!
(call-func #'foo)
```
## Running tests
You can run the tests with:
```
$ cask install
$ cask exec ert-runner
```
## Performance
elisp-refs is CPU-intensive elisp and has been carefully optimised. You
can run the benchmark script with:
```
$ cask install
$ ./bench.sh
```
New features are carefully measured to ensure performance does not get
worse.
See elisp-refs-bench.el for more details.
## Alternative Projects
**xref-find-references**: This command is included in Emacs 25.1, but
it's based on a text search. It is confused by comments and strings,
and cannot distinguish between functions and variables.
xrefs-find-references is also line oriented, so it does not show the
whole sexp that matched your search. Since it requires text files,
it doesn't search built-in .el.gz files.
**TAGS**: It is possible to record function references in TAGS
files. Whilst [universal-ctags](https://github.com/universal-ctags/ctags) (formerly
known as exuberant-ctags) does provide the ability to find references,
it is not supported in its lisp parser.
etags, the TAGS implementation shipped with Emacs, cannot find
references (to my knowledge).
**[el-search](https://elpa.gnu.org/packages/el-search.html)** allows
you to search for arbitrary forms in elisp files. It's slower, but a
much more general tool. Its design greatly influenced elisp-refs.
**[elisp-slime-nav](https://github.com/purcell/elisp-slime-nav)**
finds definitions, not references. It's a great complementary tool.
#!/bin/bash
set -ex
cask eval "(progn (require 'elisp-refs-bench) (elisp-refs-bench))"
;;; elisp-refs-bench.el --- measure elisp-refs.el performance
;;; Code:
(require 'elisp-refs)
(require 'dash)
(require 'shut-up)
(defmacro elisp-refs--print-time (form)
"Evaluate FORM, and print the time taken."
`(progn
(message "Timing %s" ',form)
(-let [(total-time gc-runs gc-time)
(shut-up (benchmark-run 1 ,form))]
(message "Elapsed time: %fs (%fs in %d GCs)"
total-time
gc-time
gc-runs))))
;; TODO: benchmark elisp-refs-variable (and add a smoke test)
;; TODO: make this more representative by loading more elisp files
;; before searching. Running this in a GUI is also conspicuously
;; slower, which bench.sh doesn't reflect.
(defun elisp-refs-bench ()
"Measure runtime of searching."
(interactive)
(elisp-refs--report-loc)
;; Measure a fairly uncommon function.
(elisp-refs--print-time (elisp-refs-function 'mod))
;; Measure a common macro
(elisp-refs--print-time (elisp-refs-macro 'when))
;; Compare with searching for the same symbol without walking
(elisp-refs--print-time (elisp-refs-symbol 'when))
;; Synthetic test of a large number of results.
(message "Formatting 10,000 results")
(let ((forms (-repeat 10000 (list '(ignored) 1 64)))
(buf (generate-new-buffer " *dummy-elisp-refs-buf*")))
(with-current-buffer buf
(insert "(defun foo (bar) (if bar nil (with-current-buffer bar))) ;; blah")
(setq-local elisp-refs--path "/tmp/foo.el"))
(elisp-refs--print-time
(elisp-refs--show-results 'foo "foo bar" (list (cons forms buf))
20 nil))
(kill-buffer buf)))
(defun elisp-refs--report-loc ()
"Report the total number of lines of code searched."
(interactive)
(let* ((loaded-paths (elisp-refs--loaded-paths))
(loaded-src-bufs (shut-up (-map #'elisp-refs--contents-buffer loaded-paths)))
(total-lines (-sum (--map (with-current-buffer it
(line-number-at-pos (point-max)))
loaded-src-bufs))))
;; Clean up temporary buffers.
(--each loaded-src-bufs (kill-buffer it))
(message "Total LOC: %s" (elisp-refs--format-int total-lines))))
(provide 'elisp-refs-bench)
;;; elisp-refs-bench.el ends here
This diff is collapsed.
;;; test-helper.el --- Helper for tests -*- lexical-binding: t; -*-
;; Copyright (C) 2016 Wilfred Hughes
;; Author: <me@wilfred.me.uk>
;;; Code:
(require 'ert)
(require 'f)
(let ((elisp-refs-dir (f-parent (f-dirname (f-this-file)))))
(add-to-list 'load-path elisp-refs-dir))
(require 'undercover)
(undercover "elisp-refs.el"
(:exclude "*-test.el")
(:report-file "/tmp/undercover-report.json"))
;;; test-helper.el ends here
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment