3 public class LanguageClientVala : LanguageClient {
5 protected bool initialized = false;
6 bool sent_shutdown = false;
7 uint change_queue_id = 0;
10 private bool _closed = false;
12 get { return this._closed ; }
14 GLib.debug("closed has been set? to %s" , value ? "TRUE" : "FALSE" );
18 private GLib.SubprocessLauncher launcher = null;
19 private GLib.Subprocess? subprocess = null;
20 private IOStream? subprocess_stream = null;
21 public Jsonrpc.Client? jsonrpc_client = null;
23 Gee.ArrayList<JsRender.JsRender> open_files;
24 private JsRender.JsRender? _change_queue_file = null;
25 private string change_queue_file_source = "";
27 JsRender.JsRender? change_queue_file {
29 this.change_queue_file_source = value == null ? "" : value.toSource();
30 this._change_queue_file = value;
33 return this._change_queue_file;
38 this.initProcess("/usr/bin/vala-language-server");
42 public LanguageClientVala(Project.Project project)
44 // extend versions will proably call initialize to start and connect to server.
46 this.open_files = new Gee.ArrayList<JsRender.JsRender>();
47 this.change_queue_id = GLib.Timeout.add_seconds(1, () => {
48 if (this.change_queue_file == null) {
52 if (this.countdown < 0){
53 this.document_change_force(this.change_queue_file, this.change_queue_file_source);
54 this.change_queue_file = null;
64 public bool initProcess(string process_path)
67 this.log(LanguageClientAction.LAUNCH, process_path);
68 GLib.debug("Launching %s", process_path);
69 this.launcher = new GLib.SubprocessLauncher (SubprocessFlags.STDIN_PIPE | SubprocessFlags.STDOUT_PIPE);
70 this.launcher.set_environ(GLib.Environ.get());
74 this.subprocess = launcher.spawnv ({ process_path });
76 this.subprocess.wait_async.begin( null, ( obj,res ) => {
78 this.subprocess.wait_async.end(res);
79 } catch (GLib.Error e) {
80 this.log(LanguageClientAction.ERROR_START, e.message);
81 GLib.debug("subprocess startup error %s", e.message);
83 this.log(LanguageClientAction.EXIT, "process ended");
84 GLib.debug("Subprocess ended %s", process_path);
88 var input_stream = this.subprocess.get_stdout_pipe ();
89 var output_stream = this.subprocess.get_stdin_pipe ();
91 if (input_stream is GLib.UnixInputStream && output_stream is GLib.UnixOutputStream) {
93 if (!GLib.Unix.set_fd_nonblocking(((GLib.UnixInputStream)input_stream).fd, true)
94 || !GLib.Unix.set_fd_nonblocking (((GLib.UnixOutputStream)output_stream).fd, true))
96 GLib.debug("could not set pipes to nonblocking");
101 this.subprocess_stream = new GLib.SimpleIOStream (input_stream, output_stream);
102 this.accept_io_stream ( this.subprocess_stream);
103 } catch (GLib.Error e) {
104 this.log(LanguageClientAction.ERROR_START, e.message);
105 GLib.debug("subprocess startup error %s", e.message);
111 bool in_close = false;
112 public override void client_accepted (Jsonrpc.Client client)
114 if (this.jsonrpc_client == null) {
115 this.jsonrpc_client = client;
117 GLib.debug("client accepted connection - calling init server");
118 this.log(LanguageClientAction.ACCEPT, "client accepted");
120 this.jsonrpc_client.notification.connect((method, paramz) => {
121 this.onNotification(method, paramz);
124 this.jsonrpc_client.failed.connect(() => {
125 this.log(LanguageClientAction.ERROR_RPC, "client failed");
128 GLib.debug("language server server has failed");
131 this.initialize_server ();
138 public override void initialize_server() {
140 Variant? return_value;
141 this.jsonrpc_client.call (
144 processId: new Variant.int32 ((int32) Posix.getpid ()),
145 rootPath: new Variant.string (this.project.path),
146 rootUri: new Variant.string (File.new_for_path (this.project.path).get_uri ())
151 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
152 this.initialized = true;
154 } catch (GLib.Error e) {
155 GLib.debug ("LS replied with error %s", e.message);
165 if (this.launcher == null) {
168 this.in_close = true;
169 GLib.debug("onClose called");
171 if (this.jsonrpc_client != null) {
173 this.jsonrpc_client.close();
174 } catch (GLib.Error e) {
175 GLib.debug("rpc Error close error %s", e.message);
178 if (this.subprocess_stream != null) {
180 this.subprocess_stream.close();
181 } catch (GLib.Error e) {
182 GLib.debug("stream Error close %s", e.message);
185 if (this.subprocess != null) {
186 this.subprocess.force_exit();
188 if (this.launcher != null) {
189 this.launcher.close();
192 this.launcher = null;
193 this.subprocess = null;
194 this.jsonrpc_client = null;
196 this.in_close = false;
199 public async void restartServer()
202 foreach(var f in this.open_files) {
203 this.document_open(f);
207 public bool isReady()
210 this.log(LanguageClientAction.RESTART,"closed is set - restarting");
211 GLib.debug("server stopped = restarting");
212 this.initialized = false;
214 GLib.MainLoop loop = new GLib.MainLoop ();
215 this.restartServer.begin ((obj, async_res) => {
216 this.restartServer.end(async_res);
219 return false; // can't do an operation yet?
223 if (!this.initialized) {
224 GLib.debug("Server has not been initialized");
227 if (this.sent_shutdown) {
228 GLib.debug("Server has been started its shutting down process");
238 public void onNotification(string method, Variant? return_value)
241 case "textDocument/publishDiagnostics":
242 this.onDiagnostic(return_value);
248 GLib.debug("got notification %s : %s", method , Json.to_string (Json.gvariant_serialize (return_value), true));
255 public void onDiagnostic(Variant? return_value)
258 var dg = Json.gobject_deserialize (typeof (Lsp.Diagnostics), Json.gvariant_serialize (return_value)) as Lsp.Diagnostics;
259 this.log(LanguageClientAction.DIAG, dg.filename);
260 var f = this.project.getByPath(dg.filename);
262 //GLib.debug("no file %s", dg.uri);
263 this.project.updateErrorsforFile(null);
266 foreach(var v in f.errorsByType.values) {
269 foreach(var diag in dg.diagnostics) {
270 var ce = new CompileError.new_from_diagnostic(f, diag);
271 if (!f.errorsByType.has_key(ce.category)) {
272 f.errorsByType.set(ce.category, new GLib.ListStore(typeof(CompileError)));
274 f.errorsByType.get(ce.category).append(ce);
276 f.project.updateErrorsforFile(f);
280 public override void document_open (JsRender.JsRender file)
282 if (!this.isReady()) {
285 if (!this.open_files.contains(file)) {
286 this.open_files.add(file);
289 GLib.debug ("LS sent open");
291 this.jsonrpc_client.send_notification (
292 "textDocument/didOpen",
294 textDocument : this.buildDict (
295 uri: new Variant.string (file.to_url()),
296 languageId : new Variant.string (file.language_id()),
297 version : new GLib.Variant.uint64 ( (uint64) file.version),
298 text : new Variant.string (file.toSource())
303 this.log(LanguageClientAction.OPEN, file.path);
304 } catch( GLib.Error e) {
305 this.log(LanguageClientAction.ERROR_RPC, e.message);
307 GLib.debug ("LS sent open err %s", e.message);
312 public override void document_save (JsRender.JsRender file)
314 if (!this.isReady()) {
317 // save only really flags the file on the server - to actually force a change update - we need to
318 // flag it as changed.
319 this.document_change_force(file, file.toSource());
321 this.change_queue_file = null;
322 GLib.debug ("LS send save");
325 var args = this.buildDict (
326 textDocument : this.buildDict ( ///TextDocumentItem;
327 uri: new GLib.Variant.string (file.to_url()),
328 version : new GLib.Variant.uint64 ( (uint64) file.version)
332 //GLib.debug ("textDocument/save send with %s", Json.to_string (Json.gvariant_serialize (args), true));
336 this.jsonrpc_client.send_notification (
337 "textDocument/didSave",
341 this.log(LanguageClientAction.SAVE, file.path);
342 } catch( GLib.Error e) {
343 this.log(LanguageClientAction.ERROR_RPC, e.message);
344 GLib.debug ("LS save err %s", e.message);
350 public override void document_close (JsRender.JsRender file)
352 if (!this.isReady()) {
355 this.change_queue_file = null;
357 if (this.open_files.contains(file)) {
358 this.open_files.remove(file);
360 this.log(LanguageClientAction.CLOSE, file.path);
361 GLib.debug ("LS send close");
363 this.jsonrpc_client.send_notification (
364 "textDocument/didChange",
366 textDocument : this.buildDict ( ///TextDocumentItem;
367 uri: new GLib.Variant.string (file.to_url())
373 } catch( GLib.Error e) {
374 this.log(LanguageClientAction.ERROR_RPC, e.message);
375 GLib.debug ("LS close err %s", e.message);
383 public override void document_change (JsRender.JsRender file )
385 if (this.change_queue_file != null && this.change_queue_file.path != file.path) {
386 this.document_change_force(this.change_queue_file, this.change_queue_file_source);
390 this.change_queue_file = file;
397 public override void document_change_force (JsRender.JsRender file, string contents)
399 if (!this.isReady()) {
404 GLib.debug ("LS send change");
405 var ar = new Json.Array();
406 var obj = new Json.Object();
407 obj.set_string_member("text", contents);
408 ar.add_object_element(obj);
409 var node = new Json.Node(Json.NodeType.ARRAY);
411 this.log(LanguageClientAction.CHANGE, file.path);
413 this.jsonrpc_client.send_notification (
414 "textDocument/didChange",
416 textDocument : this.buildDict ( ///TextDocumentItem;
417 uri: new GLib.Variant.string (file.to_url()),
418 version : new GLib.Variant.uint64 ( (uint64) file.version)
420 contentChanges : Json.gvariant_deserialize (node, null)
425 } catch( GLib.Error e) {
426 this.log(LanguageClientAction.ERROR_RPC, e.message);
427 GLib.debug ("LS change err %s", e.message);
433 // called by close window (on last window)...
434 public override void exit () throws GLib.Error
436 if (!this.isReady()) {
440 this.log(LanguageClientAction.TERM, "SEND exit");
442 this.jsonrpc_client.send_notification (
450 // not used currently..
451 public override async void shutdown () throws GLib.Error
453 if (!this.isReady()) {
456 this.log(LanguageClientAction.TERM, "SEND shutodwn");
457 this.sent_shutdown = true;
458 Variant? return_value;
459 yield this.jsonrpc_client.call_async (
465 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
467 //public async ??/symbol (string symbol) throws GLib.Error {
469 // and now for the important styff..
473 @triggerType 1 = typing or ctl-spac, 2 = tiggercharactres? 3= inside completion?
475 public override async Lsp.CompletionList? completion(JsRender.JsRender file, int line, int offset , int triggerType = 1) throws GLib.Error
477 /* partial_result_token , work_done_token context = null) */
478 GLib.debug("%s get completion %s @ %d:%d", this.get_type().name(), file.relpath, line, offset);
480 var ret = new Lsp.CompletionList();
482 if (!this.isReady()) {
483 GLib.debug("completion - language server not ready");
486 // make sure completion has the latest info..
487 //if (this.change_queue_file != null && this.change_queue_file.path != file.path) {
488 // this.document_change_real(this.change_queue_file, this.change_queue_file_source);
489 // this.change_queue_file != null;
491 this.log(LanguageClientAction.COMPLETE, "SEND complete %s @ %d:%d".printf(file.relpath, line, offset) );
493 Variant? return_value;
495 var args = this.buildDict (
496 context : this.buildDict ( ///CompletionContext;
497 triggerKind: new GLib.Variant.int32 (triggerType)
498 // triggerCharacter : new GLib.Variant.string ("")
500 textDocument : this.buildDict ( ///TextDocumentItem;
501 uri: new GLib.Variant.string (file.to_url()),
502 version : new GLib.Variant.uint64 ( (uint64) file.version)
504 position : this.buildDict (
505 line : new GLib.Variant.uint64 ( (uint) line) ,
506 character : new GLib.Variant.uint64 ( uint.max(0, (offset -1)))
510 GLib.debug ("textDocument/completion send with %s", Json.to_string (Json.gvariant_serialize (args), true));
512 yield this.jsonrpc_client.call_async (
513 "textDocument/completion",
520 //GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
521 var json = Json.gvariant_serialize (return_value);
524 if (json.get_node_type() == Json.NodeType.OBJECT) {
525 ret = Json.gobject_deserialize (typeof (Lsp.CompletionList), json) as Lsp.CompletionList;
526 this.log(LanguageClientAction.COMPLETE_REPLY, "GOT complete %d items".printf(ret.items.size) );
527 GLib.debug ("LS replied with Object");
531 if (json.get_node_type() != Json.NodeType.ARRAY) {
532 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
533 this.log(LanguageClientAction.ERROR_REPLY, "GOT something else??");
537 var ar = json.get_array();
539 for(var i = 0; i < ar.get_length(); i++ ) {
540 var add= Json.gobject_deserialize ( typeof (Lsp.CompletionItem), ar.get_element(i)) as Lsp.CompletionItem;
544 this.log(LanguageClientAction.COMPLETE_REPLY, "GOT array %d items".printf(ret.items.size) );
545 GLib.debug ("LS replied with Array");
550 //CompletionListInfo.itmems.parse_varient or CompletionListInfo.parsevarient
551 public override async Gee.ArrayList<Lsp.DocumentSymbol> syntax (JsRender.JsRender file) throws GLib.Error
553 /* partial_result_token , work_done_token context = null) */
554 GLib.debug("get syntax %s", file.relpath);
555 var ret = new Gee.ArrayList<Lsp.DocumentSymbol>();
557 if (!this.isReady()) {
560 Variant? return_value;
561 yield this.jsonrpc_client.call_async (
562 "textDocument/documentSymbol",
565 textDocument : this.buildDict ( ///TextDocumentItem;
566 uri: new GLib.Variant.string (file.to_url()),
567 version : new GLib.Variant.uint64 ( (uint64) file.version)
576 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
577 var json = Json.gvariant_serialize (return_value);
581 var ar = json.get_array();
582 for(var i = 0; i < ar.get_length(); i++ ) {
583 var add= Json.gobject_deserialize ( typeof (Lsp.DocumentSymbol), ar.get_element(i)) as Lsp.DocumentSymbol;