|
;;; company-ycmd.el --- company-mode backend for ycmd -*- lexical-binding: t -*-
|
|
;;
|
|
;; Copyright (c) 2014-2017 Austin Bingham, Peter Vasil
|
|
;;
|
|
;; Authors: Austin Bingham <austin.bingham@gmail.com>
|
|
;; Peter Vasil <mail@petervasil.net>
|
|
;; version: 0.2
|
|
;; Package-Version: 20180520.1053
|
|
;; URL: https://github.com/abingham/emacs-ycmd
|
|
;; Package-Requires: ((ycmd "1.3") (company "0.9.3") (deferred "0.5.1") (s "1.11.0") (dash "2.13.0") (let-alist "1.0.5") (f "0.19.0"))
|
|
;;
|
|
;; This file is not part of GNU Emacs.
|
|
;;
|
|
;;; Commentary:
|
|
;;
|
|
;; Description:
|
|
;;
|
|
;; ycmd is a modular code-completion framework. It includes, for
|
|
;; example, completion for C/C++/ObjC and Python. This module supplies
|
|
;; a company-mode backend for these completions.
|
|
;;
|
|
;; For more details, see the project page at
|
|
;; https://github.com/abingham/emacs-ycmd.
|
|
;;
|
|
;; Installation:
|
|
;;
|
|
;; Copy this file to to some location in your emacs load path. Then add
|
|
;; "(require 'company-ycmd)" to your emacs initialization (.emacs,
|
|
;; init.el, or something).
|
|
;;
|
|
;; Example config:
|
|
;;
|
|
;; (require 'company-ycmd)
|
|
;;
|
|
;;; License:
|
|
;;
|
|
;; 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.
|
|
|
|
;;; Code:
|
|
|
|
(eval-when-compile
|
|
(require 'let-alist))
|
|
(require 'cl-lib)
|
|
(require 'company)
|
|
(require 'company-template)
|
|
(require 'deferred)
|
|
(require 'ycmd)
|
|
(require 's)
|
|
(require 'f)
|
|
(require 'dash)
|
|
(require 'rx)
|
|
|
|
(defgroup company-ycmd nil
|
|
"Company-mode completion backend for ycmd."
|
|
:group 'company
|
|
:group 'ycmd)
|
|
|
|
(defcustom company-ycmd-insert-arguments t
|
|
"When non-nil, insert function arguments as a template after completion.
|
|
|
|
Only supported by modes in `company-ycmd--extended-features-modes'"
|
|
:type 'boolean)
|
|
|
|
(defcustom company-ycmd-enable-fuzzy-matching t
|
|
"When non-nil, use fuzzy matching for completion candidates.
|
|
|
|
Setting this to nil enables the `company-mode' internal cache
|
|
feature."
|
|
:type 'boolean)
|
|
|
|
(defcustom company-ycmd-show-completion-kind t
|
|
"Show kind of completion entry."
|
|
:type 'boolean)
|
|
|
|
(defcustom company-ycmd-request-sync-timeout 0.05
|
|
"Timeout for synchronous ycmd completion request.
|
|
When 0, do not use synchronous completion request at all."
|
|
:type 'number)
|
|
|
|
(defconst company-ycmd--extended-features-modes
|
|
'(c++-mode
|
|
c-mode
|
|
go-mode
|
|
objc-mode
|
|
rust-mode
|
|
swift-mode
|
|
python-mode
|
|
js-mode
|
|
typescript-mode)
|
|
"Major modes which have extended features in `company-ycmd'.")
|
|
|
|
(defun company-ycmd--extended-features-p ()
|
|
"Check whether to use extended features."
|
|
(memq major-mode company-ycmd--extended-features-modes))
|
|
|
|
(defun company-ycmd--prefix-candidate-p (candidate prefix)
|
|
"Return t if CANDIDATE string begins with PREFIX."
|
|
(let ((insertion-text (cdr (assq 'insertion_text candidate))))
|
|
(s-starts-with? prefix insertion-text t)))
|
|
|
|
(defun company-ycmd--filename-completer-p (extra-info)
|
|
"Check whether candidate's EXTRA-INFO indicates a filename completion."
|
|
(-contains? '("[File]" "[Dir]" "[File&Dir]") extra-info))
|
|
|
|
(defun company-ycmd--identifier-completer-p (extra-info)
|
|
"Check if candidate's EXTRA-INFO indicates a identifier completion."
|
|
(s-equals? "[ID]" extra-info))
|
|
|
|
(defmacro company-ycmd--with-destructured-candidate (candidate &rest body)
|
|
(declare (indent 1) (debug t))
|
|
`(let-alist ,candidate
|
|
(if (or (company-ycmd--identifier-completer-p .extra_menu_info)
|
|
(company-ycmd--filename-completer-p .extra_menu_info))
|
|
(propertize .insertion_text 'return_type .extra_menu_info)
|
|
,@body)))
|
|
|
|
(defun company-ycmd--extract-params-clang (function-signature)
|
|
"Extract parameters from FUNCTION-SIGNATURE if possible."
|
|
(let ((params (company-ycmd--extract-params-clang-1
|
|
function-signature)))
|
|
(if (not (and params (string-prefix-p "(*)" params)))
|
|
params
|
|
(with-temp-buffer
|
|
(insert params)
|
|
(search-backward ")")
|
|
(let ((pt (1+ (point))))
|
|
(re-search-forward ".\\_>" nil t)
|
|
(delete-region pt (point)))
|
|
(buffer-string)))))
|
|
|
|
(defun company-ycmd--extract-params-clang-1 (function-signature)
|
|
"Extract parameters from FUNCTION-SIGNATURE if possible."
|
|
(cond
|
|
((null function-signature) nil)
|
|
((string-match "[^:]:[^:]" function-signature)
|
|
(substring function-signature (1+ (match-beginning 0))))
|
|
((string-match "\\((.*)[ a-z]*\\'\\)" function-signature)
|
|
(let ((paren (match-beginning 1)))
|
|
(if (not (and (eq (aref function-signature (1- paren)) ?>)
|
|
(s-contains?
|
|
"<" (substring function-signature 0 (1- paren)))))
|
|
(match-string 1 function-signature)
|
|
(with-temp-buffer
|
|
(insert function-signature)
|
|
(goto-char paren)
|
|
(substring function-signature (1- (search-backward "<")))))))))
|
|
|
|
(defun company-ycmd--convert-kind-clang (kind)
|
|
"Convert KIND string for display."
|
|
(pcase kind
|
|
("STRUCT" "struct")
|
|
("CLASS" "class")
|
|
("ENUM" "enum")
|
|
("TYPE" "type")
|
|
("MEMBER" "member")
|
|
("FUNCTION" "fn")
|
|
("VARIABLE" "var")
|
|
("MACRO" "macro")
|
|
("PARAMETER" "parameter")
|
|
("NAMESPACE" "namespace")))
|
|
|
|
(defun company-ycmd--construct-candidate-clang (candidate)
|
|
"Construct a completion string(s) from a CANDIDATE for cpp file-types.
|
|
|
|
Returns a list with one candidate or multiple candidates for
|
|
overloaded functions."
|
|
(company-ycmd--with-destructured-candidate candidate
|
|
(let* ((overloads (and company-ycmd-insert-arguments
|
|
(stringp .detailed_info)
|
|
(s-split "\n" .detailed_info t)))
|
|
(items (or overloads (list .menu_text)))
|
|
candidates)
|
|
(when (eq major-mode 'objc-mode)
|
|
(setq .insertion_text (s-chop-suffix ":" .insertion_text)))
|
|
(dolist (it (delete-dups items) candidates)
|
|
(let* ((meta (if overloads it .detailed_info))
|
|
(kind (company-ycmd--convert-kind-clang .kind))
|
|
(params (and (or (string= kind "fn") (string= kind "class"))
|
|
(company-ycmd--extract-params-clang it)))
|
|
(return-type (or (and overloads
|
|
(let ((case-fold-search nil))
|
|
(and (string-match
|
|
(concat "\\(.*\\) [^ ]*"
|
|
(regexp-quote .insertion_text))
|
|
it)
|
|
(match-string 1 it))))
|
|
.extra_menu_info))
|
|
(doc .extra_data.doc_string))
|
|
(setq candidates
|
|
(cons (propertize .insertion_text 'return_type return-type
|
|
'meta meta 'kind kind 'doc doc 'params params)
|
|
candidates)))))))
|
|
|
|
(defun company-ycmd--construct-candidate-go (candidate)
|
|
"Construct completion string from a CANDIDATE for go file-types."
|
|
(company-ycmd--with-destructured-candidate candidate
|
|
(let* ((is-func (and .extra_menu_info
|
|
(string-prefix-p "func" .extra_menu_info)))
|
|
(meta (and .kind .menu_text .extra_menu_info
|
|
(concat .kind " " .menu_text
|
|
(if is-func
|
|
(substring .extra_menu_info 4 nil)
|
|
(concat " " .extra_menu_info)))))
|
|
(return-type (and .extra_menu_info
|
|
(string-match "^func(.*) \\(.*\\)" .extra_menu_info)
|
|
(match-string 1 .extra_menu_info)))
|
|
(params (and .extra_menu_info
|
|
(or (string-match "^func\\((.*)\\) .*" .extra_menu_info)
|
|
(string-match "^func\\((.*)\\)\\'" .extra_menu_info))
|
|
(match-string 1 .extra_menu_info)))
|
|
(kind (if (and .extra_menu_info (not is-func))
|
|
(concat .kind ": " .extra_menu_info)
|
|
.kind)))
|
|
(propertize .insertion_text 'return_type return-type
|
|
'meta meta 'kind kind 'params params))))
|
|
|
|
(defun company-ycmd--remove-self-from-function-args (args)
|
|
"Remove function argument `self' from ARGS string."
|
|
(if (s-contains? "self" args)
|
|
(when (string-match "(\\(.*\\))" args)
|
|
(->> (s-split "," (match-string 1 args) t)
|
|
(cl-remove-if (lambda (s) (string-match-p "self" s)))
|
|
(s-join ",")
|
|
(s-trim-left)
|
|
(s-prepend (substring args 0 (match-beginning 1)))
|
|
(s-append (substring args (match-end 1)))))
|
|
args))
|
|
|
|
(defun company-ycmd--remove-template-args-from-function-args (args)
|
|
"Remove template arguments from ARGS string."
|
|
(if (s-starts-with? "<" args)
|
|
(substring args (+ 1 (s-index-of ">" args)))
|
|
args))
|
|
|
|
(defun company-ycmd--extract-params-python (function-sig function-name)
|
|
"Extract function arguments from FUNCTION-SIG.
|
|
Use FUNCTION-NAME as part of the regex to match arguments.
|
|
Replace any newline characters with spaces."
|
|
(when (and function-sig
|
|
(string-match
|
|
(concat (regexp-quote function-name)
|
|
;; Regex to match everything between parentheses, including
|
|
;; newline.
|
|
;; https://www.emacswiki.org/emacs/MultilineRegexp
|
|
"\\(([\0-\377[:nonascii:]]*?)\\).*")
|
|
function-sig))
|
|
(s-replace "\n" " " (match-string 1 function-sig))))
|
|
|
|
(defun company-ycmd--extract-meta-python (doc-string)
|
|
"Extract string for meta usage from DOC-STRING.
|
|
Remove newline characters in function arguments and replace them
|
|
with spaces."
|
|
(when doc-string
|
|
(if (string-match "\n" doc-string)
|
|
(let (meta)
|
|
(setq meta (substring doc-string 0 (match-beginning 0)))
|
|
(while (and (string-match-p "(" meta)
|
|
(not (string-match-p ")" meta)))
|
|
(if (string-match "\n" doc-string (match-end 0))
|
|
(setq meta (substring doc-string 0 (match-beginning 0)))
|
|
(setq meta doc-string)))
|
|
(s-replace "\n" " " meta))
|
|
doc-string)))
|
|
|
|
(defun company-ycmd--construct-candidate-python (candidate)
|
|
"Construct completion string from a CANDIDATE for python file-types."
|
|
(company-ycmd--with-destructured-candidate candidate
|
|
(let* ((kind (s-replace "\n" " " .extra_menu_info))
|
|
(params (and (s-prefix-p "def" kind)
|
|
(company-ycmd--extract-params-python
|
|
.detailed_info .insertion_text)))
|
|
(meta (company-ycmd--extract-meta-python .detailed_info))
|
|
(filepath .extra_data.location.filepath)
|
|
(line-num .extra_data.location.line_num))
|
|
(propertize .insertion_text 'meta meta 'doc .detailed_info 'kind kind
|
|
'params params 'filepath filepath 'line_num line-num))))
|
|
|
|
;; The next two function are taken from racer.el
|
|
;; https://github.com/racer-rust/emacs-racer
|
|
(defun company-ycmd--file-and-parent (path)
|
|
"Convert PATH /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 company-ycmd--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 company-ycmd--construct-candidate-rust (candidate)
|
|
"Construct completion string from CANDIDATE for rust file-types."
|
|
(company-ycmd--with-destructured-candidate candidate
|
|
(let* ((meta .extra_menu_info)
|
|
(context (pcase .kind
|
|
("Module"
|
|
(if (string= .insertion_text .extra_menu_info)
|
|
""
|
|
(concat " " (company-ycmd--file-and-parent
|
|
.extra_menu_info))))
|
|
("StructField"
|
|
(concat " " .extra_menu_info))
|
|
(_
|
|
(->> .extra_menu_info
|
|
(company-ycmd--trim-up-to .insertion_text)
|
|
(s-chop-suffixes '(" {" "," ";"))))))
|
|
(annotation (concat context
|
|
(when (s-present? .kind)
|
|
(format " [%s]" .kind))))
|
|
(params (and (string= "Function" .kind)
|
|
(if (string-match "\\(.*?\\) -> .*" context)
|
|
(match-string 1 context)
|
|
context)))
|
|
(filepath .extra_data.location.filepath)
|
|
(line-num .extra_data.location.line_num)
|
|
(column-num .extra_data.location.column_num))
|
|
(propertize .insertion_text 'meta meta 'kind .kind
|
|
'params params 'annotation annotation
|
|
'filepath filepath 'line_num line-num
|
|
'column_num column-num))))
|
|
|
|
;; The next two function are taken from company-sourcekit.el
|
|
;; https://github.com/nathankot/company-sourcekit
|
|
(defun company-ycmd--normalize-source-text-sourcekit (sourcetext)
|
|
"Make a more readable completion candidate out of one with
|
|
placeholders inserted by SourceKit."
|
|
(replace-regexp-in-string
|
|
"<#T##\\(.*?\\)#>"
|
|
(lambda (str)
|
|
;; <#T##Int#> - No label, argument only
|
|
(save-match-data
|
|
(string-match "<#T##\\(.*?\\)#>" str)
|
|
(format "%s" (car (split-string (match-string 1 str) "#")))))
|
|
sourcetext))
|
|
|
|
(defun company-ycmd--build-yasnippet-sourcekit (sourcetext)
|
|
"Build a yasnippet-compatible snippet from the given source
|
|
text template generated by SourceKit."
|
|
(replace-regexp-in-string
|
|
"<#T##\\(.*?\\)#>"
|
|
(lambda (str)
|
|
;; <#T##Int#> - No label, argument only
|
|
(save-match-data
|
|
(string-match "<#T##\\(.*?\\)#>" str)
|
|
(format "${%s}" (car (split-string (match-string 1 str) "#")))))
|
|
sourcetext))
|
|
|
|
(defun company-ycmd--construct-candidate-swift (candidate)
|
|
"Construct completion string from CANDIDATE for swift file-types."
|
|
(company-ycmd--with-destructured-candidate candidate
|
|
(propertize (company-ycmd--normalize-source-text-sourcekit .insertion_text)
|
|
'sourcetext .insertion_text)))
|
|
|
|
(defun company-ycmd--construct-candidate-javascript (candidate)
|
|
"Construct completion string from CANDIDATE for js file-types."
|
|
(company-ycmd--with-destructured-candidate candidate
|
|
(let* ((kind (or (and (string-match "^fn" .extra_menu_info)
|
|
(match-string 0 .extra_menu_info))
|
|
.extra_menu_info))
|
|
(meta .extra_menu_info)
|
|
(params (and .extra_menu_info
|
|
(string-match "^fn\\((.*)\\).*" .extra_menu_info)
|
|
(match-string 1 .extra_menu_info)))
|
|
(return-type (and .extra_menu_info
|
|
(string-match "^fn(.*) -> \\(.*\\)" .extra_menu_info)
|
|
(match-string 1 .extra_menu_info)))
|
|
(doc .detailed_info))
|
|
(propertize .insertion_text 'meta meta 'params params
|
|
'return_type return-type 'kind kind 'doc doc))))
|
|
|
|
(defun company-ycmd--construct-candidate-typescript (candidate)
|
|
"Generic function to construct completion string from a CANDIDATE."
|
|
(company-ycmd--with-destructured-candidate candidate
|
|
(let* ((kind .kind)
|
|
(meta (and .menu_text
|
|
(if .extra_data
|
|
(concat "(" .extra_data ") " .menu_text)
|
|
(if (string-match (concat (regexp-quote .insertion_text)
|
|
"\s*\\(.*\\)")
|
|
.menu_text)
|
|
(match-string 1 .menu_text)
|
|
.menu_text))))
|
|
(base-regexp (concat "^" (regexp-quote .insertion_text)
|
|
"\s*(" (regexp-quote .kind) ") "
|
|
".*\." (regexp-quote .insertion_text)))
|
|
(params (and .menu_text
|
|
(string-match (concat base-regexp "\\((.*)\\):.*")
|
|
.menu_text)
|
|
(match-string 1 .menu_text)))
|
|
(return-type (and .menu_text
|
|
(string-match
|
|
(concat base-regexp
|
|
(and params (regexp-quote params))
|
|
": \\(.*\\)")
|
|
.menu_text)
|
|
(match-string 1 .menu_text))))
|
|
(propertize .insertion_text 'kind kind 'meta meta
|
|
'params params 'return_type return-type))))
|
|
|
|
(defun company-ycmd--construct-candidate-generic (candidate)
|
|
"Generic function to construct completion string from a CANDIDATE."
|
|
(company-ycmd--with-destructured-candidate candidate .insertion_text))
|
|
|
|
(defun company-ycmd--construct-candidates (completions
|
|
prefix
|
|
start-col
|
|
construct-candidate-fn)
|
|
"Construct candidates list from COMPLETIONS.
|
|
|
|
PREFIX is the prefix we calculated for doing the completion, and
|
|
START-COL is the column on which ycmd indicates we should place
|
|
the completion candidates. If START-COL differs from start column
|
|
offset of PREFIX, we need to calculate the substring from PREFIX
|
|
for that difference and prepend it to the insertion-text.
|
|
CONSTRUCT-CANDIDATE-FN is a function to construct a completion
|
|
candidate. See `company-ycmd--get-construct-candidate-fn'.
|
|
|
|
When `company-ycmd-enable-fuzzy-matching' is nil, check if
|
|
candidate starts with PREFIX, whether to include candidate in
|
|
candidates list."
|
|
(let* ((prefix-start-col (- (+ 1 (ycmd--column-in-bytes)) (length prefix)))
|
|
(prefix-size (- start-col prefix-start-col))
|
|
(prefix-diff (substring-no-properties prefix 0 prefix-size))
|
|
(prefix-diff-p (s-present? prefix-diff))
|
|
candidates)
|
|
(dolist (candidate completions (nreverse candidates))
|
|
(when prefix-diff-p
|
|
(let ((it (cdr (assq 'insertion_text candidate))))
|
|
(setf it (s-prepend prefix-diff it))))
|
|
(when (or company-ycmd-enable-fuzzy-matching
|
|
(company-ycmd--prefix-candidate-p candidate prefix))
|
|
(let ((result (funcall construct-candidate-fn candidate)))
|
|
(if (listp result)
|
|
(setq candidates (append result candidates))
|
|
(setq candidates (cons result candidates))))))))
|
|
|
|
(defun company-ycmd--get-construct-candidate-fn ()
|
|
"Return function to construct candidate(s) for current `major-mode'."
|
|
(pcase (car-safe (ycmd-major-mode-to-file-types major-mode))
|
|
((or `"cpp" `"c" `"objc") 'company-ycmd--construct-candidate-clang)
|
|
("go" 'company-ycmd--construct-candidate-go)
|
|
("python" 'company-ycmd--construct-candidate-python)
|
|
("rust" 'company-ycmd--construct-candidate-rust)
|
|
("swift" 'company-ycmd--construct-candidate-swift)
|
|
("javascript" 'company-ycmd--construct-candidate-javascript)
|
|
("typescript" 'company-ycmd--construct-candidate-typescript)
|
|
(_ 'company-ycmd--construct-candidate-generic)))
|
|
|
|
(defun company-ycmd--get-candidates (completions prefix &optional cb)
|
|
"Get candidates for COMPLETIONS and PREFIX.
|
|
|
|
If CB is non-nil, call it with candidates."
|
|
(let-alist completions
|
|
(funcall
|
|
(or cb 'identity)
|
|
(company-ycmd--construct-candidates
|
|
.completions prefix .completion_start_column
|
|
(company-ycmd--get-construct-candidate-fn)))))
|
|
|
|
(defun company-ycmd--get-candidates-deferred (prefix cb)
|
|
"Get completion candidates with PREFIX and call CB deferred."
|
|
(let ((request-window (selected-window))
|
|
(request-point (point))
|
|
(request-tick (buffer-chars-modified-tick)))
|
|
(ycmd-with-handled-server-exceptions (deferred:try (ycmd-get-completions)
|
|
:catch (lambda (_err) nil))
|
|
:bind-current-buffer t
|
|
(if (or (not (equal request-window (selected-window)))
|
|
(with-current-buffer (window-buffer request-window)
|
|
(or (not (equal request-buffer (current-buffer)))
|
|
(not (equal request-point (point)))
|
|
(not (equal request-tick (buffer-chars-modified-tick))))))
|
|
(message "Skip ycmd completion response")
|
|
(company-ycmd--get-candidates response prefix cb)))))
|
|
|
|
(defun company-ycmd--meta (candidate)
|
|
"Fetch the metadata text-property from a CANDIDATE string."
|
|
(let ((meta (get-text-property 0 'meta candidate)))
|
|
(if (stringp meta)
|
|
(let ((meta-trimmed (s-trim meta)))
|
|
(if (company-ycmd--extended-features-p)
|
|
(ycmd--fontify-code meta-trimmed)
|
|
meta-trimmed))
|
|
meta)))
|
|
|
|
(defun company-ycmd--annotation (candidate)
|
|
"Fetch the annotation text-property from a CANDIDATE string."
|
|
(-if-let (annotation (get-text-property 0 'annotation candidate))
|
|
annotation
|
|
(let ((kind (and company-ycmd-show-completion-kind
|
|
(get-text-property 0 'kind candidate)))
|
|
(return-type (get-text-property 0 'return_type candidate))
|
|
(params (get-text-property 0 'params candidate)))
|
|
(concat params
|
|
(when (s-present? return-type)
|
|
(s-prepend " -> " return-type))
|
|
(when (s-present? kind)
|
|
(format " [%s]" kind))))))
|
|
|
|
(defconst company-ycmd--include-declaration
|
|
(rx line-start "#" (zero-or-more blank) (or "include" "import")
|
|
(one-or-more blank)
|
|
(submatch (in "<\"") (zero-or-more (not (in ">\"")))))
|
|
"Regular expression to find C/C++/ObjC include directives.")
|
|
|
|
(defun company-ycmd--in-include ()
|
|
"Check if text before point is an include statement."
|
|
(looking-back company-ycmd--include-declaration
|
|
(line-beginning-position)))
|
|
|
|
(defun company-ycmd--prefix ()
|
|
"Prefix-command handler for the company backend."
|
|
(and ycmd-mode
|
|
buffer-file-name
|
|
(ycmd-running-p)
|
|
(or (not (company-in-string-or-comment))
|
|
(company-ycmd--in-include))
|
|
(or (company-grab-symbol-cons "\\.\\|->\\|::\\|/" 2)
|
|
'stop)))
|
|
|
|
(defun company-ycmd--candidates (prefix)
|
|
"Candidates-command handler for the company backend for PREFIX."
|
|
(let ((fetcher (cons :async
|
|
(lambda (cb)
|
|
(company-ycmd--get-candidates-deferred prefix cb)))))
|
|
(if (> company-ycmd-request-sync-timeout 0)
|
|
(let ((result (ycmd-deferred:sync!
|
|
(ycmd-deferred:timeout company-ycmd-request-sync-timeout
|
|
(funcall (cdr fetcher) nil)))))
|
|
(if (eq result 'timeout) fetcher result))
|
|
fetcher)))
|
|
|
|
(defun company-ycmd--post-completion (candidate)
|
|
"Insert function arguments after completion for CANDIDATE."
|
|
(if (eq major-mode 'swift-mode)
|
|
(company-ycmd--post-completion-swift candidate)
|
|
(--when-let (and (company-ycmd--extended-features-p)
|
|
company-ycmd-insert-arguments
|
|
(get-text-property 0 'params candidate))
|
|
(when (memq major-mode '(python-mode rust-mode))
|
|
(setq it (company-ycmd--remove-self-from-function-args it))
|
|
(when (eq major-mode 'rust-mode)
|
|
(setq it (company-ycmd--remove-template-args-from-function-args it))))
|
|
(insert it)
|
|
(if (string-match "\\`:[^:]" it)
|
|
(company-template-objc-templatify it)
|
|
(company-template-c-like-templatify
|
|
(concat candidate it))))))
|
|
|
|
(declare-function yas-expand-snippet "yasnippet")
|
|
(defun company-ycmd--post-completion-swift (candidate)
|
|
"Insert function arguments after completion for CANDIDATE in
|
|
swift mode."
|
|
(let ((template (company-ycmd--build-yasnippet-sourcekit
|
|
(get-text-property 0 'sourcetext candidate))))
|
|
(yas-expand-snippet template (- (point) (length candidate)) (point))))
|
|
|
|
(defun company-ycmd--doc-buffer (candidate)
|
|
"Return buffer with docstring for CANDIDATE if it is available."
|
|
(let ((doc (get-text-property 0 'doc candidate)))
|
|
(when (s-present? doc)
|
|
(company-doc-buffer doc))))
|
|
|
|
(defun company-ycmd--location (candidate)
|
|
"Return location for CANDIDATE."
|
|
(-when-let* ((filepath (get-text-property 0 'filepath candidate))
|
|
(line-num (get-text-property 0 'line_num candidate)))
|
|
(cons filepath line-num)))
|
|
|
|
(defun company-ycmd (command &optional arg &rest ignored)
|
|
"The company-backend command handler for ycmd."
|
|
(interactive (list 'interactive))
|
|
(cl-case command
|
|
(interactive (company-begin-backend 'company-ycmd))
|
|
(prefix (company-ycmd--prefix))
|
|
(candidates (company-ycmd--candidates arg))
|
|
(meta (company-ycmd--meta arg))
|
|
(annotation (company-ycmd--annotation arg))
|
|
(no-cache company-ycmd-enable-fuzzy-matching)
|
|
(sorted 't)
|
|
(post-completion (company-ycmd--post-completion arg))
|
|
(doc-buffer (company-ycmd--doc-buffer arg))
|
|
(location (company-ycmd--location arg))))
|
|
|
|
;;;###autoload
|
|
(defun company-ycmd-setup ()
|
|
"Add company-ycmd to the front of company-backends."
|
|
(add-to-list 'company-backends 'company-ycmd))
|
|
|
|
(defun company-ycmd--init ()
|
|
(unless (eq company-minimum-prefix-length
|
|
ycmd-min-num-chars-for-completion)
|
|
(setq-local company-minimum-prefix-length
|
|
ycmd-min-num-chars-for-completion)))
|
|
|
|
(add-hook 'ycmd-mode-hook #'company-ycmd--init)
|
|
|
|
(provide 'company-ycmd)
|
|
|
|
;;; company-ycmd.el ends here
|
|
|
|
;; Local Variables:
|
|
;; indent-tabs-mode: nil
|
|
;; End:
|