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