├── .ert-runner
├── .github
└── workflows
│ └── test.yml
├── .gitignore
├── CHANGELOG.md
├── Cask
├── README.md
├── images
├── racer_completion.png
├── racer_goto.gif
└── racer_help.png
├── racer.el
└── test
├── racer-test.el
└── test-helper.el
/.ert-runner:
--------------------------------------------------------------------------------
1 | -L .
2 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Run test suite
2 | on: [push, pull_request]
3 |
4 | jobs:
5 | test:
6 | runs-on: ubuntu-latest
7 | strategy:
8 | matrix:
9 | emacs_version:
10 | - '25.1'
11 | - '26.1'
12 | - '27.1'
13 |
14 | steps:
15 | - uses: purcell/setup-emacs@master
16 | with:
17 | version: ${{ matrix.emacs_version }}
18 | - uses: conao3/setup-cask@master
19 |
20 | - uses: actions/checkout@v2
21 |
22 | - name: Test
23 | env:
24 | COVERALLS_FLAG_NAME: Emacs ${{ matrix.emacs_version }}
25 | COVERALLS_PARALLEL: 1
26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
27 | run: |
28 | cask install
29 | cask exec ert-runner
30 |
31 | - name: Test after byte-compilation
32 | run: |
33 | cask build
34 | cask exec ert-runner
35 |
36 | finalize:
37 | runs-on: ubuntu-latest
38 | if: always()
39 | needs: test
40 | steps:
41 | - run: curl "https://coveralls.io/webhook?repo_name=$GITHUB_REPOSITORY&repo_token=${{ secrets.GITHUB_TOKEN }}" -d "payload[build_num]=$GITHUB_RUN_NUMBER&payload[status]=done"
42 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .cask
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # v1.3 (unreleased)
2 |
3 | * `racer-rust-src-path` is now set automatically by default.
4 | * New simpler installation instructions based on `rustup`.
5 | * Fixed an issue with racer completion in indirect buffers.
6 | * Fixed a crash on racer completion in buffers not backed by files.
7 | * Fixed a crash on backslashes and backticks when calling
8 | `racer-describe`.
9 |
10 | # v1.2
11 |
12 | * Added the command `racer-debug` to help users diagnose issues.
13 | * We now explicitly try `~/.cargo/bin/racer` if `racer` isn't on path.
14 | * We no longer offer completions inside comments by default (it tends
15 | to be slow and rarely offers completions). See
16 | `racer-complete-in-comments`.
17 | * Eldoc descriptions of modules now abbreviate the path relative to
18 | the project and the user's home directory.
19 | * Several improvements to markdown rendering in `racer-describe`.
20 |
21 | # v1.1
22 |
23 | * Fixed a crash when point is at the beginning of buffer.
24 | * Fixed a crash when not in a cargo project.
25 | * Added `racer-cargo-home`, which enables completion for cargo crates.
26 | * Various improvements to formatting of completion candidates.
27 | * Added `racer-describe`.
28 |
29 | # v1.0.2
30 |
31 | * Trigger completions after `::` or `.`.
32 | * Compatibility with latest company
33 | * Fixed an issue where TAGS from other projects were also completion
34 | candidates
35 |
36 | # v1.0.1
37 |
38 | No changes since v0.0.2.
39 |
40 | This release was created to [work around an issue
41 | where MELPA stable](https://github.com/milkypostman/melpa/issues/3205)
42 | had created a v1.0.0 from an early version of racer.el
43 |
44 | # v0.0.2
45 |
46 | Initial release. Includes:
47 |
48 | * Code completion with company
49 | * Jump to definition
50 | * Eldoc
51 |
52 | Early users who are using `racer-activate` or `racer-turn-on-eldoc`
53 | should use `racer-mode` and `eldoc-mode` instead. The former have been
54 | deprecated.
55 |
--------------------------------------------------------------------------------
/Cask:
--------------------------------------------------------------------------------
1 | (source gnu)
2 | (source melpa)
3 |
4 | (package-file "racer.el")
5 |
6 | (depends-on "company")
7 | (depends-on "dash")
8 | (depends-on "s")
9 | (depends-on "f")
10 | (depends-on "rust-mode")
11 |
12 | (development
13 | (depends-on "ert-runner")
14 | (depends-on "undercover"))
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Racer for Emacs
2 | [](http://melpa.org/#/racer)
3 | [](http://stable.melpa.org/#/racer)
4 | [](https://coveralls.io/github/racer-rust/emacs-racer?branch=master)
5 |
6 | This is the official Emacs package for
7 | [Racer](http://github.com/phildawes/racer).
8 |
9 |
10 | **Table of Contents**
11 |
12 | - [Racer for Emacs](#racer-for-emacs)
13 | - [Completion](#completion)
14 | - [Find Definitions](#find-definitions)
15 | - [Describe Functions and Types](#describe-functions-and-types)
16 | - [Installation](#installation)
17 | - [Testing your setup](#testing-your-setup)
18 | - [Tests](#tests)
19 |
20 |
21 |
22 | ## Completion
23 |
24 | racer.el supports code completion of variables, functions and modules.
25 |
26 | 
27 |
28 | You can also press F1 to pop up a help buffer for the current
29 | completion candidate.
30 |
31 | Note that due to a
32 | [limitation of racer](https://github.com/phildawes/racer/issues/389),
33 | racer.el cannot offer completion for macros.
34 |
35 | ## Find Definitions
36 |
37 | racer.el can jump to definition of functions and types.
38 |
39 | 
40 |
41 | You can use M-. to go to the definition, and M-,
42 | to go back.
43 |
44 | ## Describe Functions and Types
45 |
46 | racer.el can show a help buffer based on the docstring of the thing at
47 | point.
48 |
49 | 
50 |
51 | Use M-x racer-describe to open the help buffer.
52 |
53 | ## Installation
54 |
55 | 1. You will need to use a nightly version of rust.
56 | If you're using rustup, run
57 | ```
58 | $ rustup toolchain add nightly
59 | ```
60 |
61 | 2. Install [Racer](http://github.com/phildawes/racer) and download the
62 | source code of Rust:
63 |
64 | ```
65 | $ rustup component add rust-src
66 | $ cargo +nightly install racer
67 | ```
68 |
69 | 3. Allow Emacs to install packages from MELPA:
70 |
71 | ```el
72 | (require 'package)
73 | (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/"))
74 | ```
75 |
76 | 4. Install the Emacs package for Racer: `M-x package-install RET racer RET`
77 |
78 | 5. Configure Emacs to activate racer when rust-mode starts:
79 | ```el
80 | (add-hook 'rust-mode-hook #'racer-mode)
81 | (add-hook 'racer-mode-hook #'eldoc-mode)
82 | ```
83 |
84 | For completions, install company with `M-x package-install RET company RET`. A sample configuration:
85 | ```el
86 |
87 | (add-hook 'racer-mode-hook #'company-mode)
88 |
89 | (require 'rust-mode)
90 | (define-key rust-mode-map (kbd "TAB") #'company-indent-or-complete-common)
91 | (setq company-tooltip-align-annotations t)
92 | ```
93 | For automatic completions, customize `company-idle-delay` and
94 | `company-minimum-prefix-length`.
95 |
96 | Racer process may be slow to respond for instance when indexing. You can
97 | customize `racer-command-timeout` and `racer-eldoc-timeout` to avoid rendering
98 | your Emacs session unresponsive. Eldoc timeout should be on the lower side and
99 | defaults to 0.5 seconds. You can probably tweak it down on a fast machine.
100 | Timeout of `nil` will wait indefinitely.
101 |
102 | ### Testing your setup
103 |
104 | To test **completion**: Open a rust file and try typing ```use
105 | std::io::B``` and press TAB.
106 |
107 | To test **go to definition**: Place your cursor over a symbol and press
108 | `M-.` to jump to its definition.
109 |
110 | Press `C-x 4 .` to jump to its definition in another window.
111 |
112 | Press `C-x 5 .` to jump to its definition in another frame.
113 |
114 | Press `M-,` to jump back to the previous cursor location.
115 |
116 |
117 | If **it doesn't work**, try `M-x racer-debug` to see what command was
118 | run and what output was returned.
119 |
120 | ## Tests
121 |
122 | racer.el includes tests. To run them, you need to install
123 | [Cask](https://github.com/cask/cask), then:
124 |
125 | ```
126 | $ cask install
127 | $ cask exec ert-runner
128 | ```
129 |
--------------------------------------------------------------------------------
/images/racer_completion.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/racer-rust/emacs-racer/1e63e98626737ea9b662d4a9b1ffd6842b1c648c/images/racer_completion.png
--------------------------------------------------------------------------------
/images/racer_goto.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/racer-rust/emacs-racer/1e63e98626737ea9b662d4a9b1ffd6842b1c648c/images/racer_goto.gif
--------------------------------------------------------------------------------
/images/racer_help.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/racer-rust/emacs-racer/1e63e98626737ea9b662d4a9b1ffd6842b1c648c/images/racer_help.png
--------------------------------------------------------------------------------
/racer.el:
--------------------------------------------------------------------------------
1 | ;;; racer.el --- code completion, goto-definition and docs browsing for Rust via racer -*- lexical-binding: t -*-
2 |
3 | ;; Copyright (c) 2014 Phil Dawes
4 |
5 | ;; Author: Phil Dawes
6 | ;; URL: https://github.com/racer-rust/emacs-racer
7 | ;; Version: 1.3
8 | ;; Package-Requires: ((emacs "25.1") (rust-mode "0.2.0") (dash "2.13.0") (s "1.10.0") (f "0.18.2") (pos-tip "0.4.6"))
9 | ;; Keywords: abbrev, convenience, matching, rust, tools
10 |
11 | ;; This file is not part of GNU Emacs.
12 |
13 | ;; Permission is hereby granted, free of charge, to any
14 | ;; person obtaining a copy of this software and associated
15 | ;; documentation files (the "Software"), to deal in the
16 | ;; Software without restriction, including without
17 | ;; limitation the rights to use, copy, modify, merge,
18 | ;; publish, distribute, sublicense, and/or sell copies of
19 | ;; the Software, and to permit persons to whom the Software
20 | ;; is furnished to do so, subject to the following
21 | ;; conditions:
22 |
23 | ;; The above copyright notice and this permission notice
24 | ;; shall be included in all copies or substantial portions
25 | ;; of the Software.
26 |
27 | ;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
28 | ;; ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
29 | ;; TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
30 | ;; PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
31 | ;; SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
32 | ;; CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
33 | ;; OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
34 | ;; IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
35 | ;; DEALINGS IN THE SOFTWARE.
36 |
37 | ;;; Commentary:
38 |
39 | ;; Please see the readme for full documentation:
40 | ;; https://github.com/racer-rust/emacs-racer
41 |
42 | ;;; Quickstart:
43 |
44 | ;; You will need to configure Emacs to find racer:
45 | ;;
46 | ;; (setq racer-rust-src-path "/src/")
47 | ;; (setq racer-cmd "/target/release/racer")
48 | ;;
49 | ;; To activate racer in Rust buffers, run:
50 | ;;
51 | ;; (add-hook 'rust-mode-hook #'racer-mode)
52 | ;;
53 | ;; You can also use racer to find definition at point via
54 | ;; `racer-find-definition', bound to `M-.' by default.
55 | ;;
56 | ;; Finally, you can also use Racer to show the signature of the
57 | ;; current function in the minibuffer:
58 | ;;
59 | ;; (add-hook 'racer-mode-hook #'eldoc-mode)
60 |
61 | ;;; Code:
62 |
63 | (require 'dash)
64 | (require 'etags)
65 | (require 'rust-mode)
66 | (require 's)
67 | (require 'f)
68 | (require 'thingatpt)
69 | (require 'button)
70 | (require 'help-mode)
71 | (autoload 'pos-tip-show-no-propertize "pos-tip")
72 |
73 | (defgroup racer nil
74 | "Code completion, goto-definition and docs browsing for Rust via racer."
75 | :link '(url-link "https://github.com/racer-rust/emacs-racer/")
76 | :group 'rust-mode)
77 |
78 | (defcustom racer-cmd
79 | (or (executable-find "racer")
80 | (f-expand "~/.cargo/bin/racer")
81 | "/usr/local/bin/racer")
82 | "Path to the racer binary."
83 | :type 'file
84 | :group 'racer)
85 |
86 | (defcustom racer-rust-src-path
87 | (or
88 | (getenv "RUST_SRC_PATH")
89 | (when (executable-find "rustc")
90 | (let* ((sysroot (s-trim-right
91 | (shell-command-to-string
92 | (format "%s --print sysroot" (executable-find "rustc")))))
93 | (lib-path (f-join sysroot "lib/rustlib/src/rust/library"))
94 | (src-path (f-join sysroot "lib/rustlib/src/rust/src")))
95 | (or (when (file-exists-p lib-path) lib-path)
96 | (when (file-exists-p src-path) src-path))))
97 | "/usr/local/src/rust/src")
98 |
99 | "Path to the rust source tree.
100 | If nil, we will query $RUST_SRC_PATH at runtime.
101 | If $RUST_SRC_PATH is not set, look for rust source in rustup's install directory."
102 | :type 'file
103 | :group 'racer)
104 |
105 | (defcustom racer-cargo-home
106 | (or
107 | (getenv "CARGO_HOME")
108 | "~/.cargo")
109 | "Path to your current cargo home. Usually `~/.cargo'.
110 | If nil, we will query $CARGO_HOME at runtime."
111 | :type 'file
112 | :group 'racer)
113 |
114 | (defun racer--cargo-project-root ()
115 | "Find the root of the current Cargo project."
116 | (let ((root (locate-dominating-file (or (buffer-file-name (buffer-base-buffer)) default-directory)
117 | "Cargo.toml")))
118 | (and root (file-truename root))))
119 |
120 | (defun racer--header (text)
121 | "Helper function for adding text properties to TEXT."
122 | (propertize text 'face 'racer-help-heading-face))
123 |
124 | (defvar racer--prev-state nil)
125 |
126 | (defun racer-debug ()
127 | "Open a buffer describing the last racer command run.
128 | Helps users find configuration issues, or file bugs on
129 | racer or racer.el."
130 | (interactive)
131 | (unless racer--prev-state
132 | (user-error "Must run a racer command before debugging"))
133 | (let ((buf (get-buffer-create "*racer-debug*"))
134 | (inhibit-read-only t))
135 | (with-current-buffer buf
136 | (erase-buffer)
137 | (setq buffer-read-only t)
138 | (let* ((process-environment
139 | (plist-get racer--prev-state :process-environment))
140 | (rust-src-path-used
141 | (--first (s-prefix-p "RUST_SRC_PATH=" it) process-environment))
142 | (cargo-home-used
143 | (--first (s-prefix-p "CARGO_HOME=" it) process-environment))
144 | (stdout (plist-get racer--prev-state :stdout))
145 | (stderr (plist-get racer--prev-state :stderr)))
146 | (insert
147 | ;; Summarise the actual command that we run.
148 | (racer--header "The last racer command was:\n\n")
149 | (format "$ cd %s\n"
150 | (plist-get racer--prev-state :default-directory))
151 | (format "$ export %s\n" cargo-home-used)
152 | (format "$ export %s\n" rust-src-path-used)
153 | (format "$ %s %s\n\n"
154 | (plist-get racer--prev-state :program)
155 | (s-join " " (plist-get racer--prev-state :args)))
156 |
157 | ;; Describe the exit code and outputs.
158 | (racer--header
159 | (format "This command terminated with exit code %s.\n\n"
160 | (plist-get racer--prev-state :exit-code)))
161 | (if (s-blank? stdout)
162 | (racer--header "No output on stdout.\n\n")
163 | (format "%s\n\n%s\n\n"
164 | (racer--header "stdout:")
165 | (s-trim-right stdout)))
166 | (if (s-blank? stderr)
167 | (racer--header "No output on stderr.\n\n")
168 | (format "%s\n\n%s\n\n"
169 | (racer--header "stderr:")
170 | (s-trim-right stderr)))
171 |
172 | ;; Give copy-paste instructions for reproducing any errors
173 | ;; the user has seen.
174 | (racer--header
175 | (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"))
176 | (format "$ %s %s %s %s\n\n" cargo-home-used rust-src-path-used
177 | (plist-get racer--prev-state :program)
178 | (s-join " "
179 | (-drop-last 1 (plist-get racer--prev-state :args))))
180 |
181 | ;; Tell the user what to do next if they have problems.
182 | (racer--header "Please report bugs ")
183 | (racer--url-button "on GitHub" "https://github.com/racer-rust/emacs-racer/issues/new")
184 | (racer--header "."))))
185 | (switch-to-buffer buf)
186 | (goto-char (point-min))))
187 |
188 | (defun racer--call (command &rest args)
189 | "Call racer command COMMAND with args ARGS.
190 | Return stdout if COMMAND exits normally, otherwise show an
191 | error."
192 | (let ((rust-src-path (or (when racer-rust-src-path (expand-file-name racer-rust-src-path))
193 | (getenv "RUST_SRC_PATH")))
194 | (cargo-home (or (when racer-cargo-home (expand-file-name racer-cargo-home))
195 | (getenv "CARGO_HOME"))))
196 | (when (null rust-src-path)
197 | (user-error "You need to set `racer-rust-src-path' or `RUST_SRC_PATH'"))
198 | (unless (file-exists-p rust-src-path)
199 | (user-error "No such directory: %s. Please set `racer-rust-src-path' or `RUST_SRC_PATH'"
200 | rust-src-path))
201 | (let ((default-directory (or (racer--cargo-project-root) default-directory))
202 | (process-environment (append (list
203 | (format "RUST_SRC_PATH=%s" rust-src-path)
204 | (format "CARGO_HOME=%s" cargo-home))
205 | process-environment)))
206 | (-let [(exit-code stdout _stderr)
207 | (racer--shell-command racer-cmd (cons command args))]
208 | ;; Use `equal' instead of `zero' as exit-code can be a string
209 | ;; "Aborted" if racer crashes.
210 | (unless (equal 0 exit-code)
211 | (user-error "%s exited with %s. `M-x racer-debug' for more info"
212 | racer-cmd exit-code))
213 | stdout))))
214 |
215 | (defmacro racer--with-temporary-file (path-sym &rest body)
216 | "Create a temporary file, and bind its path to PATH-SYM.
217 | Evaluate BODY, then delete the temporary file."
218 | (declare (indent 1) (debug (symbolp body)))
219 | `(let ((,path-sym (make-temp-file "racer")))
220 | (unwind-protect
221 | (progn ,@body)
222 | (delete-file ,path-sym))))
223 |
224 | (defmacro racer--with-temp-buffers (stdout-sym stderr-sym &rest body)
225 | (declare (indent 2) (debug (symbolp body)))
226 | `(let ((kill-buffer-query-functions nil)
227 | (,stdout-sym (generate-new-buffer " *racer-stdout*"))
228 | (,stderr-sym (generate-new-buffer " *racer-stderr*")))
229 | (unwind-protect
230 | (progn ,@body)
231 | (when (buffer-name ,stdout-sym)
232 | (kill-buffer ,stdout-sym))
233 | (when (buffer-name ,stderr-sym)
234 | (kill-buffer ,stderr-sym)))))
235 |
236 | (defcustom racer-command-timeout nil
237 | "Abandon completion if racer process fails to respond for that
238 | many seconds (maybe float). nil means wait indefinitely."
239 | :type 'number
240 | :group 'racer)
241 |
242 | (defun racer--shell-command (program args)
243 | "Execute PROGRAM with ARGS. Return a list (exit-code stdout
244 | stderr)."
245 | (racer--with-temp-buffers stdout stderr
246 | (let (exit-code
247 | stdout-result
248 | stderr-result
249 | (proc (make-process :name "*async-racer*"
250 | :buffer stdout
251 | :command (cons program args)
252 | :connection-type 'pipe
253 | :stderr stderr)))
254 | (while
255 | (and (process-live-p proc)
256 | (with-local-quit
257 | (accept-process-output proc racer-command-timeout))))
258 | (when (process-live-p proc) (kill-process proc))
259 | (setq exit-code (process-exit-status proc)
260 | stderr-result (with-current-buffer stderr (buffer-string))
261 | stdout-result (with-current-buffer stdout (buffer-string)))
262 | (setq racer--prev-state
263 | (list
264 | :program program
265 | :args args
266 | :exit-code exit-code
267 | :stdout stdout-result
268 | :stderr stderr-result
269 | :default-directory default-directory
270 | :process-environment process-environment))
271 | (list exit-code stdout-result stderr-result))))
272 |
273 |
274 | (defun racer--call-at-point (command)
275 | "Call racer command COMMAND at point of current buffer.
276 | Return a list of all the lines returned by the command."
277 | (racer--with-temporary-file tmp-file
278 | (write-region nil nil tmp-file nil 'silent)
279 | (let ((racer-args (list
280 | command
281 | (number-to-string (line-number-at-pos))
282 | (number-to-string (racer--current-column)))))
283 | ;; If this buffer is backed by a file, pass that to racer too.
284 | (-when-let (file-name (buffer-file-name (buffer-base-buffer)))
285 | (setq racer-args
286 | (append racer-args (list file-name))))
287 |
288 | (setq racer-args (append racer-args (list tmp-file)))
289 | (s-lines
290 | (s-trim-right
291 | (apply #'racer--call racer-args))))))
292 |
293 | (defun racer--read-rust-string (string)
294 | "Convert STRING, a rust string literal, to an elisp string."
295 | (when string
296 | ;; Remove outer double quotes.
297 | (setq string (s-chop-prefix "\"" string))
298 | (setq string (s-chop-suffix "\"" string))
299 | ;; Translate escape sequences.
300 | (replace-regexp-in-string
301 | (rx "\\" (group anything))
302 | (lambda (whole-match)
303 | (let ((escaped-char (match-string 1 whole-match)))
304 | (if (equal escaped-char "n")
305 | "\n"
306 | escaped-char)))
307 | string
308 | t t)))
309 |
310 | (defun racer--split-parts (raw-output)
311 | "Given RAW-OUTPUT from racer, split on semicolons and doublequotes.
312 | Unescape strings as necessary."
313 | (let ((parts nil)
314 | (current "")
315 | (i 0))
316 | (while (< i (length raw-output))
317 | (let ((char (elt raw-output i))
318 | (prev-char (and (> i 0) (elt raw-output (1- i)))))
319 | (cond
320 | ;; A semicolon that wasn't escaped, start a new part.
321 | ((and (equal char ?\;) (not (equal prev-char ?\\)))
322 | (push current parts)
323 | (setq current ""))
324 | (t
325 | (setq current (concat current (string char))))))
326 | (setq i (1+ i)))
327 | (push current parts)
328 | (mapcar #'racer--read-rust-string (nreverse parts))))
329 |
330 | (defun racer--split-snippet-match (line)
331 | "Given LINE, a string \"MATCH ...\" from complete-with-snippet,
332 | split it into its constituent parts."
333 | (let* ((match-parts (racer--split-parts line))
334 | (docstring (nth 7 match-parts)))
335 | (when (and match-parts (equal (length match-parts) 8))
336 | (list :name (s-chop-prefix "MATCH " (nth 0 match-parts))
337 | :line (string-to-number (nth 2 match-parts))
338 | :column (string-to-number (nth 3 match-parts))
339 | :path (nth 4 match-parts)
340 | ;; Struct or Function:
341 | :kind (nth 5 match-parts)
342 | :signature (nth 6 match-parts)
343 | :docstring (if (> (length docstring) 0) docstring nil)))))
344 |
345 | (defun racer--order-descriptions (descriptions)
346 | (sort descriptions
347 | (lambda (a b)
348 | (let ((a (or (plist-get a :docstring) ""))
349 | (b (or (plist-get b :docstring) "")))
350 | (> (length a) (length b))))))
351 |
352 | (defun racer--describe-at-point (name)
353 | "Get a descriptions of the symbols matching symbol at point and
354 | NAME. If there are multiple possibilities with this NAME, prompt
355 | the user to choose. Return a list of all possibilities that
356 | start with the user's selection."
357 | (let* ((output-lines (save-excursion
358 | ;; Move to the end of the current symbol, to
359 | ;; increase racer accuracy.
360 | (skip-syntax-forward "w_")
361 | (racer--call-at-point "complete-with-snippet")))
362 | (all-matches (--map (when (s-starts-with-p "MATCH " it)
363 | (racer--split-snippet-match it))
364 | output-lines))
365 | (relevant-matches (--filter (equal (plist-get it :name) name)
366 | all-matches)))
367 | (racer--order-descriptions
368 | (if (> (length relevant-matches) 1)
369 | ;; We might have multiple matches with the same name but
370 | ;; different types. E.g. Vec::from.
371 | (let ((signature
372 | (completing-read "Multiple matches: "
373 | (--map (plist-get it :signature) relevant-matches))))
374 | (-filter
375 | (lambda (x)
376 | (let ((sig (plist-get x :signature)))
377 | (equal (substring sig 0 (min (length sig) (length signature)))
378 | signature)))
379 | relevant-matches))
380 | relevant-matches))))
381 |
382 | (defun racer--help-buf (contents)
383 | "Create a *Racer Help* buffer with CONTENTS."
384 | (let ((buf (get-buffer-create "*Racer Help*"))
385 | ;; If the buffer already existed, we need to be able to
386 | ;; override `buffer-read-only'.
387 | (inhibit-read-only t))
388 | (with-current-buffer buf
389 | (erase-buffer)
390 | (insert contents)
391 | (setq buffer-read-only t)
392 | (goto-char (point-min))
393 | (racer-help-mode))
394 | buf))
395 |
396 | (defface racer-help-heading-face
397 | '((t :weight bold))
398 | "Face for markdown headings in *Racer Help* buffers.")
399 |
400 | (defun racer--url-p (target)
401 | "Return t if TARGET looks like a fully qualified URL."
402 | (not (null
403 | (string-match-p (rx bol "http" (? "s") "://") target))))
404 |
405 | (defun racer--propertize-links (markdown)
406 | "Propertize links in MARKDOWN."
407 | (replace-regexp-in-string
408 | ;; Text of the form [foo](http://example.com)
409 | (rx "[" (group (+? (not (any "]")))) "](" (group (+? anything)) ")")
410 | ;; For every match:
411 | (lambda (whole-match)
412 | ;; Extract link and target.
413 | (let ((link-text (match-string 1 whole-match))
414 | (link-target (match-string 2 whole-match)))
415 | ;; If it's a web URL, use a clickable link.
416 | (if (racer--url-p link-target)
417 | (racer--url-button link-text link-target)
418 | ;; Otherwise, just discard the target.
419 | link-text)))
420 | markdown
421 | t t))
422 |
423 | (defun racer--propertize-all-inline-code (markdown)
424 | "Given a single line MARKDOWN, replace all instances of `foo` or
425 | \[`foo`\] with a propertized string."
426 | (let ((highlight-group
427 | (lambda (whole-match)
428 | (racer--syntax-highlight (match-string 1 whole-match)))))
429 | (setq markdown
430 | (replace-regexp-in-string
431 | (rx "[`" (group (+? anything)) "`]")
432 | highlight-group
433 | markdown
434 | t t))
435 | (setq markdown
436 | (replace-regexp-in-string
437 | (rx "`" (group (+? anything)) "`")
438 | highlight-group
439 | markdown
440 | t t))))
441 |
442 | (defun racer--indent-block (str)
443 | "Indent every line in STR."
444 | (s-join "\n" (--map (concat " " it) (s-lines str))))
445 |
446 | (defun racer--trim-newlines (str)
447 | "Remove newlines from the start and end of STR."
448 | (->> str
449 | (s-chop-prefix "\n")
450 | (s-chop-suffix "\n")))
451 |
452 | (defun racer--remove-footnote-links (str)
453 | "Remove footnote links from markdown STR."
454 | (->> (s-lines str)
455 | (--remove (string-match-p (rx bol "[`" (+? anything) "`]: ") it))
456 | (s-join "\n")
457 | ;; Collapse consecutive blank lines caused by removing footnotes.
458 | (s-replace "\n\n\n" "\n\n")))
459 |
460 | (defun racer--docstring-sections (docstring)
461 | "Split DOCSTRING into text, code and heading sections."
462 | (let* ((sections nil)
463 | (current-section-lines nil)
464 | (section-type :text)
465 | ;; Helper function.
466 | (finish-current-section
467 | (lambda ()
468 | (when current-section-lines
469 | (let ((current-section
470 | (s-join "\n" (nreverse current-section-lines))))
471 | (unless (s-blank? current-section)
472 | (push (list section-type current-section) sections))
473 | (setq current-section-lines nil))))))
474 | (dolist (line (s-lines docstring))
475 | (cond
476 | ;; If this is a closing ```
477 | ((and (s-starts-with-p "```" line) (eq section-type :code))
478 | (push line current-section-lines)
479 | (funcall finish-current-section)
480 | (setq section-type :text))
481 | ;; If this is an opening ```
482 | ((s-starts-with-p "```" line)
483 | (funcall finish-current-section)
484 | (push line current-section-lines)
485 | (setq section-type :code))
486 | ;; Headings
487 | ((and (not (eq section-type :code)) (s-starts-with-p "# " line))
488 | (funcall finish-current-section)
489 | (push (list :heading line) sections))
490 | ;; Normal text.
491 | (t
492 | (push line current-section-lines))))
493 | (funcall finish-current-section)
494 | (nreverse sections)))
495 |
496 | (defun racer--clean-code-section (section)
497 | "Given a SECTION, a markdown code block, remove
498 | fenced code delimiters and code annotations."
499 | (->> (s-lines section)
500 | (-drop 1)
501 | (-drop-last 1)
502 | ;; Ignore annotations like # #[allow(dead_code)]
503 | (--remove (s-starts-with-p "# " it))
504 | (s-join "\n")))
505 |
506 | (defun racer--propertize-docstring (docstring)
507 | "Replace markdown syntax in DOCSTRING with text properties."
508 | (let* ((sections (racer--docstring-sections docstring))
509 | (propertized-sections
510 | (--map (-let [(section-type section) it]
511 | ;; Remove trailing newlines, so we can ensure we
512 | ;; have consistent blank lines between sections.
513 | (racer--trim-newlines
514 | (pcase section-type
515 | (:text
516 | (racer--propertize-all-inline-code
517 | (racer--propertize-links
518 | (racer--remove-footnote-links
519 | section))))
520 | (:code
521 | (racer--indent-block
522 | (racer--syntax-highlight
523 | (racer--clean-code-section section))))
524 | (:heading
525 | (racer--header
526 | (s-chop-prefix "# " section))))))
527 | sections)))
528 | (s-join "\n\n" propertized-sections)))
529 |
530 | (defun racer--find-file (path line column find-file-func)
531 | "Open PATH and move point to LINE and COLUMN."
532 | (funcall find-file-func path)
533 | (goto-char (point-min))
534 | (forward-line (1- line))
535 | (forward-char column))
536 |
537 | (defun racer--button-go-to-src (button)
538 | (racer--find-file
539 | (button-get button 'path)
540 | (button-get button 'line)
541 | (button-get button 'column)
542 | #'find-file))
543 |
544 | (define-button-type 'racer-src-button
545 | 'action 'racer--button-go-to-src
546 | 'follow-link t
547 | 'help-echo "Go to definition")
548 |
549 | (defun racer--url-button (text url)
550 | "Return a button that opens a browser at URL."
551 | (with-temp-buffer
552 | (insert-text-button
553 | text
554 | :type 'help-url
555 | 'help-args (list url))
556 | (buffer-string)))
557 |
558 | (defun racer--src-button (path line column)
559 | "Return a button that navigates to PATH at LINE number and
560 | COLUMN number."
561 | ;; Convert "/foo/bar/baz/foo.rs" to "baz/foo.rs"
562 | (let* ((filename (f-filename path))
563 | (parent-dir (f-filename (f-parent path)))
564 | (short-path (f-join parent-dir filename)))
565 | (with-temp-buffer
566 | (insert-text-button
567 | short-path
568 | :type 'racer-src-button
569 | 'path path
570 | 'line line
571 | 'column column)
572 | (buffer-string))))
573 |
574 | (defun racer--kind-description (raw-kind)
575 | "Human friendly description of a rust kind.
576 | For example, 'EnumKind' -> 'an enum kind'."
577 | (let* ((parts (s-split-words raw-kind))
578 | (description (s-join " " (--map (downcase it) parts)))
579 | (a (if (string-match-p (rx bos (or "a" "e" "i" "o" "u")) description)
580 | "an" "a")))
581 | (format "%s %s" a description)))
582 |
583 | (defun racer--describe (name)
584 | "Return a *Racer Help* buffer for the function or type at point.
585 | If there are multiple candidates at point, use NAME to find the
586 | correct value."
587 | (let ((descriptions (racer--describe-at-point name)))
588 | (when descriptions
589 | (racer--help-buf
590 | (let ((output "")
591 | (first-iteration t))
592 | (dolist (description descriptions output)
593 | (unless first-iteration
594 | (setf output
595 | (concat output (format "\n---------------------------------------------------------------\n"))))
596 | (setf output
597 | (concat
598 | output
599 | (let* ((name (plist-get description :name))
600 | (raw-docstring (plist-get description :docstring))
601 | (docstring (if raw-docstring
602 | (racer--propertize-docstring raw-docstring)
603 | "Not documented."))
604 | (kind (plist-get description :kind)))
605 | (setf first-iteration nil)
606 | (format
607 | "%s is %s defined in %s.\n\n%s%s"
608 | name
609 | (racer--kind-description kind)
610 | (racer--src-button
611 | (plist-get description :path)
612 | (plist-get description :line)
613 | (plist-get description :column))
614 | (if (equal kind "Module")
615 | ;; No point showing the 'signature' of modules, which is
616 | ;; just their full path.
617 | ""
618 | (format " %s\n\n" (racer--syntax-highlight (plist-get description :signature))))
619 | docstring))))))))))
620 |
621 | (defun racer-describe ()
622 | "Show a *Racer Help* buffer for the function or type at point."
623 | (interactive)
624 | (let ((buf (racer--describe (thing-at-point 'symbol))))
625 | (if buf
626 | (temp-buffer-window-show buf)
627 | (user-error "No function or type found at point"))))
628 |
629 | (defface racer-tooltip
630 | '((((min-colors 16777216))
631 | :background "#292C33" :foreground "white")
632 | (t
633 | :background "black" :foreground "white"))
634 | "Face used for the tooltip with `racer-describe-tooltip'")
635 |
636 | (defun racer-describe-tooltip ()
637 | "Show the docstring in a tooltip.
638 | The tooltip's face is `racer-tooltip'
639 | See `racer-describe'."
640 | (interactive)
641 | (-some-> (symbol-at-point)
642 | (symbol-name)
643 | (racer--describe)
644 | (with-current-buffer (concat "\n" (buffer-string) "\n\n"))
645 | (pos-tip-show-no-propertize 'racer-tooltip nil nil 1000)))
646 |
647 | (defvar racer-help-mode-map
648 | (let ((map (make-sparse-keymap)))
649 | (set-keymap-parent map (make-composed-keymap button-buffer-map
650 | special-mode-map))
651 | map)
652 | "Keymap for racer help mode.")
653 |
654 | (define-derived-mode racer-help-mode fundamental-mode
655 | "Racer-Help"
656 | "Major mode for *Racer Help* buffers.
657 |
658 | Commands:
659 | \\{racer-help-mode-map}")
660 |
661 | (defcustom racer-complete-in-comments
662 | nil
663 | "If non-nil, query racer for completions inside comments too."
664 | :type 'boolean
665 | :group 'racer)
666 |
667 | (defcustom racer-complete-insert-argument-placeholders
668 | t
669 | "If non-nil, insert argument placeholders after completion.
670 | Note that this feature is only available when `company-mode' is installed."
671 | :type 'boolean
672 | :group 'racer)
673 |
674 | (defun racer-complete-at-point ()
675 | "Complete the symbol at point."
676 | (let* ((ppss (syntax-ppss))
677 | (in-string (nth 3 ppss))
678 | (in-comment (nth 4 ppss)))
679 | (when (and
680 | (not in-string)
681 | (or (not in-comment) racer-complete-in-comments))
682 | (let* ((bounds (bounds-of-thing-at-point 'symbol))
683 | (beg (or (car bounds) (point)))
684 | (end (or (cdr bounds) (point))))
685 | (list beg end
686 | (completion-table-dynamic #'racer-complete)
687 | :annotation-function #'racer-complete--annotation
688 | :company-prefix-length (racer-complete--prefix-p beg end)
689 | :company-docsig #'racer-complete--docsig
690 | :company-doc-buffer #'racer--describe
691 | :company-location #'racer-complete--location
692 | :exit-function #'racer-complete--insert-args)))))
693 |
694 | (declare-function company-template-c-like-templatify 'company-template)
695 |
696 | (defun racer-complete--insert-args (arg &optional _finished)
697 | "If a ARG is the name of a completed function, try to find and insert its arguments."
698 | (when (and racer-complete-insert-argument-placeholders
699 | (require 'company-template nil t)
700 | (equal "Function"
701 | (get-text-property 0 'matchtype arg))
702 | ;; Don't add arguments if the user has already added
703 | ;; some.
704 | (not (eq (char-after) ?\()))
705 | (let* ((ctx (get-text-property 0 'ctx arg))
706 | (arguments (racer-complete--extract-args ctx)))
707 | (insert arguments)
708 | (company-template-c-like-templatify arguments))))
709 |
710 | (defun racer-complete--extract-args (str)
711 | "Extract function arguments from STR (excluding a possible self argument)."
712 | (string-match
713 | (rx
714 | (or (seq "(" (zero-or-more (not (any ","))) "self)")
715 | (seq "("
716 | (zero-or-more (seq (zero-or-more (not (any "(")))
717 | "self"
718 | (zero-or-more space)
719 | ","))
720 | (zero-or-more space)
721 | (group (zero-or-more (not (any ")"))))
722 | ")")))
723 | str)
724 | (let ((extract (match-string 1 str)))
725 | (if extract
726 | (format "(%s)" extract)
727 | "()")))
728 |
729 | (defun racer--file-and-parent (path)
730 | "Convert /foo/bar/baz/q.txt to baz/q.txt."
731 | (let ((file (f-filename path))
732 | (parent (f-filename (f-parent path))))
733 | (f-join parent file)))
734 |
735 | (defun racer-complete (&optional _ignore)
736 | "Completion candidates at point."
737 | (->> (racer--call-at-point "complete")
738 | (--filter (s-starts-with? "MATCH" it))
739 | (--map (-let [(name line col file matchtype ctx)
740 | (s-split-up-to "," (s-chop-prefix "MATCH " it) 5)]
741 | (put-text-property 0 1 'line (string-to-number line) name)
742 | (put-text-property 0 1 'col (string-to-number col) name)
743 | (put-text-property 0 1 'file file name)
744 | (put-text-property 0 1 'matchtype matchtype name)
745 | (put-text-property 0 1 'ctx ctx name)
746 | name))))
747 |
748 | (defun racer--trim-up-to (needle s)
749 | "Return content after the occurrence of NEEDLE in S."
750 | (-if-let (idx (s-index-of needle s))
751 | (substring s (+ idx (length needle)))
752 | s))
753 |
754 | (defun racer-complete--prefix-p (beg _end)
755 | "Return t if a completion should be triggered for a prefix between BEG and END."
756 | (save-excursion
757 | (goto-char beg)
758 | ;; If we're at the beginning of the buffer, we can't look back 2
759 | ;; characters.
760 | (ignore-errors
761 | (looking-back "\\.\\|::" 2))))
762 |
763 | (defun racer-complete--annotation (arg)
764 | "Return an annotation for completion candidate ARG."
765 | (let* ((ctx (get-text-property 0 'ctx arg))
766 | (type (get-text-property 0 'matchtype arg))
767 | (pretty-ctx
768 | (pcase type
769 | ("Module"
770 | (if (string= arg ctx)
771 | ""
772 | (concat " " (racer--file-and-parent ctx))))
773 | ("StructField"
774 | (concat " " ctx))
775 | (_
776 | (->> ctx
777 | (racer--trim-up-to arg)
778 | (s-chop-suffixes '(" {" "," ";")))))))
779 | (format "%s : %s" pretty-ctx type)))
780 |
781 | (defun racer-complete--docsig (arg)
782 | "Return a signature for completion candidate ARG."
783 | (racer--syntax-highlight (format "%s" (get-text-property 0 'ctx arg))))
784 |
785 | (defun racer-complete--location (arg)
786 | "Return location of completion candidate ARG."
787 | (cons (get-text-property 0 'file arg)
788 | (get-text-property 0 'line arg)))
789 |
790 | (defun racer--current-column ()
791 | "Get the current column based on underlying character representation."
792 | (length (buffer-substring-no-properties
793 | (line-beginning-position) (point))))
794 |
795 |
796 | (defun racer--find-definition(find-file-func)
797 | (-if-let (match (--first (s-starts-with? "MATCH" it)
798 | (racer--call-at-point "find-definition")))
799 | (-let [(_name line col file _matchtype _ctx)
800 | (s-split-up-to "," (s-chop-prefix "MATCH " match) 5)]
801 | (if (fboundp 'xref-push-marker-stack)
802 | (xref-push-marker-stack)
803 | (with-no-warnings
804 | (ring-insert find-tag-marker-ring (point-marker))))
805 | (racer--find-file file (string-to-number line) (string-to-number col) find-file-func))
806 | (error "No definition found")))
807 |
808 | ;;;###autoload
809 | (defun racer-find-definition ()
810 | "Run the racer find-definition command and process the results."
811 | (interactive)
812 | (racer--find-definition #'find-file))
813 |
814 | ;;;###autoload
815 | (defun racer-find-definition-other-window ()
816 | "Run the racer find-definition command and process the results."
817 | (interactive)
818 | (racer--find-definition #'find-file-other-window))
819 |
820 | ;;;###autoload
821 | (defun racer-find-definition-other-frame ()
822 | "Run the racer find-definition command and process the results."
823 | (interactive)
824 | (racer--find-definition #'find-file-other-frame))
825 |
826 | (defun racer--syntax-highlight (str)
827 | "Apply font-lock properties to a string STR of Rust code."
828 | (let (result)
829 | ;; Load all of STR in a rust-mode buffer, and use its
830 | ;; highlighting.
831 | (save-match-data
832 | (with-temp-buffer
833 | (insert str)
834 | (delay-mode-hooks (rust-mode))
835 | (if (fboundp 'font-lock-ensure)
836 | (font-lock-ensure)
837 | (with-no-warnings
838 | (font-lock-fontify-buffer)))
839 | (setq result (buffer-string))))
840 |
841 | ;; If we haven't applied any text properties yet, apply some
842 | ;; heuristics to try to find an appropriate colour.
843 | (when (null (text-properties-at 0 result))
844 | (cond
845 | ;; If it's a standalone symbol, then assume it's a
846 | ;; variable.
847 | ((string-match-p (rx bos (+ (any lower "_")) eos) str)
848 | (setq result (propertize str 'face 'font-lock-variable-name-face)))
849 | ;; If it starts with a backslash, treat it as a string. See
850 | ;; .lines() on strings.
851 | ((string-match-p (rx bos "\\") str)
852 | (setq result (propertize str 'face 'font-lock-string-face)))))
853 |
854 | result))
855 |
856 | (defun racer--goto-func-name ()
857 | "If point is inside a function call, move to the function name.
858 |
859 | foo(bar, |baz); -> foo|(bar, baz);"
860 | (let ((last-paren-pos (nth 1 (syntax-ppss)))
861 | (start-pos (point)))
862 | (when last-paren-pos
863 | ;; Move to just before the last paren.
864 | (goto-char last-paren-pos)
865 | ;; If we're inside a round paren, we're inside a function call.
866 | (unless (looking-at "(")
867 | ;; Otherwise, return to our start position, as point may have been on a
868 | ;; function already:
869 | ;; foo|(bar, baz);
870 | (goto-char start-pos)))))
871 |
872 | (defun racer--relative (path &optional directory)
873 | "Return PATH relative to DIRECTORY (`default-directory' by default).
874 | If PATH is not in DIRECTORY, just abbreviate it."
875 | (unless directory
876 | (setq directory default-directory))
877 | (if (s-starts-with? directory path)
878 | (concat "./" (f-relative path directory))
879 | (f-abbrev path)))
880 |
881 | (defcustom racer-eldoc-timeout 0.5
882 | "Abandon Eldoc hinting if racer process fails to respond for
883 | that many seconds (maybe float)."
884 | :type 'number
885 | :group 'racer)
886 |
887 | (defun racer-eldoc ()
888 | "Show eldoc for context at point."
889 | (save-excursion
890 | (racer--goto-func-name)
891 | ;; If there's a variable at point:
892 | (-when-let* ((rust-sym (symbol-at-point))
893 | (comp-possibilities (let ((racer-command-timeout racer-eldoc-timeout))
894 | (racer-complete)))
895 | (matching-possibility
896 | (--find (string= it (symbol-name rust-sym)) comp-possibilities))
897 | (prototype (get-text-property 0 'ctx matching-possibility))
898 | (matchtype (get-text-property 0 'matchtype matching-possibility)))
899 | (if (equal matchtype "Module")
900 | (racer--relative prototype)
901 | ;; Syntax highlight function signatures.
902 | (racer--syntax-highlight prototype)))))
903 |
904 | (defvar racer-mode-map
905 | (let ((map (make-sparse-keymap)))
906 | (define-key map (kbd "M-.") #'racer-find-definition)
907 | (define-key map (kbd "C-x 4 .") #'racer-find-definition-other-window)
908 | (define-key map (kbd "C-x 5 .") #'racer-find-definition-other-frame)
909 | (define-key map (kbd "M-,") #'pop-tag-mark)
910 | map))
911 |
912 | ;;;###autoload
913 | (define-minor-mode racer-mode
914 | "Minor mode for racer."
915 | :lighter " racer"
916 | :keymap racer-mode-map
917 | (setq-local eldoc-documentation-function #'racer-eldoc)
918 | (set (make-local-variable 'completion-at-point-functions) nil)
919 | (add-hook 'completion-at-point-functions #'racer-complete-at-point))
920 |
921 | (define-obsolete-function-alias 'racer-turn-on-eldoc 'eldoc-mode "2015-08-24")
922 | (define-obsolete-function-alias 'racer-activate 'racer-mode "2015-08-24")
923 |
924 | (provide 'racer)
925 | ;;; racer.el ends here
926 |
--------------------------------------------------------------------------------
/test/racer-test.el:
--------------------------------------------------------------------------------
1 | (require 'racer)
2 | (require 'ert)
3 |
4 | (ert-deftest racer--file-and-parent ()
5 | (should
6 | (equal
7 | (racer--file-and-parent "/foo/bar/baz/q.txt")
8 | "baz/q.txt")))
9 |
10 | (ert-deftest racer--goto-func-name ()
11 | (with-temp-buffer
12 | ;; Insert a function call.
13 | (insert "foo(bar, baz);")
14 | ;; Move to the start of the second argument.
15 | (goto-char (point-min))
16 | (while (not (looking-at "baz"))
17 | (forward-char 1))
18 | ;; We should be at the end of foo.
19 | (racer--goto-func-name)
20 | (should (equal (point) 4))))
21 |
22 | (ert-deftest racer--read-rust-string ()
23 | (should
24 | (equal
25 | (racer--read-rust-string "\"foo \\n \\\" \\' \\; bar")
26 | "foo \n \" ' ; bar")))
27 |
28 | (ert-deftest racer--read-rust-string-backslash-n ()
29 | "Regression test for a literal backslash followed by an n, not
30 | a newline."
31 | (should
32 | (equal
33 | (racer--read-rust-string "\\\\n")
34 | "\\n")))
35 |
36 | (ert-deftest racer--help-buf ()
37 | (should
38 | (bufferp
39 | (racer--help-buf "foo bar."))))
40 |
41 | (ert-deftest racer--propertize-all-inline-code ()
42 | (should
43 | (equal-including-properties
44 | (racer--propertize-all-inline-code "foo `bar` [`baz`] biz")
45 | #("foo bar baz biz" 4 7 (face font-lock-variable-name-face) 8 11 (face font-lock-variable-name-face)))))
46 |
47 | (ert-deftest racer--propertize-all-inline-code-with-escaped-newlines ()
48 | "Regression test for an escaped newline at the end of inline code."
49 | (should
50 | (equal
51 | (racer--propertize-all-inline-code "foo `\\n`")
52 | "foo \\n")))
53 |
54 | (ert-deftest racer--propertize-docstring-code ()
55 | "Ensure we render code blocks with indents."
56 | (should
57 | (equal
58 | (racer--propertize-docstring "foo
59 |
60 | ```rust
61 | func();
62 | ```
63 |
64 | bar.
65 |
66 | ```text
67 | 1
68 | 2
69 | ```
70 | ")
71 | "foo
72 |
73 | func();
74 |
75 | bar.
76 |
77 | 1
78 | 2")))
79 |
80 | (defun racer--remove-properties (text)
81 | "Remove all the properties on TEXT.
82 | Tests that use `equal' ignore properties, but
83 | this makes the ert failure descriptions clearer."
84 | (with-temp-buffer
85 | (insert text)
86 | (buffer-substring-no-properties (point-min) (point-max))))
87 |
88 | (ert-deftest racer--propertize-docstring-code-annotations ()
89 | "Ignore '# foo' lines in code sections in docstrings."
90 | (should
91 | (equal
92 | (racer--remove-properties
93 | (racer--propertize-docstring "```
94 | # #[allow(dead_code)]
95 | #[derive(Debug)]
96 | struct Foo {}
97 | ```"))
98 | " #[derive(Debug)]
99 | struct Foo {}")))
100 |
101 | (ert-deftest racer--propertize-docstring-code-newlines ()
102 | "Ensure we always have a blank line before a code block."
103 |
104 | (should
105 | (equal
106 | (racer--remove-properties
107 | (racer--propertize-docstring "```
108 | bar1();
109 | ```
110 | foo
111 | ```
112 | bar2();
113 | ```"))
114 | " bar1();
115 |
116 | foo
117 |
118 | bar2();")))
119 |
120 | (ert-deftest racer--propertize-docstring-newlines ()
121 | "Ensure we still handle links that have been split over two lines."
122 | (should
123 | (equal
124 | (racer--propertize-docstring "[foo\nbar](baz)")
125 | "foo\nbar")))
126 |
127 | (ert-deftest racer--propertize-docstring-link-after-attribute ()
128 | "We should not confuse attributes with links."
129 | (should
130 | (equal
131 | (racer--remove-properties
132 | (racer--propertize-docstring "Result is annotated with the #[must_use] attribute,
133 | by the [`Write`](../../std/io/trait.Write.html) trait"))
134 | "Result is annotated with the #[must_use] attribute,
135 | by the Write trait")))
136 |
137 | (ert-deftest racer--propertize-docstring-footnotes ()
138 | "Ensure we discard footnote links."
139 | (should
140 | (equal
141 | (racer--remove-properties
142 | (racer--propertize-docstring "foo [`str`] bar
143 |
144 | \[`str`]: ../../std/primitive.str.html
145 |
146 | baz."))
147 | "foo str bar
148 |
149 | baz.")))
150 |
151 | (ert-deftest racer--propertize-docstring-urls ()
152 | "Ensure we render buttons for links with urls."
153 | (let ((result (racer--propertize-docstring "[foo](http://example.com)")))
154 | (should (equal result "foo"))
155 | (should (equal (get-text-property 0 'button result) '(t))))
156 | (should
157 | (equal-including-properties
158 | (racer--propertize-docstring "[foo](#bar)")
159 | "foo")))
160 |
161 | (ert-deftest racer--propertize-docstring-heading ()
162 | "Ensure we render markdown headings correctly."
163 | (should
164 | (equal-including-properties
165 | (racer--propertize-docstring "# foo")
166 | #("foo" 0 3 (face racer-help-heading-face)))))
167 |
168 | (ert-deftest racer--split-parts ()
169 | "Ensure we correctly parse racer CSV."
170 | (should
171 | (equal (racer--split-parts "foo;bar")
172 | '("foo" "bar")))
173 | (should
174 | (equal (racer--split-parts "foo;\"bar\"")
175 | '("foo" "bar")))
176 | (should
177 | (equal (racer--split-parts "foo\\;bar;baz")
178 | '("foo;bar" "baz"))))
179 |
180 | (ert-deftest racer--describe-at-point-name ()
181 | "Ensure we extract the correct name in `racer--describe-at-point'."
182 | (cl-letf (((symbol-function 'racer--call)
183 | (lambda (&rest _)
184 | (s-join
185 | "\n"
186 | (list
187 | "PREFIX 36,37,n"
188 | "MATCH new;new();294;11;/home/user/src/rustc-1.10.0/src/libstd/../libcollections/vec.rs;Function;pub fn new() -> Vec;\"Constructs a new, empty `Vec`.\""
189 | "END")))))
190 | (dolist (desc (racer--describe-at-point "new"))
191 | (should
192 | (equal (plist-get desc :name) "new")))))
193 |
194 | (ert-deftest racer--describe-at-point-nil-docstring ()
195 | "If there's no docstring, racer--describe-at-point should use nil."
196 | (cl-letf (((symbol-function 'racer--call)
197 | (lambda (&rest _)
198 | (s-join
199 | "\n"
200 | (list
201 | "PREFIX 36,37,n"
202 | "MATCH new;new();294;11;/home/user/src/rustc-1.10.0/src/libstd/../libcollections/vec.rs;Function;pub fn new() -> Vec;\"\""
203 | "END")))))
204 | (dolist (desc (racer--describe-at-point "new"))
205 | (should
206 | (null (plist-get desc :docstring))))))
207 |
208 | (ert-deftest racer--describe-at-point-shortest ()
209 | "If there are multiple matches, we want the shortest.
210 |
211 | Since we've moved point to the end of symbol, the other functions just happen to have the same prefix."
212 | (cl-letf (((symbol-function 'racer--call)
213 | (lambda (&rest _)
214 | (s-join
215 | "\n"
216 | (list
217 | "PREFIX 36,37,n"
218 | "MATCH new_bar;new_bar();294;11;/home/user/src/rustc-1.10.0/src/libstd/../libcollections/vec.rs;Function;pub fn new() -> Vec;\"\""
219 | "MATCH new;new();294;11;/home/user/src/rustc-1.10.0/src/libstd/../libcollections/vec.rs;Function;pub fn new() -> Vec;\"\""
220 | "MATCH new_foo;new_foo();294;11;/home/user/src/rustc-1.10.0/src/libstd/../libcollections/vec.rs;Function;pub fn new() -> Vec;\"\""
221 | "END")))))
222 | (dolist (desc (racer--describe-at-point "new"))
223 | (should
224 | (equal (plist-get desc :name) "new")))))
225 |
226 | (ert-deftest racer--syntax-highlight ()
227 | "Ensure we highlight code blocks and snippets correctly."
228 | ;; Highlighting types should always use the type face.
229 | (should
230 | (equal-including-properties
231 | (racer--syntax-highlight "Foo")
232 | #("Foo" 0 3 (face font-lock-type-face))))
233 | ;; Highlighting keywords.
234 | (should
235 | (equal-including-properties
236 | (racer--syntax-highlight "false")
237 | #("false" 0 5 (face font-lock-keyword-face))))
238 | ;; Simple variables should be highlighted, even when standalone.
239 | (should
240 | (equal-including-properties
241 | (racer--syntax-highlight "foo")
242 | #("foo" 0 3 (face font-lock-variable-name-face)))))
243 |
244 | (ert-deftest racer-describe ()
245 | "Smoke test for `racer-describe'."
246 | (cl-letf (((symbol-function 'racer--call)
247 | (lambda (&rest _)
248 | (s-join
249 | "\n"
250 | (list
251 | "PREFIX 36,37,n"
252 | "MATCH foo;foo();294;11;/home/user/src/rustc-1.10.0/src/libstd/../libcollections/vec.rs;Function;pub fn new() -> Vec;\"\""
253 | "END")))))
254 | (with-temp-buffer
255 | (rust-mode)
256 | (insert "foo();")
257 | (goto-char (point-min))
258 | (racer-describe))))
259 |
260 | (ert-deftest racer-describe-test-description ()
261 | "Ensure we write the correct text summary in the first line
262 | of the racer describe buffer."
263 | (cl-letf (((symbol-function 'racer--call)
264 | (lambda (&rest _)
265 | "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")))
266 | (with-temp-buffer
267 | (rust-mode)
268 | (insert "Ok")
269 | (goto-char (point-min))
270 | (switch-to-buffer (racer--describe "Ok"))
271 | (let ((first-line (-first-item (s-lines (buffer-substring-no-properties
272 | (point-min) (point-max))))))
273 | (should
274 | (equal first-line
275 | "Ok is an enum variant defined in libcore/result.rs."))))))
276 |
277 | (ert-deftest racer-describe-module-description ()
278 | "Ensure we write the correct text summary in the first line
279 | of the racer describe buffer."
280 | (cl-letf (((symbol-function 'racer--call)
281 | (lambda (&rest _)
282 | "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")))
283 | (with-temp-buffer
284 | (rust-mode)
285 | (insert "extern crate matches;")
286 | (goto-char (1- (point-max)))
287 | (switch-to-buffer (racer--describe "matches"))
288 | (should
289 | (equal (racer--remove-properties (buffer-string))
290 | "matches is a module defined in matches-0.1.2/lib.rs.
291 |
292 | Not documented.")))))
293 |
294 | (ert-deftest racer-describe-uses-whole-symbol ()
295 | "Racer uses the symbol *before* point, so make sure we move point to
296 | the end of the current symbol.
297 |
298 | Otherwise, if the point is at the start of the symbol, we don't find anything."
299 | (let (point-during-call)
300 | (cl-letf (((symbol-function 'racer--call)
301 | (lambda (&rest _)
302 | (setq point-during-call (point))
303 | (s-join
304 | "\n"
305 | (list
306 | "PREFIX 36,37,n"
307 | "MATCH foo;foo();294;11;/home/user/src/rustc-1.10.0/src/libstd/../libcollections/vec.rs;Function;pub fn new() -> Vec;\"\""
308 | "END")))))
309 | (with-temp-buffer
310 | (rust-mode)
311 | (insert "foo();")
312 | (goto-char (point-min))
313 | ;; This should move point to the end of 'foo' before calling
314 | ;; racer--call.
315 | (racer-describe))
316 | (should (equal point-during-call 4)))))
317 |
318 | (ert-deftest racer-debug ()
319 | "Smoke test for `racer-debug'."
320 | (let ((racer--prev-state
321 | (list
322 | :program "racer"
323 | :args '("complete" "1" "2")
324 | :exit-code 0
325 | :stdout "PREFIX 1,2,Ok\nMATCH FOO\nEND\n"
326 | :stderr ""
327 | :default-directory "/"
328 | :process-environment
329 | '("RUST_SRC_PATH=/home/user/src/rustc-1.10.0/src"
330 | "CARGO_HOME=/home/user/.cargo"))))
331 | (racer-debug)))
332 |
333 | (ert-deftest racer--relative ()
334 | ;; Common case: the path is relative to the directory.
335 | (should (equal (racer--relative "/foo/bar" "/foo")
336 | "./bar"))
337 | ;; Path is not relative, but it's a home directory.
338 | (should (equal (racer--relative (f-expand "~/foo")
339 | (f-expand "~/bar"))
340 | "~/foo"))
341 | ;; Path is not relative and not a home directory.
342 | (should (equal (racer--relative "/foo/bar" "/quux")
343 | "/foo/bar")))
344 |
345 | (ert-deftest racer-eldoc-no-completions ()
346 | "`racer-eldoc' should handle no completions gracefully."
347 | (cl-letf (((symbol-function 'racer--call)
348 | (lambda (&rest _)
349 | "PREFIX 4,4,\nEND\n")))
350 | (with-temp-buffer
351 | (rust-mode)
352 | (insert "use ")
353 | ;; Midle of the 'use'.
354 | (goto-char 2)
355 | ;; Should return nil without crashing.
356 | (should (null (racer-eldoc))))))
357 |
358 | (ert-deftest racer-complete--extract-args ()
359 | (should
360 | (equal
361 | (racer-complete--extract-args
362 | "pub unsafe fn alloc(layout: Layout) -> *mut u8")
363 | "(layout: Layout)")))
364 |
--------------------------------------------------------------------------------
/test/test-helper.el:
--------------------------------------------------------------------------------
1 | ;;; test-helper --- Test helper for racer
2 |
3 | ;;; Commentary:
4 |
5 | ;;; Code:
6 |
7 | (require 'ert)
8 | (require 'f)
9 |
10 | (let ((racer-dir (f-parent (f-dirname (f-this-file)))))
11 | (add-to-list 'load-path racer-dir))
12 |
13 | (require 'undercover)
14 | (undercover "racer.el" (:exclude "*-test.el"))
15 |
16 | ;;; test-helper.el ends here
17 |
--------------------------------------------------------------------------------