forked from emacs-eclim/emacs-eclim
-
Notifications
You must be signed in to change notification settings - Fork 0
/
eclimd.el
325 lines (288 loc) · 12.9 KB
/
eclimd.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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
;;; eclimd.el --- Start and stop eclimd from within emacs -*- lexical-binding: t -*-
;;
;; Copyright (C) 2012 Vedat Hallac
;; Authors: Vedat Hallac
;; Version: 1.0
;; Created: 2012/05/11
;; Keywords: java, emacs-eclim
;; This file is NOT part of Emacs.
;;
;; 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 2
;; 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, write to the Free Software
;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
;; MA 02110-1301, USA.
;;
;;; Commentary:
;;
;; Common code used by other files in this package.
;;
;;; Code:
(require 'eclim-common)
(require 'cl-lib)
(require 'dash)
(eval-when-compile (require 'eclim-macros))
(defgroup eclimd nil
"eclimd customizations"
:prefix "eclimd-"
:group 'eclim)
(defcustom eclimd-executable
nil
"The eclimd executable to use.
Set to nil to auto-discover from `eclim-executable' value
\(the default). Set to \"eclimd\" if eclim and eclimd are
in `exec-path'. Otherwise, set to the full path of the
eclimd executable."
:type '(choice (const :tag "Same directory as eclim-executable variable" nil)
(string :tag "Custom value" "eclimd"))
:group 'eclimd)
(defcustom eclimd-default-workspace
"~/workspace"
"The default value to use when `start-eclimd' asks for a workspace."
:type 'directory
:group 'eclimd)
(defcustom eclimd-wait-for-process
nil
"Non-nil means `start-eclimd' blocks until eclimd is ready.
When this variable is nil, `start-eclimd' returns
immediately after the eclimd process is started. Since the
eclimd process startup takes a few seconds, running eclim
commands immediately after the function returns may cause
failures. You can freeze Emacs until eclimd is ready to
accept commands with this variable."
:tag "Wait until eclimd is ready"
:type 'boolean
:group 'eclimd)
(defvar eclimd-process-buffer nil
"Buffer used for communication with the eclimd process.")
(defvar eclimd-process nil
"The active eclimd process.")
(defvar eclimd-port nil
"The port on which eclimd is serving.
This is nil unless the eclimd server is running and ready.")
(defconst eclimd-process-buffer-name "eclimd"
"The name to use for the eclimd process buffer.")
(defun eclimd--executable-path ()
"Return path to the eclimd executable.
This can be set explicitly with `eclimd-executable'. If
that variable is not set, this function will attempt to
discover the actual path."
(if eclimd-executable
(executable-find eclimd-executable)
(let ((eclim-prog (executable-find eclim-executable)))
(expand-file-name "eclimd" (file-name-directory eclim-prog)))))
(defconst eclimd--started-regexp
"Eclim Server Started on\\(?: port\\|:\\) \\(?:\\(?:[0-9]+\\.\\)\\{3\\}[0-9]+:\\)?\\([0-9]+\\)"
"Regular expression to detect when eclimd has finished starting.
The one and only capturing subgroup matches the port number
on which eclimd is serving.")
(defun eclimd--read-workspace-dir ()
"Prompt the user for the workspace directory and return it."
(read-directory-name "Eclimd workspace directory: "
eclimd-default-workspace nil t))
(defcustom eclimd-autostart-with-default-workspace
nil
"Non-nil means do not ask for a workspace when autostarting eclimd.
When `eclimd-autostart' is non-nil, this option controls
whether eclimd is started silently with the workspace set to
`eclimd-default-workspace', or whether the user is asked for
a workspace as with regular calls to `start-eclimd'."
:tag "Autostart eclimd with default workspace"
:type 'boolean
:group 'eclimd)
(defun eclimd--autostart-workspace ()
"Return the workspace to use when autostarting eclimd.
If `eclimd-autostart-with-default-workspace' is nil, the
user is asked to provide the workspace. Otherwise,
`eclimd-default-workspace' is assumed."
(if eclimd-autostart-with-default-workspace
eclimd-default-workspace
(eclimd--read-workspace-dir)))
(defcustom eclimd-autostart
nil
"Non-nil means automatically start eclimd within Emacs when needed.
You may want to set this to nil if you prefer starting
eclimd manually and don't want it to run as a child
process of Emacs. When set, eclimd gets started either when
`eclim-mode' is enabled or the first time
`global-eclim-mode' needs it to determine if `eclim-mode'
should be enabled in a buffer. See also
`eclimd-autostart-with-default-workspace'."
:tag "Autostart eclimd"
:type 'boolean
:group 'eclimd)
(defvar eclimd--process-event-functions nil
"List of functions to run when eclimd outputs text or changes state.
Functions receive the process, the output string and the
process state as argument. Either of the last two may be
nil, but never both. When a function returns nil it is
removed from the list, but functions returning non-nil are
kept.")
(defun eclimd--process-sentinel (proc state)
"The sentinel used to process events from the eclimd buffer.
PROC is the eclimd process and STATE describes the change of
state.
Each of `eclimd--process-event-functions' will be called
with PROC and STATE. The output string will be nil. Any
of these functions which return non-nil will be removed from
the list."
(setq eclimd--process-event-functions
(cl-remove-if-not (lambda (fun) (funcall fun proc nil state))
eclimd--process-event-functions)))
(defvar eclimd--comint-process-filter nil
"The default process filter for the eclimd process buffer.")
(defun eclimd--process-filter (proc string)
"Fitler eclimd process output.
PROC is the eclimd process and STRING is a line of output
from eclimd. STRING is output to the eclimd process buffer
if one exists.
Each of `eclimd--process-event-functions' will be called
with PROC and STRING. The process state will be nil. Any
of these functions which return non-nil will be removed from
the list."
(setq eclimd--process-event-functions
(cl-remove-if-not (lambda (fun) (funcall fun proc string nil))
eclimd--process-event-functions))
;; Appends output to eclimd buffer.
(when eclimd--comint-process-filter
(funcall eclimd--comint-process-filter proc string)))
(defun eclimd--match-process-output (regexp &optional async callback)
"Wait for eclimd to output a string matching REGEXP.
When ASYNC is omitted or nil, block and return the string
used for `string-match' if a match is found, or nil if the
process is killed. Execute CALLBACK when the process
outputs the desired string or terminates and pass the
corresponding return value as argument."
(let* ((output "")
(terminated-p)
(finished-p)
(match-data)
(closure (lambda (proc string _state)
(setf output (concat output string))
(setf terminated-p (not (eq 'run (process-status proc))))
(setf finished-p (or terminated-p
;; Although Emacs already saves the
;; match data when calling process
;; filters/sentinels, one such call may
;; execute multiple closures.
(save-match-data
(-when-let (match-pos (string-match regexp output))
;; Remember the match data so the closure can access it.
(setf match-data (match-data))
match-pos))))
(when (and finished-p callback)
(save-match-data
(set-match-data match-data)
(funcall callback (unless terminated-p output))))
;; Remove the closure from the hook when it has finished.
(not finished-p))))
(if async
(add-hook 'eclimd--process-event-functions closure)
(unwind-protect
(progn
(add-hook 'eclimd--process-event-functions closure)
(while (not finished-p)
(accept-process-output eclimd-process))
(unless terminated-p output))
(remove-hook 'eclimd--process-event-functions closure)))))
(defun eclimd--await-connection (&optional async callback)
"Wait for the eclimd server to become active.
If ASYNC is omitted or nil, block until the eclimd server
becomes active. Call CALLBACK with no arguments when the
connection is established, but not when eclimd fails to
start."
(eclimd--match-process-output
eclimd--started-regexp async
(lambda (output)
(when output
(setq eclimd-port (match-string 1 output))
(when callback (funcall callback))))))
;;;###autoload
(defun start-eclimd (workspace-dir &optional callback)
"Start the eclimd server and optionally wait for it to be ready.
WORKSPACE-DIR is the desired workspace directory for which
eclimd will be started. `eclimd-default-workspace' is used
as the default value of this directory.
If CALLBACK is non-nil, it is called with no arguments once
the server is ready.
After having started the server process, this function may
block until eclimd is ready to receive commands, depending
on the value of `eclimd-wait-for-process'. Commands will
fail if they are executed before the server is ready.
To stop the server, you should use `stop-eclimd'."
(interactive (list (eclimd--read-workspace-dir)))
(let ((eclimd-prog (eclimd--executable-path)))
(if (not eclimd-prog)
(message "Cannot start eclimd: check eclimd-executable variable.")
(if eclimd-process
(message "Cannot start eclimd: eclimd was already started.")
(message (concat "Starting eclimd for workspace: " workspace-dir "..."))
(setq eclimd-process-buffer
(make-comint eclimd-process-buffer-name
eclimd-prog
nil
(concat "-Dosgi.instance.area.default="
(replace-regexp-in-string "~" "@user.home" workspace-dir))))
(setq eclimd-process (get-buffer-process eclimd-process-buffer))
(setq eclimd--comint-process-filter (process-filter eclimd-process))
(set-process-filter eclimd-process 'eclimd--process-filter)
(set-process-sentinel eclimd-process 'eclimd--process-sentinel)
(add-hook 'kill-emacs-hook #'stop-eclimd)
;; The flag is required because on exit Emacs asks the user about
;; running processes before running the `kill-emacs-hook'.
(set-process-query-on-exit-flag eclimd-process nil)
(eclimd--await-connection
(not eclimd-wait-for-process)
(lambda ()
(message "eclimd serving at port %s" eclimd-port)
(eclim--problems-update-maybe)
(when callback (funcall callback))))))))
(defun eclimd--ensure-started (&optional async callback)
"Ensure eclimd is running, autostarting it when possible.
If ASYNC is non-nil, the eclimd process will be connected to
asynchronously. After being connected, CALLBACK will be
invoked with no arguments.
An error is raised if both `eclimd-autostart' and ASYNC are
nil while there is no eclimd process. If ASYNC is non-nil
and eclimd cannot be started or is already running, CALLBACK is
not executed.
An error is raised when both `eclimd-autostart' and ASYNC are nil
while there is no eclimd process. If ASYNC is t and eclimd can
not be started / is already running, CALLBACK is not executed."
(if (eclim--connected-p)
(when callback (funcall callback))
(if eclimd-process
;; `eclimd-process' is set but we can not connect to eclimd, thus
;; eclimd is currently being started.
(eclimd--await-connection async callback)
(if eclimd-autostart
(let ((eclimd-wait-for-process (not async)))
(start-eclimd (eclimd--autostart-workspace) callback))
(let ((msg "Autostarting of eclimd is disabled, please start eclimd manually."))
(if async (message msg) (error msg)))))))
(defun stop-eclimd ()
"Gracefully terminate the eclimd process.
Also kill the *eclimd*-buffer and remove any hooks added by
`start-eclimd'."
(interactive)
(when eclimd-process
(when (eclim--connected-p)
(eclim/execute-command "shutdown")
(eclimd--match-process-output "Process eclimd finished"))
(delete-process eclimd-process)
(setq eclimd-process nil))
(when eclimd-process-buffer
(kill-buffer eclimd-process-buffer)
(setq eclimd-process-buffer nil))
(remove-hook 'kill-emacs-hook #'stop-eclimd))
(provide 'eclimd)
;;; eclimd ends here