Merge commit 'f7471da19a1c773aaab8d97243b3e5ad0de23626' as 'emacs-racer'
This commit is contained in:
commit
acefb0b62d
1
emacs-racer/.ert-runner
Normal file
1
emacs-racer/.ert-runner
Normal file
@ -0,0 +1 @@
|
||||
-L .
|
1
emacs-racer/.gitignore
vendored
Normal file
1
emacs-racer/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
.cask
|
22
emacs-racer/.travis.yml
Normal file
22
emacs-racer/.travis.yml
Normal 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
emacs-racer/CHANGELOG.md
Normal file
51
emacs-racer/CHANGELOG.md
Normal 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
emacs-racer/Cask
Normal file
14
emacs-racer/Cask
Normal 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
emacs-racer/Makefile
Normal file
21
emacs-racer/Makefile
Normal 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
emacs-racer/README.md
Normal file
112
emacs-racer/README.md
Normal 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
emacs-racer/images/racer_completion.png
Normal file
BIN
emacs-racer/images/racer_completion.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
BIN
emacs-racer/images/racer_goto.gif
Normal file
BIN
emacs-racer/images/racer_goto.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 67 KiB |
BIN
emacs-racer/images/racer_help.png
Normal file
BIN
emacs-racer/images/racer_help.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 49 KiB |
768
emacs-racer/racer.el
Normal file
768
emacs-racer/racer.el
Normal 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
emacs-racer/test/racer-test.el
Normal file
340
emacs-racer/test/racer-test.el
Normal 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
emacs-racer/test/test-helper.el
Normal file
12
emacs-racer/test/test-helper.el
Normal 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
|
Reference in New Issue
Block a user