You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
|
|
;;; company-meghanada.el --- Company support for meganada -*- coding: utf-8; lexical-binding: t; -*-
;; Copyright (C) 2017 - 2020 Yutaka Matsubara;; License: http://www.gnu.org/licenses/gpl.html
;; This program is free software; you can redistribute it and/or modify;; it under the terms of the GNU General Public License as published by;; the Free Software Foundation, either version 3 of the License, or;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,;; but WITHOUT ANY WARRANTY; without even the implied warranty of;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:;;;; The `company-meghanada' is a `company' backend that;; will serve completion candidates asynchronously.;;
;;; Code:
(eval-when-compile (require 'cl-lib) (require 'pcase))
(require 'company)(require 'company-template)(require 'thingatpt)(require 'meghanada)(require 'yasnippet)
(defgroup company-meghanada nil "Company-mode completion backend for Meghanada." :group 'meghanada)
(defcustom company-meghanada-show-annotation t "Show an annotation inline with the candidate." :group 'company-meghanada :type 'boolean)
(defcustom company-meghanada-auto-import t "Add new package class autoimport." :group 'company-meghanada :type 'boolean)
(defcustom company-meghanada-prefix-length nil "Start completion prefix-length." :group 'company-meghanada :type 'integer)
(defcustom company-meghanada-insert-args nil "Insert method argument information when you select a completion candidate.
If t is set, it will be inserted. The default is nil."
:group 'company-meghanada :type 'boolean)
(defconst company-meghanada--trigger "^package \\|^import \\w\\{%d,\\}\\|new \\w\\{%d,\\}\\|@\\w\\{%d,\\}\\|(.*)\\.\\w*\\|[A-Za-z0-9]+\\.\\w*\\|\\.\\w*")
(defvar company-meghanada-trigger-regex nil)
;;;###autoload(defun meghanada-company-enable () "Enable auto completion with company." (company-mode t) (if company-meghanada-prefix-length (set (make-local-variable 'company-minimum-prefix-length) company-meghanada-prefix-length) (set (make-local-variable 'company-meghanada-prefix-length) company-minimum-prefix-length)) (setq company-meghanada-trigger-regex (format company-meghanada--trigger company-meghanada-prefix-length company-meghanada-prefix-length company-meghanada-prefix-length company-meghanada-prefix-length)) (set (make-local-variable 'company-backends) '((company-meghanada :separate company-dabbrev-code))) (yas-minor-mode t) (make-local-variable 'yas-minor-mode-map) (advice-add #' company--insert-candidate :override #'meghanada--insert-candidate))
(defun make-icon-hash (type) (let ((kind-val (pcase type ("VARIABLE" 6) ("METHOD" 2) ("CONSTRUCTOR" 2) ("FIELD" 5) ("CLASS" 22) ("IMPORT" 10) ("PACKAGE" 10))) (ht (make-hash-table :test 'equal))) (puthash "kind" kind-val ht) ht))
(defun company-meghanada--to-candidate (result) (mapcar (lambda (candidate) (propertize (nth 1 candidate) 'desc (nth 2 candidate) 'meta (nth 3 candidate) 'type (nth 0 candidate) 'return-type (nth 4 candidate) 'extra (nth 5 candidate) 'lsp-completion-item (make-icon-hash (nth 0 candidate)))) result))
(defun company-meghanada--to-candidates (output) (when (> (length output) 0) (company-meghanada--to-candidate output)))
(defun company-meghanada--autocomplete-callback (output &rest args) (let ((callback (car args))) (funcall callback (company-meghanada--to-candidates output))))
(defvar meghanada--sp-prefix nil)
(defun company-meghanada--candidates (prefix) (let ((prefix (if meghanada--sp-prefix meghanada--sp-prefix prefix))) (when prefix (cons :async #'(lambda (callback) (meghanada-autocomplete-prefix-async prefix (list #'company-meghanada--autocomplete-callback callback)))))))
(defun meghanada--search-method-caller () (save-excursion (backward-list) (forward-char -1) (get-text-property (point) 'return-type)))
(defun meghanada--search-access-caller () (save-excursion (search-backward "." nil t) (backward-word) (if (= (meghanada--what-word) "this") "this" (get-text-property (point) 'return-type))))
(defun meghanada--last-is-paren () (save-excursion (search-backward "." nil t) (forward-char -1) (while (or (string= (char-to-string (char-after)) " ") (string= (char-to-string (char-after)) "\n")) (forward-char -1)) (string= (char-to-string (char-after)) ")")))
(defun meghanada--last-statement-position () ;; Get position of nearest ";" and "{" character before current line, And ;; assume this is the last statement position (max (save-excursion (or (search-backward ";" nil t) 0) (save-excursion (or (search-backward "{" nil t) 0)))))
(defun meghanada--last-is-assignment (lap) ;; Whether is in an assignment statement, if in assignment statement, variable ;; type or name need to be send to backend to do smart completion (save-excursion (search-backward "=" lap t) 0))
(defun meghanada--variable-type-or-name (lap) ;; Get assignment statement variable type or variable name, then send it to ;; backend for smart completion (save-excursion (search-backward "=" lap t) (backward-word 2) (if (< (point) lap) (forward-word 2)) (meghanada--what-word)))
(defun meghanada--grab-symbol-cons () (let ((symbol (company-grab-symbol)) (re company-meghanada-trigger-regex))
(setq meghanada--sp-prefix nil) (when symbol (save-excursion (if (looking-back re (line-beginning-position) t) (let* ((match (match-string 0)) (keyword (cond ((string-prefix-p "package" match) "*package")
((string-prefix-p "import " match) (concat "*" (replace-regexp-in-string " " ":" match)))
((string-prefix-p "new" match) (concat "*" (replace-regexp-in-string " " ":" match)))
((string-match "\)\\.\\(\\w*\\)$" match) (let ((prefix (match-string 1 match)) (rt (meghanada--search-method-caller))) (if rt (concat "*method:" rt "#" prefix) (concat "*method#" prefix))))
((string-match "\\.\\(\\w*\\)$" match) (let* ((prefix (match-string 1 match)) (paren (meghanada--last-is-paren)) (lap (meghanada--last-statement-position)) (assign (meghanada--last-is-assignment lap)) (vt (if assign (ignore-errors (meghanada--variable-type-or-name lap)))) (rt (if paren (ignore-errors (meghanada--search-method-caller)) (ignore-errors (meghanada--search-access-caller)))) (sym (if paren (save-excursion (backward-list) (forward-char -1) (meghanada--what-word)) (save-excursion (search-backward "." nil t) (backward-word) (meghanada--what-word))))) (if assign (if rt (concat "*method:" rt "*" vt "#" prefix) (concat "*" sym "*" vt "#" prefix)) (if rt (concat "*method:" rt "#" prefix) (concat "*" sym "#" prefix)) )))
((string-match "\\(.*\\)\\.\\(\\w*\\)$" match) (let* ((var (match-string 1 match)) (prefix (match-string 2 match))) (concat "*" var "#" prefix))) (t match))))
(setq meghanada--sp-prefix keyword) (cons symbol t)) symbol)))))
;; (defun company-meghanada--prefix ();; (company-grab-symbol-cons "\\(package \\)\\|\\(news \\)\\|[A-Za-z0-9)]+\\."))
(defun company-meghanada--prefix () (meghanada--grab-symbol-cons))
(defun company-meghanada--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-meghanada--annotation (arg) (let ((meta (get-text-property 0 'meta arg))) (when (string-match "\\(([^-]*\\)" meta) (substring meta (match-beginning 1) (match-end 1)))))
(defun company-meghanada--typep-annotation (arg) (let ((desc (get-text-property 0 'desc arg))) (when (string-match "\\(<[^-]*\\)" desc) (substring desc (match-beginning 1) (match-end 1)))))
(defun company-meghanada--post-class (arg) (let ((meta (get-text-property 0 'meta arg)) (anno (company-meghanada--typep-annotation arg)) (return-t (get-text-property 0 'return-type arg))) (unless (meghanada--import-exists-p meta) (if company-meghanada-auto-import (meghanada--add-import meta (current-buffer)) (when (y-or-n-p (format "Add import %s ? " (meghanada--import-name meta))) (meghanada--add-import meta (current-buffer)))))
(save-excursion (forward-char -1) (set-text-properties (beginning-of-thing 'symbol) (end-of-thing 'symbol) (list 'class t 'return-type return-t 'meta meta 'type 'class)))
(if (and meghanada--sp-prefix (or (string-prefix-p "*new" meghanada--sp-prefix) (string-prefix-p "@" meghanada--sp-prefix))) (if anno ;; complete diamond op. like a new HashMap<>() (progn (insert "<>()") (backward-char 3)) (progn (insert "()") (backward-char 1))) (when anno (insert anno) (when company-meghanada-insert-args (company-template-c-like-templatify anno))))))
(defun company-meghanada--post-method (arg) (let* ((meta (get-text-property 0 'meta arg)) (desc (get-text-property 0 'desc arg)) (anno (company-meghanada--annotation arg)) (return-t (get-text-property 0 'return-type arg)) (extra (split-string (get-text-property 0 'extra arg)))) (when return-t (save-excursion (forward-char -1) (set-text-properties (beginning-of-thing 'symbol) (end-of-thing 'symbol) (list 'return-type return-t 'meta meta 'type 'method)))) (when anno (when company-meghanada-insert-args (company-template-c-like-templatify anno)) (when (and (> (length extra) 1) (string= "static-import" (car extra))) (let* ((class (nth 1 extra)) (imp (format "%s#%s" class arg))) (if company-meghanada-auto-import (meghanada--add-import imp (current-buffer)) (when (y-or-n-p (format "Add import %s ? " (meghanada--import-name class))) (meghanada--add-import imp (current-buffer)))))))))
(defun company-meghanada--post-field (arg) (let ((meta (get-text-property 0 'meta arg)) (anno (company-meghanada--annotation arg)) (return-t (get-text-property 0 'return-type arg)) (extra (split-string (get-text-property 0 'extra arg)))) (when return-t (save-excursion (forward-char -1) (set-text-properties (beginning-of-thing 'symbol) (end-of-thing 'symbol) (list 'return-type return-t 'meta meta 'type 'field))) (when (and (> (length extra) 1) (string= "static-import" (car extra))) (let* ((class (nth 1 extra)) (imp (format "%s#%s" class arg))) (if company-meghanada-auto-import (meghanada--add-import imp (current-buffer)) (when (y-or-n-p (format "Add import %s ? " (meghanada--import-name class))) (meghanada--add-import imp (current-buffer)))))))))
(defun company-meghanada--post-var (arg) (let ((meta (get-text-property 0 'meta arg)) (anno (company-meghanada--annotation arg)) (return-t (get-text-property 0 'return-type arg))) (when return-t (save-excursion (forward-char -1) (set-text-properties (beginning-of-thing 'symbol) (end-of-thing 'symbol) (list 'return-type return-t 'meta meta 'type 'var))))))
(defun company-meghanada--post-completion (arg) (let ((type (intern (get-text-property 0 'type arg))) (meta (get-text-property 0 'meta arg)) (desc (get-text-property 0 'desc arg)) (anno (company-meghanada--annotation arg))) (meghanada-autocomplete-resolve-async type arg desc #'identity) (pcase type ;; completion class (`CLASS (company-meghanada--post-class arg)) ;; completion field (`FIELD (company-meghanada--post-field arg)) ;; completion method (`METHOD (company-meghanada--post-method arg)) ;; completion var (`VAR (company-meghanada--post-var arg)) ;; completion const (`CONSTRUCTOR (progn (insert "()") (backward-char 1))) ;; completion const (`IMPORT (progn (backward-word) (insert meta) (insert ";") (delete-region (point) (+ (point) (length arg))))))))
(defun substring--candidate (candidate) (if (and (meghanada-alive-p) (not company-meghanada-insert-args)) (if (string-match "\\(([^-]*\\)" candidate) (substring candidate 0 (match-beginning 1)) candidate) candidate))
(defun meghanada--insert-candidate (candidate) (when (> (length candidate) 0) (setq candidate (substring-no-properties candidate)) (if (eq (company-call-backend 'ignore-case) 'keep-prefix) (insert (substring--candidate (company-strip-prefix candidate))) (unless (equal company-prefix candidate) (delete-region (- (point) (length company-prefix)) (point)) (insert (substring--candidate candidate))))))
(defun company-meghanada (command &optional arg &rest ignored) (cl-case command (prefix (and (meghanada-alive-p) (not (company-in-string-or-comment)) (not (company-meghanada--in-num-literal-p)) (or (company-meghanada--prefix) 'stop))) (candidates (company-meghanada--candidates arg)) (meta (get-text-property 0 'meta arg)) (annotation (when company-meghanada-show-annotation (concat " " (get-text-property 0 'desc arg)))) (ignore-case t) (sorted t) (no-cache (unless (and (string= "prefix" meghanada-completion-matcher) (string= "prefix" meghanada-class-completion-matcher)) t)) (require-match 'never) (post-completion (company-meghanada--post-completion arg))))
(provide 'company-meghanada)
;;; company-meghanada.el ends here
|