-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathlsp-javacomp.el
209 lines (173 loc) · 7.7 KB
/
lsp-javacomp.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
;;; lsp-javacomp.el --- Provide Java IDE features powered by JavaComp. -*- lexical-binding: t -*-
;; Version: 1.0
;; Package-Requires: ((emacs "25.1") (lsp-mode "3.0") (s "1.2.0"))
;; Keywords: java tools lsp
;; URL: https://github.com/tigersoldier/lsp-javacomp
;; 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:
;; A lsp-mode client that provides Java code-completion and other IDE
;; features for Emacs. It's backed by JavaComp.
;; To use it, add the code below to your .emacs file:
;; (require 'lsp-javacomp)
;; (add-hook 'java-mode-hook #'lsp-javacomp-enable)
;;; Code:
(require 'cc-mode)
(require 'lsp-mode)
(require 'json)
(require 's)
(defalias 'lsp-javacomp-enable 'lsp)
(defcustom lsp-javacomp-server-install-dir (locate-user-emacs-file "javacomp/")
"Install directory for JavaComp server.
Requires to be ended with a slash."
:group 'lsp-javacomp
:risky t
:type 'directory)
(defcustom lsp-javacomp-java-executable "java"
"Name or path of the java executable binary file."
:type '(choice (const nil) string)
:group 'lsp-javacomp)
(defcustom lsp-javacomp-java-options nil
"List of command line options to be pased to java command."
:type '(set string)
:group 'lsp-javacomp)
(defcustom lsp-javacomp-server-log-level nil
"Server log level.
This option sets the value of the logLevel initialization option.
Possible values are \"severe\", \"warning\", \"info\", \"fine\",
\"finer\", \"finest\". If it's set to nil, the option is not sent
to the server."
:type '(choice
(const nil)
(const "severe")
(const "warning")
(const "info")
(const "fine")
(const "finer")
(const "finest"))
:group 'lsp-javacomp)
(defcustom lsp-javacomp-server-log-path nil
"Server log path.
This option sets the value of the logPath initialization option."
:type '(choice (const nil) string)
:group 'lsp-javacomp)
(defcustom lsp-javacomp-server-ignore-paths nil
"A list of string paths to be ignored by the server.
This option sets the value of the ignorePaths initialization option."
:type '(repeat string)
:group 'lsp-javacomp)
(defcustom lsp-javacomp-server-type-index-files nil
"A list of string paths of the type index files.
This option sets the value of the typeIndexFiles initialization option."
:type '(repeat string)
:group 'lsp-javacomp)
(defconst lsp-javacomp-latest-release-url
"https://api.github.com/repos/tigersoldier/JavaComp/releases/latest"
"URL to retrieve the latest release of JavaComp server.")
(defun lsp-javacomp--server-jar-path ()
"Return the path to the JavaComp server JAR file."
(expand-file-name "javacomp.jar" lsp-javacomp-server-install-dir))
(defun lsp-javacomp--command ()
"Return a list of the command and arguments to launch the JavaComp server."
`( ,lsp-javacomp-java-executable
,@lsp-javacomp-java-options
"-jar"
,(lsp-javacomp--server-jar-path)))
(defun lsp-javacomp--suggest-root ()
"Retrieves the root directory of the java project root based on some well-known project files."
(when (memq major-mode '(java-mode))
(let ((project-files '("javacomp.json" "pom.xml" "build.gradle" ".project" "WORKSPACE")))
(when-let ((root (seq-some (lambda (file)
(locate-dominating-file default-directory file))
project-files)))
(expand-file-name root)))))
(defun lsp-javacomp--get-prefix ()
"Get prefix for completion.
Return a cons of (start . end) for the bound of the prefix."
(let* ((bound (bounds-of-thing-at-point 'symbol))
(start (or (and bound (car bound)) (point)))
(end (or (and bound (cdr bound)) (point))))
;; java-mode considers '@' as a symbol constituent. However JavaComp doesn't
;; take the leading '@' as part of the prefix. Remove the leading '@' from
;; the prefix.
(when (and (< start end) (char-equal (char-after start) ?@))
(setq start (1+ start)))
(cons start end)))
(defun lsp-javacomp--get-init-params ()
"Return initialization options."
(list :logPath lsp-javacomp-server-log-path
:logLevel lsp-javacomp-server-log-level
:ignorePths lsp-javacomp-server-ignore-paths
:typeIndexFiles lsp-javacomp-server-type-index-files))
;;;###autoload
(defun lsp-javacomp-install-server (&optional prompt-exists)
"Download the JavaComp server JAR file if it does not exist.
If PROMPT-EXISTS is non-nil, show a message if the server jar
file already exists."
(interactive '(t))
(let ((jar-file (lsp-javacomp--server-jar-path)))
(if (file-exists-p jar-file)
(and prompt-exists (message "JavaComp server already exists."))
(lsp-javacomp--download-server))))
;;;###autoload
(defun lsp-javacomp-update-server ()
"Update JavaComp jar file to the latest version."
(interactive)
(lsp-javacomp--download-server))
(defun lsp-javacomp--download-server ()
"Download latest JavaComp jar file."
(message "Getting the latest JavaComp server...")
(url-retrieve lsp-javacomp-latest-release-url #'lsp-javacomp--latest-release-callback))
(defun lsp-javacomp--latest-release-callback (stats)
"Handle the `url-retrive' callback for JavaComp latest release request.
STATS is passed by `url-retrieve'.
See https://developer.github.com/v3/repos/releases/#get-the-latest-release"
(search-forward "\n\n")
(if-let (err (plist-get stats :error))
(error "Failed to get the latest release of JavaComp server: %s" (car err))
(let* ((release (json-read))
(assets (alist-get 'assets release))
(jar-asset (seq-find (lambda (asset)
(s-match "^javacomp.*\\.jar$" (alist-get 'name asset)))
assets))
(jar-url (alist-get 'browser_download_url jar-asset))
(release-version (alist-get 'tag_name release)))
(if jar-url
(progn
(message "Found JavaComp %s, downloading..." release-version)
(make-directory (expand-file-name lsp-javacomp-server-install-dir) t)
(url-copy-file jar-url (lsp-javacomp--server-jar-path) t))
(error "Fail to get the URL of the JavaComp server")))))
(defun lsp-javacomp-document-content ()
(interactive)
(let* ((document-content (lsp-send-request (lsp-make-request
"$JavaComp/documentContent"
`(:uri ,(lsp--buffer-uri)))))
(snapshot-content (gethash "snapshotContent" document-content))
(buffer (get-buffer-create "*JavaComp document content*")))
(with-current-buffer buffer
(erase-buffer)
(insert snapshot-content)
(java-mode))
(display-buffer-use-some-window buffer nil)))
(advice-add 'lsp--suggest-project-root :before-until #'lsp-javacomp--suggest-root)
(lsp-register-client
(make-lsp-client
:new-connection (lsp-stdio-connection #'lsp-javacomp--command)
:major-modes '(java-mode)
:server-id 'javacomp
:multi-root nil
:initialization-options #'lsp-javacomp--get-init-params
:ignore-regexps '("^SLF4J: "
"^Listening for transport dt_socket at address: ")
:library-folders-fn nil))
(provide 'lsp-javacomp)
;;; lsp-javacomp.el ends here