Personal emacs config
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.

419 lines
16 KiB

  1. ;;; company-meghanada.el --- Company support for meganada -*- coding: utf-8; lexical-binding: t; -*-
  2. ;; Copyright (C) 2017 - 2020 Yutaka Matsubara
  3. ;; License: http://www.gnu.org/licenses/gpl.html
  4. ;; This program is free software; you can redistribute it and/or modify
  5. ;; it under the terms of the GNU General Public License as published by
  6. ;; the Free Software Foundation, either version 3 of the License, or
  7. ;; (at your option) any later version.
  8. ;; This program is distributed in the hope that it will be useful,
  9. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. ;; GNU General Public License for more details.
  12. ;; You should have received a copy of the GNU General Public License
  13. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. ;;; Commentary:
  15. ;;
  16. ;; The `company-meghanada' is a `company' backend that
  17. ;; will serve completion candidates asynchronously.
  18. ;;
  19. ;;; Code:
  20. (eval-when-compile
  21. (require 'cl-lib)
  22. (require 'pcase))
  23. (require 'company)
  24. (require 'company-template)
  25. (require 'thingatpt)
  26. (require 'meghanada)
  27. (require 'yasnippet)
  28. (defgroup company-meghanada nil
  29. "Company-mode completion backend for Meghanada."
  30. :group 'meghanada)
  31. (defcustom company-meghanada-show-annotation t
  32. "Show an annotation inline with the candidate."
  33. :group 'company-meghanada
  34. :type 'boolean)
  35. (defcustom company-meghanada-auto-import t
  36. "Add new package class autoimport."
  37. :group 'company-meghanada
  38. :type 'boolean)
  39. (defcustom company-meghanada-prefix-length nil
  40. "Start completion prefix-length."
  41. :group 'company-meghanada
  42. :type 'integer)
  43. (defcustom company-meghanada-insert-args nil
  44. "Insert method argument information when you select a completion candidate.
  45. If t is set, it will be inserted. The default is nil."
  46. :group 'company-meghanada
  47. :type 'boolean)
  48. (defconst company-meghanada--trigger "^package \\|^import \\w\\{%d,\\}\\|new \\w\\{%d,\\}\\|@\\w\\{%d,\\}\\|(.*)\\.\\w*\\|[A-Za-z0-9]+\\.\\w*\\|\\.\\w*")
  49. (defvar company-meghanada-trigger-regex nil)
  50. ;;;###autoload
  51. (defun meghanada-company-enable ()
  52. "Enable auto completion with company."
  53. (company-mode t)
  54. (if company-meghanada-prefix-length
  55. (set (make-local-variable 'company-minimum-prefix-length) company-meghanada-prefix-length)
  56. (set (make-local-variable 'company-meghanada-prefix-length) company-minimum-prefix-length))
  57. (setq company-meghanada-trigger-regex (format company-meghanada--trigger
  58. company-meghanada-prefix-length
  59. company-meghanada-prefix-length
  60. company-meghanada-prefix-length
  61. company-meghanada-prefix-length))
  62. (set (make-local-variable 'company-backends) '((company-meghanada :separate company-dabbrev-code)))
  63. (yas-minor-mode t)
  64. (make-local-variable 'yas-minor-mode-map)
  65. (advice-add #' company--insert-candidate :override #'meghanada--insert-candidate))
  66. (defun make-icon-hash (type)
  67. (let ((kind-val (pcase type
  68. ("VARIABLE" 6)
  69. ("METHOD" 2)
  70. ("CONSTRUCTOR" 2)
  71. ("FIELD" 5)
  72. ("CLASS" 22)
  73. ("IMPORT" 10)
  74. ("PACKAGE" 10)))
  75. (ht (make-hash-table :test 'equal)))
  76. (puthash "kind" kind-val ht)
  77. ht))
  78. (defun company-meghanada--to-candidate (result)
  79. (mapcar (lambda (candidate)
  80. (propertize (nth 1 candidate)
  81. 'desc
  82. (nth 2 candidate)
  83. 'meta
  84. (nth 3 candidate)
  85. 'type
  86. (nth 0 candidate)
  87. 'return-type
  88. (nth 4 candidate)
  89. 'extra
  90. (nth 5 candidate)
  91. 'lsp-completion-item
  92. (make-icon-hash (nth 0 candidate)))) result))
  93. (defun company-meghanada--to-candidates (output)
  94. (when (> (length output) 0)
  95. (company-meghanada--to-candidate output)))
  96. (defun company-meghanada--autocomplete-callback (output &rest args)
  97. (let ((callback (car args)))
  98. (funcall callback (company-meghanada--to-candidates output))))
  99. (defvar meghanada--sp-prefix nil)
  100. (defun company-meghanada--candidates (prefix)
  101. (let ((prefix (if meghanada--sp-prefix
  102. meghanada--sp-prefix
  103. prefix)))
  104. (when prefix
  105. (cons :async
  106. #'(lambda (callback)
  107. (meghanada-autocomplete-prefix-async
  108. prefix
  109. (list #'company-meghanada--autocomplete-callback callback)))))))
  110. (defun meghanada--search-method-caller ()
  111. (save-excursion
  112. (backward-list)
  113. (forward-char -1)
  114. (get-text-property (point) 'return-type)))
  115. (defun meghanada--search-access-caller ()
  116. (save-excursion
  117. (search-backward "." nil t)
  118. (backward-word)
  119. (if (= (meghanada--what-word) "this")
  120. "this"
  121. (get-text-property (point) 'return-type))))
  122. (defun meghanada--last-is-paren ()
  123. (save-excursion
  124. (search-backward "." nil t)
  125. (forward-char -1)
  126. (while (or
  127. (string= (char-to-string (char-after)) " ")
  128. (string= (char-to-string (char-after)) "\n"))
  129. (forward-char -1))
  130. (string= (char-to-string (char-after)) ")")))
  131. (defun meghanada--last-statement-position ()
  132. ;; Get position of nearest ";" and "{" character before current line, And
  133. ;; assume this is the last statement position
  134. (max (save-excursion (or (search-backward ";" nil t) 0)
  135. (save-excursion (or (search-backward "{" nil t) 0)))))
  136. (defun meghanada--last-is-assignment (lap)
  137. ;; Whether is in an assignment statement, if in assignment statement, variable
  138. ;; type or name need to be send to backend to do smart completion
  139. (save-excursion
  140. (search-backward "=" lap t) 0))
  141. (defun meghanada--variable-type-or-name (lap)
  142. ;; Get assignment statement variable type or variable name, then send it to
  143. ;; backend for smart completion
  144. (save-excursion
  145. (search-backward "=" lap t) (backward-word 2)
  146. (if (< (point) lap)
  147. (forward-word 2))
  148. (meghanada--what-word)))
  149. (defun meghanada--grab-symbol-cons ()
  150. (let ((symbol (company-grab-symbol))
  151. (re company-meghanada-trigger-regex))
  152. (setq meghanada--sp-prefix nil)
  153. (when symbol
  154. (save-excursion
  155. (if (looking-back re (line-beginning-position) t)
  156. (let* ((match (match-string 0))
  157. (keyword
  158. (cond
  159. ((string-prefix-p "package" match) "*package")
  160. ((string-prefix-p "import " match)
  161. (concat "*" (replace-regexp-in-string " " ":" match)))
  162. ((string-prefix-p "new" match)
  163. (concat "*" (replace-regexp-in-string " " ":" match)))
  164. ((string-match "\)\\.\\(\\w*\\)$" match)
  165. (let ((prefix (match-string 1 match))
  166. (rt (meghanada--search-method-caller)))
  167. (if rt
  168. (concat "*method:" rt "#" prefix)
  169. (concat "*method#" prefix))))
  170. ((string-match "\\.\\(\\w*\\)$" match)
  171. (let* ((prefix (match-string 1 match))
  172. (paren (meghanada--last-is-paren))
  173. (lap (meghanada--last-statement-position))
  174. (assign (meghanada--last-is-assignment lap))
  175. (vt (if assign
  176. (ignore-errors (meghanada--variable-type-or-name lap))))
  177. (rt (if paren
  178. (ignore-errors (meghanada--search-method-caller))
  179. (ignore-errors (meghanada--search-access-caller))))
  180. (sym (if paren
  181. (save-excursion
  182. (backward-list)
  183. (forward-char -1)
  184. (meghanada--what-word))
  185. (save-excursion
  186. (search-backward "." nil t)
  187. (backward-word)
  188. (meghanada--what-word)))))
  189. (if assign
  190. (if rt (concat "*method:" rt "*" vt "#" prefix)
  191. (concat "*" sym "*" vt "#" prefix))
  192. (if rt (concat "*method:" rt "#" prefix)
  193. (concat "*" sym "#" prefix))
  194. )))
  195. ((string-match "\\(.*\\)\\.\\(\\w*\\)$" match)
  196. (let* ((var (match-string 1 match))
  197. (prefix (match-string 2 match)))
  198. (concat "*" var "#" prefix)))
  199. (t match))))
  200. (setq meghanada--sp-prefix keyword)
  201. (cons symbol t))
  202. symbol)))))
  203. ;; (defun company-meghanada--prefix ()
  204. ;; (company-grab-symbol-cons "\\(package \\)\\|\\(news \\)\\|[A-Za-z0-9)]+\\."))
  205. (defun company-meghanada--prefix ()
  206. (meghanada--grab-symbol-cons))
  207. (defun company-meghanada--in-num-literal-p ()
  208. "Returns t if point is in a numeric literal."
  209. (let ((word (company-grab-word)))
  210. (when word
  211. (string-match-p "^0x\\|^[0-9]+" word))))
  212. (defun company-meghanada--annotation (arg)
  213. (let ((meta (get-text-property 0 'meta arg)))
  214. (when (string-match "\\(([^-]*\\)" meta)
  215. (substring meta (match-beginning 1) (match-end 1)))))
  216. (defun company-meghanada--typep-annotation (arg)
  217. (let ((desc (get-text-property 0 'desc arg)))
  218. (when (string-match "\\(<[^-]*\\)" desc)
  219. (substring desc (match-beginning 1) (match-end 1)))))
  220. (defun company-meghanada--post-class (arg)
  221. (let ((meta (get-text-property 0 'meta arg))
  222. (anno (company-meghanada--typep-annotation arg))
  223. (return-t (get-text-property 0 'return-type arg)))
  224. (unless (meghanada--import-exists-p meta)
  225. (if company-meghanada-auto-import
  226. (meghanada--add-import meta (current-buffer))
  227. (when (y-or-n-p (format "Add import %s ? " (meghanada--import-name meta)))
  228. (meghanada--add-import meta (current-buffer)))))
  229. (save-excursion
  230. (forward-char -1)
  231. (set-text-properties
  232. (beginning-of-thing 'symbol)
  233. (end-of-thing 'symbol)
  234. (list 'class t 'return-type return-t 'meta meta 'type 'class)))
  235. (if (and meghanada--sp-prefix
  236. (or (string-prefix-p "*new" meghanada--sp-prefix)
  237. (string-prefix-p "@" meghanada--sp-prefix)))
  238. (if anno
  239. ;; complete diamond op. like a new HashMap<>()
  240. (progn
  241. (insert "<>()")
  242. (backward-char 3))
  243. (progn
  244. (insert "()")
  245. (backward-char 1)))
  246. (when anno
  247. (insert anno)
  248. (when company-meghanada-insert-args
  249. (company-template-c-like-templatify anno))))))
  250. (defun company-meghanada--post-method (arg)
  251. (let* ((meta (get-text-property 0 'meta arg))
  252. (desc (get-text-property 0 'desc arg))
  253. (anno (company-meghanada--annotation arg))
  254. (return-t (get-text-property 0 'return-type arg))
  255. (extra (split-string (get-text-property 0 'extra arg))))
  256. (when return-t
  257. (save-excursion
  258. (forward-char -1)
  259. (set-text-properties
  260. (beginning-of-thing 'symbol)
  261. (end-of-thing 'symbol)
  262. (list 'return-type return-t 'meta meta 'type 'method))))
  263. (when anno
  264. (when company-meghanada-insert-args
  265. (company-template-c-like-templatify anno))
  266. (when (and
  267. (> (length extra) 1)
  268. (string= "static-import" (car extra)))
  269. (let* ((class (nth 1 extra))
  270. (imp (format "%s#%s" class arg)))
  271. (if company-meghanada-auto-import
  272. (meghanada--add-import imp (current-buffer))
  273. (when (y-or-n-p
  274. (format "Add import %s ? " (meghanada--import-name class)))
  275. (meghanada--add-import imp (current-buffer)))))))))
  276. (defun company-meghanada--post-field (arg)
  277. (let ((meta (get-text-property 0 'meta arg))
  278. (anno (company-meghanada--annotation arg))
  279. (return-t (get-text-property 0 'return-type arg))
  280. (extra (split-string (get-text-property 0 'extra arg))))
  281. (when return-t
  282. (save-excursion
  283. (forward-char -1)
  284. (set-text-properties
  285. (beginning-of-thing 'symbol)
  286. (end-of-thing 'symbol)
  287. (list 'return-type return-t 'meta meta 'type 'field)))
  288. (when (and
  289. (> (length extra) 1)
  290. (string= "static-import" (car extra)))
  291. (let* ((class (nth 1 extra))
  292. (imp (format "%s#%s" class arg)))
  293. (if company-meghanada-auto-import
  294. (meghanada--add-import imp (current-buffer))
  295. (when (y-or-n-p
  296. (format "Add import %s ? " (meghanada--import-name class)))
  297. (meghanada--add-import imp (current-buffer)))))))))
  298. (defun company-meghanada--post-var (arg)
  299. (let ((meta (get-text-property 0 'meta arg))
  300. (anno (company-meghanada--annotation arg))
  301. (return-t (get-text-property 0 'return-type arg)))
  302. (when return-t
  303. (save-excursion
  304. (forward-char -1)
  305. (set-text-properties
  306. (beginning-of-thing 'symbol)
  307. (end-of-thing 'symbol)
  308. (list 'return-type return-t 'meta meta 'type 'var))))))
  309. (defun company-meghanada--post-completion (arg)
  310. (let ((type (intern (get-text-property 0 'type arg)))
  311. (meta (get-text-property 0 'meta arg))
  312. (desc (get-text-property 0 'desc arg))
  313. (anno (company-meghanada--annotation arg)))
  314. (meghanada-autocomplete-resolve-async type arg desc #'identity)
  315. (pcase type
  316. ;; completion class
  317. (`CLASS (company-meghanada--post-class arg))
  318. ;; completion field
  319. (`FIELD (company-meghanada--post-field arg))
  320. ;; completion method
  321. (`METHOD (company-meghanada--post-method arg))
  322. ;; completion var
  323. (`VAR (company-meghanada--post-var arg))
  324. ;; completion const
  325. (`CONSTRUCTOR (progn (insert "()") (backward-char 1)))
  326. ;; completion const
  327. (`IMPORT (progn
  328. (backward-word)
  329. (insert meta)
  330. (insert ";")
  331. (delete-region (point) (+ (point) (length arg))))))))
  332. (defun substring--candidate (candidate)
  333. (if (and (meghanada-alive-p) (not company-meghanada-insert-args))
  334. (if (string-match "\\(([^-]*\\)" candidate)
  335. (substring candidate 0 (match-beginning 1))
  336. candidate)
  337. candidate))
  338. (defun meghanada--insert-candidate (candidate)
  339. (when (> (length candidate) 0)
  340. (setq candidate (substring-no-properties candidate))
  341. (if (eq (company-call-backend 'ignore-case) 'keep-prefix)
  342. (insert (substring--candidate (company-strip-prefix candidate)))
  343. (unless (equal company-prefix candidate)
  344. (delete-region (- (point) (length company-prefix)) (point))
  345. (insert (substring--candidate candidate))))))
  346. (defun company-meghanada (command &optional arg &rest ignored)
  347. (cl-case command
  348. (prefix (and (meghanada-alive-p)
  349. (not (company-in-string-or-comment))
  350. (not (company-meghanada--in-num-literal-p))
  351. (or (company-meghanada--prefix) 'stop)))
  352. (candidates (company-meghanada--candidates arg))
  353. (meta (get-text-property 0 'meta arg))
  354. (annotation (when company-meghanada-show-annotation
  355. (concat " " (get-text-property 0 'desc arg))))
  356. (ignore-case t)
  357. (sorted t)
  358. (no-cache
  359. (unless (and (string= "prefix" meghanada-completion-matcher) (string= "prefix" meghanada-class-completion-matcher))
  360. t))
  361. (require-match 'never)
  362. (post-completion
  363. (company-meghanada--post-completion arg))))
  364. (provide 'company-meghanada)
  365. ;;; company-meghanada.el ends here