Squashed 'emacs-racer/' content from commit 6e0d1b3

git-subtree-dir: emacs-racer
git-subtree-split: 6e0d1b3ebd54497c0cc995a92f09328ff101cd33
This commit is contained in:
Wojciech Kozlowski 2017-09-09 00:25:09 +01:00
commit ccb24dd46e
13 changed files with 1342 additions and 0 deletions

1
.ert-runner Normal file
View File

@ -0,0 +1 @@
-L .

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.cask

22
.travis.yml Normal file
View File

@ -0,0 +1,22 @@
language: generic
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.3-travis
- 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
notifications:
email: false
matrix:
fast_finish: true
allow_failures:
- env: EVM_EMACS=emacs-git-snapshot-travis

51
CHANGELOG.md Normal file
View File

@ -0,0 +1,51 @@
# v1.3 (unreleased)
* `racer-rust-src-path` is now set automatically by default.
* New simpler installation instructions based on `rustup`.
* Fixed an issue with racer completion in indirect buffers.
# v1.2
* Added the command `racer-debug` to help users diagnose issues.
* We now explicitly try `~/.cargo/bin/racer` if `racer` isn't on path.
* We no longer offer completions inside comments by default (it tends
to be slow and rarely offers completions). See
`racer-complete-in-comments`.
* Eldoc descriptions of modules now abbreviate the path relative to
the project and the user's home directory.
* Several improvements to markdown rendering in `racer-describe`.
# v1.1
* Fixed a crash when point is at the beginning of buffer.
* Fixed a crash when not in a cargo project.
* Added `racer-cargo-home`, which enables completion for cargo crates.
* Various improvements to formatting of completion candidates.
* Added `racer-describe`.
# v1.0.2
* Trigger completions after `::` or `.`.
* Compatibility with latest company
* Fixed an issue where TAGS from other projects were also completion
candidates
# v1.0.1
No changes since v0.0.2.
This release was created to [work around an issue
where MELPA stable](https://github.com/milkypostman/melpa/issues/3205)
had created a v1.0.0 from an early version of racer.el
# v0.0.2
Initial release. Includes:
* Code completion with company
* Jump to definition
* Eldoc
Early users who are using `racer-activate` or `racer-turn-on-eldoc`
should use `racer-mode` and `eldoc-mode` instead. The former have been
deprecated.

14
Cask Normal file
View File

@ -0,0 +1,14 @@
(source gnu)
(source melpa)
(package-file "racer.el")
(depends-on "company")
(depends-on "dash")
(depends-on "s")
(depends-on "f")
(depends-on "rust-mode")
(development
(depends-on "ert-runner")
(depends-on "undercover"))

21
Makefile Normal file
View File

@ -0,0 +1,21 @@
EMACS ?= emacs
CASK ?= cask
all: test
test: clean-elc
${MAKE} unit
${MAKE} compile
${MAKE} unit
${MAKE} clean-elc
unit:
${CASK} exec ert-runner
compile:
${CASK} exec ${EMACS} -Q -batch -f batch-byte-compile racer.el
clean-elc:
rm -f racer.elc
.PHONY: all test unit compile

112
README.md Normal file
View File

@ -0,0 +1,112 @@
# Racer for Emacs
[![MELPA](http://melpa.org/packages/racer-badge.svg)](http://melpa.org/#/racer)
[![MELPA Stable](http://stable.melpa.org/packages/racer-badge.svg)](http://stable.melpa.org/#/racer)
[![Build Status](https://travis-ci.org/racer-rust/emacs-racer.svg?branch=master)](https://travis-ci.org/racer-rust/emacs-racer)
[![Coverage Status](https://coveralls.io/repos/github/racer-rust/emacs-racer/badge.svg?branch=master)](https://coveralls.io/github/racer-rust/emacs-racer?branch=master)
This is the official Emacs package for
[Racer](http://github.com/phildawes/racer).
<!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-generate-toc again -->
**Table of Contents**
- [Racer for Emacs](#racer-for-emacs)
- [Completion](#completion)
- [Find Definitions](#find-definitions)
- [Describe Functions and Types](#describe-functions-and-types)
- [Installation](#installation)
- [Testing your setup](#testing-your-setup)
- [Tests](#tests)
<!-- markdown-toc end -->
## Completion
racer.el supports code completion of variables, functions and modules.
![racer completion screenshot](images/racer_completion.png)
You can also press <kbd>F1</kbd> to pop up a help buffer for the current
completion candidate.
Note that due to a
[limitation of racer](https://github.com/phildawes/racer/issues/389),
racer.el cannot offer completion for macros.
## Find Definitions
racer.el can jump to definition of functions and types.
![racer go to definition](images/racer_goto.gif)
You can use <kbd>M-.</kbd> to go to the definition, and <kbd>M-,</kbd>
to go back.
## Describe Functions and Types
racer.el can show a help buffer based on the docstring of the thing at
point.
![racer completion screenshot](images/racer_help.png)
Use <kbd>M-x racer-describe</kbd> to open the help buffer.
## Installation
1. Install [Racer](http://github.com/phildawes/racer) and download the
source code of Rust:
```
$ rustup component add rust-src
$ cargo install racer
```
2. Allow Emacs to install packages from MELPA:
```el
(require 'package)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/"))
```
3. Install the Emacs package for Racer: `M-x package-install RET racer RET`
4. Configure Emacs to activate racer when rust-mode starts:
```el
(add-hook 'rust-mode-hook #'racer-mode)
(add-hook 'racer-mode-hook #'eldoc-mode)
```
For completions, install company with `M-x package-install RET company RET`. A sample configuration:
```el
(add-hook 'racer-mode-hook #'company-mode)
(require 'rust-mode)
(define-key rust-mode-map (kbd "TAB") #'company-indent-or-complete-common)
(setq company-tooltip-align-annotations t)
```
For automatic completions, customize `company-idle-delay` and
`company-minimum-prefix-length`.
### Testing your setup
To test **completion**: Open a rust file and try typing ```use
std::io::B``` and press <kbd>TAB</kbd>.
To test **go to definition**: Place your cursor over a symbol and press
`M-.` to jump to its definition.
Press `M-,` to jump back to the previous cursor location.
If **it doesn't work**, try `M-x racer-debug` to see what command was
run and what output was returned.
## Tests
racer.el includes tests. To run them, you need to install
[Cask](https://github.com/cask/cask), then:
```
$ cask install
$ cask exec ert-runner
```

BIN
images/racer_completion.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
images/racer_goto.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

BIN
images/racer_help.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

768
racer.el Normal file
View File

@ -0,0 +1,768 @@
;;; racer.el --- code completion, goto-definition and docs browsing for Rust via racer -*- lexical-binding: t -*-
;; Copyright (c) 2014 Phil Dawes
;; Author: Phil Dawes
;; URL: https://github.com/racer-rust/emacs-racer
;; Version: 1.3
;; Package-Requires: ((emacs "24.3") (rust-mode "0.2.0") (dash "2.13.0") (s "1.10.0") (f "0.18.2"))
;; Keywords: abbrev, convenience, matching, rust, tools
;; This file is not part of GNU Emacs.
;; Permission is hereby granted, free of charge, to any
;; person obtaining a copy of this software and associated
;; documentation files (the "Software"), to deal in the
;; Software without restriction, including without
;; limitation the rights to use, copy, modify, merge,
;; publish, distribute, sublicense, and/or sell copies of
;; the Software, and to permit persons to whom the Software
;; is furnished to do so, subject to the following
;; conditions:
;; The above copyright notice and this permission notice
;; shall be included in all copies or substantial portions
;; of the Software.
;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
;; ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
;; TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
;; PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
;; SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
;; CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
;; OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
;; IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
;; DEALINGS IN THE SOFTWARE.
;;; Commentary:
;; Please see the readme for full documentation:
;; https://github.com/racer-rust/emacs-racer
;;; Quickstart:
;; You will need to configure Emacs to find racer:
;;
;; (setq racer-rust-src-path "<path-to-rust-srcdir>/src/")
;; (setq racer-cmd "<path-to-racer>/target/release/racer")
;;
;; To activate racer in Rust buffers, run:
;;
;; (add-hook 'rust-mode-hook #'racer-mode)
;;
;; You can also use racer to find definition at point via
;; `racer-find-definition', bound to `M-.' by default.
;;
;; Finally, you can also use Racer to show the signature of the
;; current function in the minibuffer:
;;
;; (add-hook 'racer-mode-hook #'eldoc-mode)
;;; Code:
(require 'dash)
(require 'etags)
(require 'rust-mode)
(require 's)
(require 'f)
(require 'thingatpt)
(require 'button)
(require 'help-mode)
(defgroup racer nil
"Code completion, goto-definition and docs browsing for Rust via racer."
:link '(url-link "https://github.com/racer-rust/emacs-racer/")
:group 'rust-mode)
(defcustom racer-cmd
(or (executable-find "racer")
(f-expand "~/.cargo/bin/racer")
"/usr/local/bin/racer")
"Path to the racer binary."
:type 'file
:group 'racer)
(defcustom racer-rust-src-path
(or
(getenv "RUST_SRC_PATH")
(when (executable-find "rustc")
(let* ((sysroot (s-trim-right
(shell-command-to-string
(format "%s --print sysroot" (executable-find "rustc")))))
(src-path (f-join sysroot "lib/rustlib/src/rust/src")))
(when (file-exists-p src-path)
src-path)
src-path))
"/usr/local/src/rust/src")
"Path to the rust source tree.
If nil, we will query $RUST_SRC_PATH at runtime.
If $RUST_SRC_PATH is not set, look for rust source in rustup's install directory."
:type 'file
:group 'racer)
(defcustom racer-cargo-home
(or
(getenv "CARGO_HOME")
"~/.cargo")
"Path to your current cargo home. Usually `~/.cargo'.
If nil, we will query $CARGO_HOME at runtime."
:type 'file
:group 'racer)
(defun racer--cargo-project-root ()
"Find the root of the current Cargo project."
(let ((root (locate-dominating-file (or (buffer-file-name (buffer-base-buffer)) default-directory)
"Cargo.toml")))
(and root (file-truename root))))
(defun racer--header (text)
"Helper function for adding text properties to TEXT."
(propertize text 'face 'racer-help-heading-face))
(defvar racer--prev-state nil)
(defun racer-debug ()
"Open a buffer describing the last racer command run.
Helps users find configuration issues, or file bugs on
racer or racer.el."
(interactive)
(unless racer--prev-state
(user-error "Must run a racer command before debugging"))
(let ((buf (get-buffer-create "*racer-debug*"))
(inhibit-read-only t))
(with-current-buffer buf
(erase-buffer)
(setq buffer-read-only t)
(let* ((process-environment
(plist-get racer--prev-state :process-environment))
(rust-src-path-used
(--first (s-prefix-p "RUST_SRC_PATH=" it) process-environment))
(cargo-home-used
(--first (s-prefix-p "CARGO_HOME=" it) process-environment))
(stdout (plist-get racer--prev-state :stdout))
(stderr (plist-get racer--prev-state :stderr)))
(insert
;; Summarise the actual command that we run.
(racer--header "The last racer command was:\n\n")
(format "$ cd %s\n"
(plist-get racer--prev-state :default-directory))
(format "$ export %s\n" cargo-home-used)
(format "$ export %s\n" rust-src-path-used)
(format "$ %s %s\n\n"
(plist-get racer--prev-state :program)
(s-join " " (plist-get racer--prev-state :args)))
;; Describe the exit code and outputs.
(racer--header
(format "This command terminated with exit code %s.\n\n"
(plist-get racer--prev-state :exit-code)))
(if (s-blank? stdout)
(racer--header "No output on stdout.\n\n")
(format "%s\n\n%s\n\n"
(racer--header "stdout:")
(s-trim-right stdout)))
(if (s-blank? stderr)
(racer--header "No output on stderr.\n\n")
(format "%s\n\n%s\n\n"
(racer--header "stderr:")
(s-trim-right stderr)))
;; Give copy-paste instructions for reproducing any errors
;; the user has seen.
(racer--header
(s-word-wrap 60 "The temporary file will have been deleted. You should be able to reproduce the same output from racer with the following command:\n\n"))
(format "$ %s %s %s %s\n\n" cargo-home-used rust-src-path-used
(plist-get racer--prev-state :program)
(s-join " "
(-drop-last 1 (plist-get racer--prev-state :args))))
;; Tell the user what to do next if they have problems.
(racer--header "Please report bugs ")
(racer--url-button "on GitHub" "https://github.com/racer-rust/emacs-racer/issues/new")
(racer--header "."))))
(switch-to-buffer buf)
(goto-char (point-min))))
(defun racer--call (command &rest args)
"Call racer command COMMAND with args ARGS.
Return stdout if COMMAND exits normally, otherwise show an
error."
(let ((rust-src-path (or racer-rust-src-path (getenv "RUST_SRC_PATH")))
(cargo-home (or racer-cargo-home (getenv "CARGO_HOME"))))
(when (null rust-src-path)
(user-error "You need to set `racer-rust-src-path' or `RUST_SRC_PATH'"))
(unless (file-exists-p rust-src-path)
(user-error "No such directory: %s. Please set `racer-rust-src-path' or `RUST_SRC_PATH'"
rust-src-path))
(let ((default-directory (or (racer--cargo-project-root) default-directory))
(process-environment (append (list
(format "RUST_SRC_PATH=%s" (expand-file-name rust-src-path))
(format "CARGO_HOME=%s" (expand-file-name cargo-home)))
process-environment)))
(-let [(exit-code stdout _stderr)
(racer--shell-command racer-cmd (cons command args))]
;; Use `equal' instead of `zero' as exit-code can be a string
;; "Aborted" if racer crashes.
(unless (equal 0 exit-code)
(user-error "%s exited with %s. `M-x racer-debug' for more info"
racer-cmd exit-code))
stdout))))
(defmacro racer--with-temporary-file (path-sym &rest body)
"Create a temporary file, and bind its path to PATH-SYM.
Evaluate BODY, then delete the temporary file."
(declare (indent 1) (debug (symbolp body)))
`(let ((,path-sym (make-temp-file "racer")))
(unwind-protect
(progn ,@body)
(delete-file ,path-sym))))
(defun racer--slurp (file)
"Return the contents of FILE as a string."
(with-temp-buffer
(insert-file-contents-literally file)
(buffer-string)))
(defun racer--shell-command (program args)
"Execute PROGRAM with ARGS.
Return a list (exit-code stdout stderr)."
(racer--with-temporary-file tmp-file-for-stderr
(let (exit-code stdout stderr)
;; Create a temporary buffer for `call-process` to write stdout
;; into.
(with-temp-buffer
(setq exit-code
(apply #'call-process program nil
(list (current-buffer) tmp-file-for-stderr)
nil args))
(setq stdout (buffer-string)))
(setq stderr (racer--slurp tmp-file-for-stderr))
(setq racer--prev-state
(list
:program program
:args args
:exit-code exit-code
:stdout stdout
:stderr stderr
:default-directory default-directory
:process-environment process-environment))
(list exit-code stdout stderr))))
(defun racer--call-at-point (command)
"Call racer command COMMAND at point of current buffer.
Return a list of all the lines returned by the command."
(racer--with-temporary-file tmp-file
(write-region nil nil tmp-file nil 'silent)
(s-lines
(s-trim-right
(racer--call command
(number-to-string (line-number-at-pos))
(number-to-string (racer--current-column))
(buffer-file-name (buffer-base-buffer))
tmp-file)))))
(defun racer--read-rust-string (string)
"Convert STRING, a rust string literal, to an elisp string."
(when string
(->> string
;; Remove outer double quotes.
(s-chop-prefix "\"")
(s-chop-suffix "\"")
;; Replace escaped characters.
(s-replace "\\n" "\n")
(s-replace "\\\"" "\"")
(s-replace "\\'" "'")
(s-replace "\\;" ";"))))
(defun racer--split-parts (raw-output)
"Given RAW-OUTPUT from racer, split on semicolons and doublequotes.
Unescape strings as necessary."
(let ((parts nil)
(current "")
(i 0))
(while (< i (length raw-output))
(let ((char (elt raw-output i))
(prev-char (and (> i 0) (elt raw-output (1- i)))))
(cond
;; A semicolon that wasn't escaped, start a new part.
((and (equal char ?\;) (not (equal prev-char ?\\)))
(push current parts)
(setq current ""))
(t
(setq current (concat current (string char))))))
(setq i (1+ i)))
(push current parts)
(mapcar #'racer--read-rust-string (nreverse parts))))
(defun racer--split-snippet-match (line)
"Given LINE, a string \"MATCH ...\" from complete-with-snippet,
split it into its constituent parts."
(let* ((match-parts (racer--split-parts line))
(docstring (nth 7 match-parts)))
(when (and match-parts (equal (length match-parts) 8))
(list :name (s-chop-prefix "MATCH " (nth 0 match-parts))
:line (string-to-number (nth 2 match-parts))
:column (string-to-number (nth 3 match-parts))
:path (nth 4 match-parts)
;; Struct or Function:
:kind (nth 5 match-parts)
:signature (nth 6 match-parts)
:docstring (if (> (length docstring) 0) docstring nil)))))
(defun racer--describe-at-point (name)
"Get a description of the symbol at point matching NAME.
If there are multiple possibilities with this NAME, prompt
the user to choose."
(let* ((output-lines (save-excursion
;; Move to the end of the current symbol, to
;; increase racer accuracy.
(skip-syntax-forward "w_")
(racer--call-at-point "complete-with-snippet")))
(all-matches (--map (when (s-starts-with-p "MATCH " it)
(racer--split-snippet-match it))
output-lines))
(relevant-matches (--filter (equal (plist-get it :name) name)
all-matches)))
(if (> (length relevant-matches) 1)
;; We might have multiple matches with the same name but
;; different types. E.g. Vec::from.
(let ((signature
(completing-read "Multiple matches: "
(--map (plist-get it :signature) relevant-matches))))
(--first (equal (plist-get it :signature) signature) relevant-matches))
(-first-item relevant-matches))))
(defun racer--help-buf (contents)
"Create a *Racer Help* buffer with CONTENTS."
(let ((buf (get-buffer-create "*Racer Help*"))
;; If the buffer already existed, we need to be able to
;; override `buffer-read-only'.
(inhibit-read-only t))
(with-current-buffer buf
(erase-buffer)
(insert contents)
(setq buffer-read-only t)
(goto-char (point-min))
(racer-help-mode))
buf))
(defface racer-help-heading-face
'((t :weight bold))
"Face for markdown headings in *Racer Help* buffers.")
(defun racer--url-p (target)
"Return t if TARGET looks like a fully qualified URL."
(not (null
(string-match-p (rx bol "http" (? "s") "://") target))))
(defun racer--propertize-links (markdown)
"Propertize links in MARKDOWN."
(replace-regexp-in-string
;; Text of the form [foo](http://example.com)
(rx "[" (group (+? (not (any "]")))) "](" (group (+? anything)) ")")
;; For every match:
(lambda (whole-match)
;; Extract link and target.
(let ((link-text (match-string 1 whole-match))
(link-target (match-string 2 whole-match)))
;; If it's a web URL, use a clickable link.
(if (racer--url-p link-target)
(racer--url-button link-text link-target)
;; Otherwise, just discard the target.
link-text)))
markdown))
(defun racer--propertize-all-inline-code (markdown)
"Given a single line MARKDOWN, replace all instances of `foo` or
\[`foo`\] with a propertized string."
(let ((highlight-group
(lambda (whole-match)
(racer--syntax-highlight (match-string 1 whole-match)))))
(->> markdown
(replace-regexp-in-string
(rx "[`" (group (+? anything)) "`]")
highlight-group)
(replace-regexp-in-string
(rx "`" (group (+? anything)) "`")
highlight-group))))
(defun racer--indent-block (str)
"Indent every line in STR."
(s-join "\n" (--map (concat " " it) (s-lines str))))
(defun racer--trim-newlines (str)
"Remove newlines from the start and end of STR."
(->> str
(s-chop-prefix "\n")
(s-chop-suffix "\n")))
(defun racer--remove-footnote-links (str)
"Remove footnote links from markdown STR."
(->> (s-lines str)
(--remove (string-match-p (rx bol "[`" (+? anything) "`]: ") it))
(s-join "\n")
;; Collapse consecutive blank lines caused by removing footnotes.
(s-replace "\n\n\n" "\n\n")))
(defun racer--docstring-sections (docstring)
"Split DOCSTRING into text, code and heading sections."
(let* ((sections nil)
(current-section-lines nil)
(section-type :text)
;; Helper function.
(finish-current-section
(lambda ()
(when current-section-lines
(let ((current-section
(s-join "\n" (nreverse current-section-lines))))
(unless (s-blank? current-section)
(push (list section-type current-section) sections))
(setq current-section-lines nil))))))
(dolist (line (s-lines docstring))
(cond
;; If this is a closing ```
((and (s-starts-with-p "```" line) (eq section-type :code))
(push line current-section-lines)
(funcall finish-current-section)
(setq section-type :text))
;; If this is an opening ```
((s-starts-with-p "```" line)
(funcall finish-current-section)
(push line current-section-lines)
(setq section-type :code))
;; Headings
((and (not (eq section-type :code)) (s-starts-with-p "# " line))
(funcall finish-current-section)
(push (list :heading line) sections))
;; Normal text.
(t
(push line current-section-lines))))
(funcall finish-current-section)
(nreverse sections)))
(defun racer--clean-code-section (section)
"Given a SECTION, a markdown code block, remove
fenced code delimiters and code annotations."
(->> (s-lines section)
(-drop 1)
(-drop-last 1)
;; Ignore annotations like # #[allow(dead_code)]
(--remove (s-starts-with-p "# " it))
(s-join "\n")))
(defun racer--propertize-docstring (docstring)
"Replace markdown syntax in DOCSTRING with text properties."
(let* ((sections (racer--docstring-sections docstring))
(propertized-sections
(--map (-let [(section-type section) it]
;; Remove trailing newlines, so we can ensure we
;; have consistent blank lines between sections.
(racer--trim-newlines
(pcase section-type
(:text
(racer--propertize-all-inline-code
(racer--propertize-links
(racer--remove-footnote-links
section))))
(:code
(racer--indent-block
(racer--syntax-highlight
(racer--clean-code-section section))))
(:heading
(racer--header
(s-chop-prefix "# " section))))))
sections)))
(s-join "\n\n" propertized-sections)))
(defun racer--find-file (path line column)
"Open PATH and move point to LINE and COLUMN."
(find-file path)
(goto-char (point-min))
(forward-line (1- line))
(forward-char column))
(defun racer--button-go-to-src (button)
(racer--find-file
(button-get button 'path)
(button-get button 'line)
(button-get button 'column)))
(define-button-type 'racer-src-button
'action 'racer--button-go-to-src
'follow-link t
'help-echo "Go to definition")
(defun racer--url-button (text url)
"Return a button that opens a browser at URL."
(with-temp-buffer
(insert-text-button
text
:type 'help-url
'help-args (list url))
(buffer-string)))
(defun racer--src-button (path line column)
"Return a button that navigates to PATH at LINE number and
COLUMN number."
;; Convert "/foo/bar/baz/foo.rs" to "baz/foo.rs"
(let* ((filename (f-filename path))
(parent-dir (f-filename (f-parent path)))
(short-path (f-join parent-dir filename)))
(with-temp-buffer
(insert-text-button
short-path
:type 'racer-src-button
'path path
'line line
'column column)
(buffer-string))))
(defun racer--kind-description (raw-kind)
"Human friendly description of a rust kind.
For example, 'EnumKind' -> 'an enum kind'."
(let* ((parts (s-split-words raw-kind))
(description (s-join " " (--map (downcase it) parts)))
(a (if (string-match-p (rx bos (or "a" "e" "i" "o" "u")) description)
"an" "a")))
(format "%s %s" a description)))
(defun racer--describe (name)
"Return a *Racer Help* buffer for the function or type at point.
If there are multiple candidates at point, use NAME to find the
correct value."
(let ((description (racer--describe-at-point name)))
(when description
(let* ((name (plist-get description :name))
(raw-docstring (plist-get description :docstring))
(docstring (if raw-docstring
(racer--propertize-docstring raw-docstring)
"Not documented."))
(kind (plist-get description :kind)))
(racer--help-buf
(format
"%s is %s defined in %s.\n\n%s%s"
name
(racer--kind-description kind)
(racer--src-button
(plist-get description :path)
(plist-get description :line)
(plist-get description :column))
(if (equal kind "Module")
;; No point showing the 'signature' of modules, which is
;; just their full path.
""
(format " %s\n\n" (racer--syntax-highlight (plist-get description :signature))))
docstring))))))
(defun racer-describe ()
"Show a *Racer Help* buffer for the function or type at point."
(interactive)
(let ((buf (racer--describe (thing-at-point 'symbol))))
(if buf
(temp-buffer-window-show buf)
(user-error "No function or type found at point"))))
(defvar racer-help-mode-map
(let ((map (make-sparse-keymap)))
(set-keymap-parent map (make-composed-keymap button-buffer-map
special-mode-map))
map)
"Keymap for racer help mode.")
(define-derived-mode racer-help-mode fundamental-mode
"Racer-Help"
"Major mode for *Racer Help* buffers.
Commands:
\\{racer-help-mode-map}")
(defcustom racer-complete-in-comments
nil
"If non-nil, query racer for completions inside comments too."
:type 'boolean
:group 'racer)
(defun racer-complete-at-point ()
"Complete the symbol at point."
(let* ((ppss (syntax-ppss))
(in-string (nth 3 ppss))
(in-comment (nth 4 ppss)))
(when (and
(not in-string)
(or (not in-comment) racer-complete-in-comments))
(let* ((bounds (bounds-of-thing-at-point 'symbol))
(beg (or (car bounds) (point)))
(end (or (cdr bounds) (point))))
(list beg end
(completion-table-dynamic #'racer-complete)
:annotation-function #'racer-complete--annotation
:company-prefix-length (racer-complete--prefix-p beg end)
:company-docsig #'racer-complete--docsig
:company-doc-buffer #'racer--describe
:company-location #'racer-complete--location)))))
(defun racer--file-and-parent (path)
"Convert /foo/bar/baz/q.txt to baz/q.txt."
(let ((file (f-filename path))
(parent (f-filename (f-parent path))))
(f-join parent file)))
(defun racer-complete (&optional _ignore)
"Completion candidates at point."
(->> (racer--call-at-point "complete")
(--filter (s-starts-with? "MATCH" it))
(--map (-let [(name line col file matchtype ctx)
(s-split-up-to "," (s-chop-prefix "MATCH " it) 5)]
(put-text-property 0 1 'line (string-to-number line) name)
(put-text-property 0 1 'col (string-to-number col) name)
(put-text-property 0 1 'file file name)
(put-text-property 0 1 'matchtype matchtype name)
(put-text-property 0 1 'ctx ctx name)
name))))
(defun racer--trim-up-to (needle s)
"Return content after the occurrence of NEEDLE in S."
(-if-let (idx (s-index-of needle s))
(substring s (+ idx (length needle)))
s))
(defun racer-complete--prefix-p (beg _end)
"Return t if a completion should be triggered for a prefix between BEG and END."
(save-excursion
(goto-char beg)
;; If we're at the beginning of the buffer, we can't look back 2
;; characters.
(ignore-errors
(looking-back "\\.\\|::" 2))))
(defun racer-complete--annotation (arg)
"Return an annotation for completion candidate ARG."
(let* ((ctx (get-text-property 0 'ctx arg))
(type (get-text-property 0 'matchtype arg))
(pretty-ctx
(pcase type
("Module"
(if (string= arg ctx)
""
(concat " " (racer--file-and-parent ctx))))
("StructField"
(concat " " ctx))
(_
(->> ctx
(racer--trim-up-to arg)
(s-chop-suffixes '(" {" "," ";")))))))
(format "%s : %s" pretty-ctx type)))
(defun racer-complete--docsig (arg)
"Return a signature for completion candidate ARG."
(racer--syntax-highlight (format "%s" (get-text-property 0 'ctx arg))))
(defun racer-complete--location (arg)
"Return location of completion candidate ARG."
(cons (get-text-property 0 'file arg)
(get-text-property 0 'line arg)))
(defun racer--current-column ()
"Get the current column based on underlying character representation."
(length (buffer-substring-no-properties
(line-beginning-position) (point))))
;;;###autoload
(defun racer-find-definition ()
"Run the racer find-definition command and process the results."
(interactive)
(-if-let (match (--first (s-starts-with? "MATCH" it)
(racer--call-at-point "find-definition")))
(-let [(_name line col file _matchtype _ctx)
(s-split-up-to "," (s-chop-prefix "MATCH " match) 5)]
(if (fboundp 'xref-push-marker-stack)
(xref-push-marker-stack)
(with-no-warnings
(ring-insert find-tag-marker-ring (point-marker))))
(racer--find-file file (string-to-number line) (string-to-number col)))
(error "No definition found")))
(defun racer--syntax-highlight (str)
"Apply font-lock properties to a string STR of Rust code."
(let (result)
;; Load all of STR in a rust-mode buffer, and use its
;; highlighting.
(with-temp-buffer
(insert str)
(delay-mode-hooks (rust-mode))
(if (fboundp 'font-lock-ensure)
(font-lock-ensure)
(with-no-warnings
(font-lock-fontify-buffer)))
(setq result (buffer-string)))
(when (and
;; If we haven't applied any properties yet,
(null (text-properties-at 0 result))
;; and if it's a standalone symbol, then assume it's a
;; variable.
(string-match-p (rx bos (+ (any lower "_")) eos) str))
(setq result (propertize str 'face 'font-lock-variable-name-face)))
result))
(defun racer--goto-func-name ()
"If point is inside a function call, move to the function name.
foo(bar, |baz); -> foo|(bar, baz);"
(let ((last-paren-pos (nth 1 (syntax-ppss)))
(start-pos (point)))
(when last-paren-pos
;; Move to just before the last paren.
(goto-char last-paren-pos)
;; If we're inside a round paren, we're inside a function call.
(unless (looking-at "(")
;; Otherwise, return to our start position, as point may have been on a
;; function already:
;; foo|(bar, baz);
(goto-char start-pos)))))
(defun racer--relative (path &optional directory)
"Return PATH relative to DIRECTORY (`default-directory' by default).
If PATH is not in DIRECTORY, just abbreviate it."
(unless directory
(setq directory default-directory))
(if (s-starts-with? directory path)
(concat "./" (f-relative path directory))
(f-abbrev path)))
(defun racer-eldoc ()
"Show eldoc for context at point."
(save-excursion
(racer--goto-func-name)
;; If there's a variable at point:
(-when-let* ((rust-sym (symbol-at-point))
(comp-possibilities (racer-complete))
(matching-possibility
(--find (string= it (symbol-name rust-sym)) comp-possibilities))
(prototype (get-text-property 0 'ctx matching-possibility))
(matchtype (get-text-property 0 'matchtype matching-possibility)))
(if (equal matchtype "Module")
(racer--relative prototype)
;; Syntax highlight function signatures.
(racer--syntax-highlight prototype)))))
(defvar racer-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "M-.") #'racer-find-definition)
(define-key map (kbd "M-,") #'pop-tag-mark)
map))
;;;###autoload
(define-minor-mode racer-mode
"Minor mode for racer."
:lighter " racer"
:keymap racer-mode-map
(setq-local eldoc-documentation-function #'racer-eldoc)
(set (make-local-variable 'completion-at-point-functions) nil)
(add-hook 'completion-at-point-functions #'racer-complete-at-point))
(define-obsolete-function-alias 'racer-turn-on-eldoc 'eldoc-mode)
(define-obsolete-function-alias 'racer-activate 'racer-mode)
(provide 'racer)
;;; racer.el ends here

340
test/racer-test.el Normal file
View File

@ -0,0 +1,340 @@
(require 'racer)
(require 'ert)
(ert-deftest racer--file-and-parent ()
(should
(equal
(racer--file-and-parent "/foo/bar/baz/q.txt")
"baz/q.txt")))
(ert-deftest racer--goto-func-name ()
(with-temp-buffer
;; Insert a function call.
(insert "foo(bar, baz);")
;; Move to the start of the second argument.
(goto-char (point-min))
(while (not (looking-at "baz"))
(forward-char 1))
;; We should be at the end of foo.
(racer--goto-func-name)
(should (equal (point) 4))))
(ert-deftest racer--read-rust-string ()
(should
(equal
(racer--read-rust-string "\"foo \\n \\\" \\' \\; bar")
"foo \n \" ' ; bar")))
(ert-deftest racer--help-buf ()
(should
(bufferp
(racer--help-buf "foo bar."))))
(ert-deftest racer--propertize-all-inline-code ()
(should
(equal-including-properties
(racer--propertize-all-inline-code "foo `bar` [`baz`] biz")
#("foo bar baz biz" 4 7 (face font-lock-variable-name-face) 8 11 (face font-lock-variable-name-face)))))
(ert-deftest racer--propertize-docstring-code ()
"Ensure we render code blocks with indents."
(should
(equal
(racer--propertize-docstring "foo
```rust
func();
```
bar.
```text
1
2
```
")
"foo
func();
bar.
1
2")))
(defun racer--remove-properties (text)
"Remove all the properties on TEXT.
Tests that use `equal' ignore properties, but
this makes the ert failure descriptions clearer."
(with-temp-buffer
(insert text)
(buffer-substring-no-properties (point-min) (point-max))))
(ert-deftest racer--propertize-docstring-code-annotations ()
"Ignore '# foo' lines in code sections in docstrings."
(should
(equal
(racer--remove-properties
(racer--propertize-docstring "```
# #[allow(dead_code)]
#[derive(Debug)]
struct Foo {}
```"))
" #[derive(Debug)]
struct Foo {}")))
(ert-deftest racer--propertize-docstring-code-newlines ()
"Ensure we always have a blank line before a code block."
(should
(equal
(racer--remove-properties
(racer--propertize-docstring "```
bar1();
```
foo
```
bar2();
```"))
" bar1();
foo
bar2();")))
(ert-deftest racer--propertize-docstring-newlines ()
"Ensure we still handle links that have been split over two lines."
(should
(equal
(racer--propertize-docstring "[foo\nbar](baz)")
"foo\nbar")))
(ert-deftest racer--propertize-docstring-link-after-attribute ()
"We should not confuse attributes with links."
(should
(equal
(racer--remove-properties
(racer--propertize-docstring "Result is annotated with the #[must_use] attribute,
by the [`Write`](../../std/io/trait.Write.html) trait"))
"Result is annotated with the #[must_use] attribute,
by the Write trait")))
(ert-deftest racer--propertize-docstring-footnotes ()
"Ensure we discard footnote links."
(should
(equal
(racer--remove-properties
(racer--propertize-docstring "foo [`str`] bar
\[`str`]: ../../std/primitive.str.html
baz."))
"foo str bar
baz.")))
(ert-deftest racer--propertize-docstring-urls ()
"Ensure we render buttons for links with urls."
(let ((result (racer--propertize-docstring "[foo](http://example.com)")))
(should (equal result "foo"))
(should (equal (get-text-property 0 'button result) '(t))))
(should
(equal-including-properties
(racer--propertize-docstring "[foo](#bar)")
"foo")))
(ert-deftest racer--propertize-docstring-heading ()
"Ensure we render markdown headings correctly."
(should
(equal-including-properties
(racer--propertize-docstring "# foo")
#("foo" 0 3 (face racer-help-heading-face)))))
(ert-deftest racer--split-parts ()
"Ensure we correctly parse racer CSV."
(should
(equal (racer--split-parts "foo;bar")
'("foo" "bar")))
(should
(equal (racer--split-parts "foo;\"bar\"")
'("foo" "bar")))
(should
(equal (racer--split-parts "foo\\;bar;baz")
'("foo;bar" "baz"))))
(ert-deftest racer--describe-at-point-name ()
"Ensure we extract the correct name in `racer--describe-at-point'."
(cl-letf (((symbol-function 'racer--call)
(lambda (&rest _)
(s-join
"\n"
(list
"PREFIX 36,37,n"
"MATCH new;new();294;11;/home/user/src/rustc-1.10.0/src/libstd/../libcollections/vec.rs;Function;pub fn new() -> Vec<T>;\"Constructs a new, empty `Vec<T>`.\""
"END")))))
(should
(equal (plist-get (racer--describe-at-point "new") :name)
"new"))))
(ert-deftest racer--describe-at-point-nil-docstring ()
"If there's no docstring, racer--describe-at-point should use nil."
(cl-letf (((symbol-function 'racer--call)
(lambda (&rest _)
(s-join
"\n"
(list
"PREFIX 36,37,n"
"MATCH new;new();294;11;/home/user/src/rustc-1.10.0/src/libstd/../libcollections/vec.rs;Function;pub fn new() -> Vec<T>;\"\""
"END")))))
(should
(null (plist-get (racer--describe-at-point "new") :docstring)))))
(ert-deftest racer--describe-at-point-shortest ()
"If there are multiple matches, we want the shortest.
Since we've moved point to the end of symbol, the other functions just happen to have the same prefix."
(cl-letf (((symbol-function 'racer--call)
(lambda (&rest _)
(s-join
"\n"
(list
"PREFIX 36,37,n"
"MATCH new_bar;new_bar();294;11;/home/user/src/rustc-1.10.0/src/libstd/../libcollections/vec.rs;Function;pub fn new() -> Vec<T>;\"\""
"MATCH new;new();294;11;/home/user/src/rustc-1.10.0/src/libstd/../libcollections/vec.rs;Function;pub fn new() -> Vec<T>;\"\""
"MATCH new_foo;new_foo();294;11;/home/user/src/rustc-1.10.0/src/libstd/../libcollections/vec.rs;Function;pub fn new() -> Vec<T>;\"\""
"END")))))
(should
(equal (plist-get (racer--describe-at-point "new") :name)
"new"))))
(ert-deftest racer--syntax-highlight ()
"Ensure we highlight code blocks and snippets correctly."
;; Highlighting types should always use the type face.
(should
(equal-including-properties
(racer--syntax-highlight "Foo")
#("Foo" 0 3 (face font-lock-type-face))))
;; Highlighting keywords.
(should
(equal-including-properties
(racer--syntax-highlight "false")
#("false" 0 5 (face font-lock-keyword-face))))
;; Simple variables should be highlighted, even when standalone.
(should
(equal-including-properties
(racer--syntax-highlight "foo")
#("foo" 0 3 (face font-lock-variable-name-face)))))
(ert-deftest racer-describe ()
"Smoke test for `racer-describe'."
(cl-letf (((symbol-function 'racer--call)
(lambda (&rest _)
(s-join
"\n"
(list
"PREFIX 36,37,n"
"MATCH foo;foo();294;11;/home/user/src/rustc-1.10.0/src/libstd/../libcollections/vec.rs;Function;pub fn new() -> Vec<T>;\"\""
"END")))))
(with-temp-buffer
(rust-mode)
(insert "foo();")
(goto-char (point-min))
(racer-describe))))
(ert-deftest racer-describe-test-description ()
"Ensure we write the correct text summary in the first line
of the racer describe buffer."
(cl-letf (((symbol-function 'racer--call)
(lambda (&rest _)
"PREFIX 8,10,Ok\nMATCH Ok;Ok;253;4;/home/user/src/rustc-1.10.0/src/libstd/../libcore/result.rs;EnumVariant;Ok(#[stable(feature = \"rust1\", since = \"1.0.0\")] T),;\"`Result` is a type that represents either success (`Ok`) or failure (`Err`).\n\nSee the [`std::result`](index.html) module documentation for details.\nEND\n")))
(with-temp-buffer
(rust-mode)
(insert "Ok")
(goto-char (point-min))
(switch-to-buffer (racer--describe "Ok"))
(let ((first-line (-first-item (s-lines (buffer-substring-no-properties
(point-min) (point-max))))))
(should
(equal first-line
"Ok is an enum variant defined in libcore/result.rs."))))))
(ert-deftest racer-describe-module-description ()
"Ensure we write the correct text summary in the first line
of the racer describe buffer."
(cl-letf (((symbol-function 'racer--call)
(lambda (&rest _)
"PREFIX 13,20,matches\nMATCH matches;matches;1;0;/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/matches-0.1.2/lib.rs;Module;/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/matches-0.1.2/lib.rs;\"\"\nEND\n")))
(with-temp-buffer
(rust-mode)
(insert "extern crate matches;")
(goto-char (1- (point-max)))
(switch-to-buffer (racer--describe "matches"))
(should
(equal (racer--remove-properties (buffer-string))
"matches is a module defined in matches-0.1.2/lib.rs.
Not documented.")))))
(ert-deftest racer-describe-uses-whole-symbol ()
"Racer uses the symbol *before* point, so make sure we move point to
the end of the current symbol.
Otherwise, if the point is at the start of the symbol, we don't find anything."
(let (point-during-call)
(cl-letf (((symbol-function 'racer--call)
(lambda (&rest _)
(setq point-during-call (point))
(s-join
"\n"
(list
"PREFIX 36,37,n"
"MATCH foo;foo();294;11;/home/user/src/rustc-1.10.0/src/libstd/../libcollections/vec.rs;Function;pub fn new() -> Vec<T>;\"\""
"END")))))
(with-temp-buffer
(rust-mode)
(insert "foo();")
(goto-char (point-min))
;; This should move point to the end of 'foo' before calling
;; racer--call.
(racer-describe))
(should (equal point-during-call 4)))))
(ert-deftest racer-debug ()
"Smoke test for `racer-debug'."
(let ((racer--prev-state
(list
:program "racer"
:args '("complete" "1" "2")
:exit-code 0
:stdout "PREFIX 1,2,Ok\nMATCH FOO\nEND\n"
:stderr ""
:default-directory "/"
:process-environment
'("RUST_SRC_PATH=/home/user/src/rustc-1.10.0/src"
"CARGO_HOME=/home/user/.cargo"))))
(racer-debug)))
(ert-deftest racer--relative ()
;; Common case: the path is relative to the directory.
(should (equal (racer--relative "/foo/bar" "/foo")
"./bar"))
;; Path is not relative, but it's a home directory.
(should (equal (racer--relative (f-expand "~/foo")
(f-expand "~/bar"))
"~/foo"))
;; Path is not relative and not a home directory.
(should (equal (racer--relative "/foo/bar" "/quux")
"/foo/bar")))
(ert-deftest racer-eldoc-no-completions ()
"`racer-eldoc' should handle no completions gracefully."
(cl-letf (((symbol-function 'racer--call)
(lambda (&rest _)
"PREFIX 4,4,\nEND\n")))
(with-temp-buffer
(rust-mode)
(insert "use ")
;; Midle of the 'use'.
(goto-char 2)
;; Should return nil without crashing.
(should (null (racer-eldoc))))))

12
test/test-helper.el Normal file
View File

@ -0,0 +1,12 @@
;;; test-helper --- Test helper for racer
;;; Commentary:
;;; Code:
(require 'undercover)
(undercover "racer.el"
(:exclude "*-test.el")
(:report-file "/tmp/undercover-report.json"))
;;; test-helper.el ends here