;;; company-go.el --- company-mode backend for Go (using gocode) ;; Copyright (C) 2012 ;; Author: nsf ;; Keywords: languages ;; Package-Version: 20170825.943 ;; Package-Requires: ((company "0.8.0") (go-mode "1.0.0")) ;; No license, this code is under public domain, do whatever you want. ;;; Code: (require 'company-template) (eval-when-compile (require 'cl)) (require 'go-mode) (require 'company) ;; Close gocode daemon at exit unless it was already running (eval-after-load "go-mode" '(progn (let* ((user (or (getenv "USER") "all")) (sock (format (concat temporary-file-directory "gocode-daemon.%s") user))) (unless (file-exists-p sock) (add-hook 'kill-emacs-hook #'(lambda () (ignore-errors (call-process company-go-gocode-command nil nil nil "close")))))))) (defgroup company-go nil "Completion back-end for Go." :group 'company) (defcustom company-go-show-annotation nil "Show an annotation inline with the candidate." :group 'company-go :type 'boolean) (defcustom company-go-begin-after-member-access t "When non-nil, automatic completion will start whenever the current symbol is preceded by a \".\", ignoring `company-minimum-prefix-length'." :group 'company-go :type 'boolean) (defcustom company-go-insert-arguments t "When non-nil, insert function or method arguments as a template after completion." :group 'company-go :type 'boolean) (defcustom company-go-gocode-command "gocode" "The command to invoke `gocode'" :group 'company-go :type 'string) (defcustom company-go-gocode-args nil "Additional arguments to pass to `gocode'" :group 'company-go :type '(repeat string)) (defcustom company-go-godoc-command "go doc" "The command to invoke `go doc' with." :group 'company-go :type 'string) (defcustom company-go-godoc-args "-u" "Arguments to pass to `go doc'." :group 'company-go :type 'string) (defun company-go--invoke-autocomplete () (let ((code-buffer (current-buffer)) (gocode-args (append company-go-gocode-args (list "-f=csv-with-package" "autocomplete" (or (buffer-file-name) "") (concat "c" (int-to-string (- (point) 1))))))) (with-temp-buffer (let ((temp-buffer (current-buffer))) (with-current-buffer code-buffer (apply #'call-process-region (point-min) (point-max) company-go-gocode-command nil temp-buffer nil gocode-args)) (buffer-string))))) (defun company-go--format-meta (candidate) (let ((class (nth 0 candidate)) (name (nth 1 candidate)) (type (nth 2 candidate))) (setq type (if (string-prefix-p "func" type) (substring type 4 nil) (concat " " type))) (concat class " " name type))) (defun company-go--get-candidates (strings) (mapcar (lambda (str) (let ((candidate (split-string str ",,"))) (propertize (nth 1 candidate) 'meta (company-go--format-meta candidate) 'package (nth 3 candidate)))) strings)) (defun company-go--candidates () (let ((candidates (company-go--get-candidates (split-string (company-go--invoke-autocomplete) "\n" t)))) (if (equal candidates '("PANIC")) (error "GOCODE PANIC: Please check your code by \"go build\"") candidates))) (defun company-go--location (arg) (when (require 'go-mode nil t) (company-go--location-1 arg))) (defun company-go--location-1 (arg) (let* ((temp (make-temp-file (directory-file-name (expand-file-name "company-go--location")))) (buffer (current-buffer)) (prefix-len (length company-prefix)) (point (point)) (temp-buffer (find-file-noselect temp))) (unwind-protect (progn (with-current-buffer temp-buffer (insert-buffer-substring buffer) (goto-char point) (insert (substring arg prefix-len)) (goto-char point) (company-go--godef-jump point))) (ignore-errors (with-current-buffer temp-buffer (set-buffer-modified-p nil)) (kill-buffer temp-buffer) (delete-file temp))))) (defun company-go--prefix () "Returns the symbol to complete. Also, if point is on a dot, triggers a completion immediately." (if company-go-begin-after-member-access (company-grab-symbol-cons "\\." 1) (company-grab-symbol))) (defun company-go--godef-jump (point) (condition-case nil (let ((file (car (godef--call point)))) (cond ((string= "-" file) (message "company-go: expression is not defined anywhere") nil) ((string= "company-go: no identifier found" file) (message "%s" file) nil) ((go--string-prefix-p "godef: no declaration found for " file) (message "%s" file) nil) ((go--string-prefix-p "error finding import path for " file) (message "%s" file) nil) (t (if (not (string-match "\\(.+\\):\\([0-9]+\\):\\([0-9]+\\)" file)) (cons (find-file-noselect file) 0) (let ((filename (match-string 1 file)) (line (string-to-number (match-string 2 file))) (column (string-to-number (match-string 3 file)))) (with-current-buffer (find-file-noselect filename) (go--goto-line line) (beginning-of-line) (forward-char (1- column)) (cons (current-buffer) (point)))))))) (file-error (message "company-go: Could not run godef binary") nil))) (defun company-go--insert-arguments (meta) "Insert arguments when META is a function or a method." (when (string-match "^func\\s *[^(]+\\(.*\\)" meta) (let ((args (company-go--extract-arguments (match-string 1 meta)))) (insert args) (company-template-c-like-templatify args)))) (defun company-go--extract-arguments (str) "Extract arguments with parentheses from STR." (let ((len (length str)) (pos 1) (pirs-paren 1)) (while (and (/= pirs-paren 0) (< pos len)) (let ((c (substring-no-properties str pos (1+ pos)))) (cond ((string= c "(") (setq pirs-paren (1+ pirs-paren))) ((string= c ")") (setq pirs-paren (1- pirs-paren)))) (setq pos (1+ pos)))) (substring-no-properties str 0 pos))) ; Uses meta as-is if annotation alignment is enabled. Otherwise removes first ; two words from the meta, which are usually the class and the name of the ; entity, the rest is the function signature or type. That's how annotations are ; supposed to be used. (defun company-go--extract-annotation (meta) "Extract annotation from META." (if company-tooltip-align-annotations meta (save-match-data (and (string-match "\\w+ \\w+\\(.+\\)" meta) (match-string 1 meta))))) (defun company-go--in-num-literal-p () "Returns t if point is in a numeric literal." (let ((word (company-grab-word))) (when word (string-match-p "^0x\\|^[0-9]+" word)))) (defun company-go--syntax-highlight (str) "Apply syntax highlighting to STR." ;; If the user has disabled font-lock, respect that. (if global-font-lock-mode (with-temp-buffer (insert str) (delay-mode-hooks (go-mode)) (if (fboundp 'font-lock-ensure) (font-lock-ensure) (with-no-warnings (font-lock-fontify-buffer))) (buffer-string)) str)) (defun company-go--godoc-as-buffer (arg) "Return Go documentation for QUERY as a buffer." (unless (string= arg "") (let* ((package (get-text-property 0 'package arg)) (query (if (string= package "") arg (format "%s.%s" package arg))) (buf (godoc--get-buffer query)) (exit-code (call-process-shell-command (concat company-go-godoc-command " " company-go-godoc-args " " query) nil buf nil))) (if (zerop exit-code) buf (kill-buffer buf) nil)))) ;;;###autoload (defun company-go (command &optional arg &rest ignored) (interactive (list 'interactive)) (case command (interactive (company-begin-backend 'company-go)) (prefix (and (derived-mode-p 'go-mode) (not (company-in-string-or-comment)) (not (company-go--in-num-literal-p)) (or (company-go--prefix) 'stop))) (candidates (company-go--candidates)) (meta (company-go--syntax-highlight (get-text-property 0 'meta arg))) (annotation (when company-go-show-annotation (company-go--extract-annotation (get-text-property 0 'meta arg)))) (location (company-go--location arg)) (doc-buffer (company-go--godoc-as-buffer arg)) (sorted t) (post-completion (when (and company-go-insert-arguments (not (char-equal ?\( (following-char)))) (company-go--insert-arguments (get-text-property 0 'meta arg)))))) (provide 'company-go) ;;; company-go.el ends here