├── .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 | [![MELPA](http://melpa.org/packages/racer-badge.svg)](http://melpa.org/#/racer) 3 | [![MELPA Stable](http://stable.melpa.org/packages/racer-badge.svg)](http://stable.melpa.org/#/racer) 4 | [![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) 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 | ![racer completion screenshot](images/racer_completion.png) 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 | ![racer go to definition](images/racer_goto.gif) 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 | ![racer completion screenshot](images/racer_help.png) 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 | --------------------------------------------------------------------------------