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