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 var exe = GLib.Environment.find_program_in_path( "vala-language-server");
40 GLib.warning("could not find vala-language-server");
44 this.initProcess(exe);
48 public LanguageClientVala(Project.Project project)
50 // extend versions will proably call initialize to start and connect to server.
53 this.change_queue_id = GLib.Timeout.add_seconds(1, () => {
54 if (this.change_queue_file == null) {
57 if (this.getting_diagnostics) {
63 if (this.countdown < 0){
64 this.document_change_force.begin(this.change_queue_file, this.change_queue_file_source, (o, res) => {
65 this.document_change_force.end(res);
67 this.change_queue_file = null;
77 public bool initProcess(string process_path)
80 this.log(LanguageClientAction.LAUNCH, process_path);
81 GLib.debug("Launching %s", process_path);
82 this.launcher = new GLib.SubprocessLauncher (SubprocessFlags.STDIN_PIPE | SubprocessFlags.STDOUT_PIPE);
83 var env = GLib.Environ.get();
84 env += "G_MESSAGES_DEBUG=all";
86 this.launcher.set_environ(env);
87 var logpath = GLib.Environment.get_home_dir() + "/.cache/vala-language-server";
89 if (!GLib.FileUtils.test(logpath, GLib.FileTest.IS_DIR)) {
90 Posix.mkdir(logpath, 0700);
92 // not very reliable..
93 //this.launcher.set_stderr_file_path(
95 // (new GLib.DateTime.now_local()).format("%Y-%m-%d") + ".log"
97 //GLib.debug("log lang server to %s", logpath + "/" +
98 // (new GLib.DateTime.now_local()).format("%Y-%m-%d") + ".log");
103 this.subprocess = launcher.spawnv ({ process_path , "2>" , "/tmp/vala-language-server.log" });
105 this.subprocess.wait_async.begin( null, ( obj,res ) => {
107 this.subprocess.wait_async.end(res);
108 } catch (GLib.Error e) {
109 this.log(LanguageClientAction.ERROR_START, e.message);
110 GLib.debug("subprocess startup error %s", e.message);
112 this.log(LanguageClientAction.EXIT, "process ended");
113 GLib.debug("Subprocess ended %s", process_path);
117 var input_stream = this.subprocess.get_stdout_pipe ();
118 var output_stream = this.subprocess.get_stdin_pipe ();
120 if (input_stream is GLib.UnixInputStream && output_stream is GLib.UnixOutputStream) {
122 if (!GLib.Unix.set_fd_nonblocking(((GLib.UnixInputStream)input_stream).fd, true)
123 || !GLib.Unix.set_fd_nonblocking (((GLib.UnixOutputStream)output_stream).fd, true))
125 GLib.debug("could not set pipes to nonblocking");
130 this.subprocess_stream = new GLib.SimpleIOStream (input_stream, output_stream);
131 this.accept_io_stream ( this.subprocess_stream);
132 } catch (GLib.Error e) {
133 this.log(LanguageClientAction.ERROR_START, e.message);
134 GLib.debug("subprocess startup error %s", e.message);
140 bool in_close = false;
141 public override void client_accepted (Jsonrpc.Client client)
143 if (this.jsonrpc_client == null) {
144 this.jsonrpc_client = client;
146 GLib.debug("client accepted connection - calling init server");
147 this.log(LanguageClientAction.ACCEPT, "client accepted");
149 this.jsonrpc_client.notification.connect((method, paramz) => {
150 this.onNotification(method, paramz);
153 this.jsonrpc_client.failed.connect(() => {
154 this.log(LanguageClientAction.ERROR_RPC, "client failed");
155 GLib.debug("language server server has failed");
161 this.initialize_server ();
168 public override void initialize_server() {
170 Variant? return_value;
171 this.jsonrpc_client.call (
174 processId: new Variant.int32 ((int32) Posix.getpid ()),
175 rootPath: new Variant.string (this.project.path),
176 rootUri: new Variant.string (File.new_for_path (this.project.path).get_uri ()),
177 capabilities : this.buildDict (
178 textDocument: this.buildDict (
179 documentSymbol : this.buildDict (
180 hierarchicalDocumentSymbolSupport : new Variant.boolean (true)
188 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
189 this.open_files = new Gee.ArrayList<JsRender.JsRender>((a,b) => {
190 return a.path == b.path;
192 this.initialized = true;
193 this.getting_diagnostics = false;
195 } catch (GLib.Error e) {
196 GLib.debug ("LS replied with error %s", e.message);
206 if (this.launcher == null) {
209 this.getting_diagnostics = false;
210 this.in_close = true;
211 GLib.debug("onClose called");
213 if (this.jsonrpc_client != null) {
215 this.jsonrpc_client.close();
216 } catch (GLib.Error e) {
217 GLib.debug("rpc Error close error %s", e.message);
220 if (this.subprocess_stream != null) {
222 this.subprocess_stream.close();
223 } catch (GLib.Error e) {
224 GLib.debug("stream Error close %s", e.message);
227 if (this.subprocess != null) {
228 this.subprocess.force_exit();
230 if (this.launcher != null) {
231 this.launcher.close();
234 this.launcher = null;
235 this.subprocess = null;
236 this.jsonrpc_client = null;
238 this.in_close = false;
241 public async void restartServer()
247 public bool isReady()
250 this.log(LanguageClientAction.RESTART,"closed is set - restarting");
251 GLib.debug("server stopped = restarting");
252 this.initialized = false;
254 GLib.MainLoop loop = new GLib.MainLoop ();
255 this.restartServer.begin ((obj, async_res) => {
256 this.restartServer.end(async_res);
259 return false; // can't do an operation yet?
263 if (!this.initialized) {
264 GLib.debug("Server has not been initialized");
267 if (this.sent_shutdown) {
268 GLib.debug("Server has been started its shutting down process");
278 public void onNotification(string method, Variant? return_value)
281 case "textDocument/publishDiagnostics":
282 //GLib.debug("got notification %s : %s", method , Json.to_string (Json.gvariant_serialize (return_value), true));
284 GLib.Idle.add(() => {
285 this.onDiagnostic(return_value);
293 GLib.debug("got notification %s : %s", method , Json.to_string (Json.gvariant_serialize (return_value), true));
297 bool getting_diagnostics = false;
301 public void onDiagnostic(Variant? return_value)
303 //GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
304 var dg = Json.gobject_deserialize (typeof (Lsp.Diagnostics), Json.gvariant_serialize (return_value)) as Lsp.Diagnostics;
305 GLib.debug("got diag for %s", dg.filename);
306 this.log(LanguageClientAction.DIAG, dg.filename);
307 if (this.project.path == dg.filename) {
308 this.getting_diagnostics = false;
309 this.log(LanguageClientAction.DIAG_END, "diagnostics done");
313 this.getting_diagnostics =true;
314 var f = this.project.getByPath(dg.filename);
316 //GLib.debug("no file %s", dg.uri);
317 //this.project.updateErrorsforFile(null);
320 //GLib.debug("got Diagnostics for %s", f.path);
321 f.updateErrors( dg.diagnostics );
326 public override void document_open (JsRender.JsRender file)
328 if (!this.isReady()) {
331 if (this.open_files.contains(file)) {
334 this.open_files.add(file);
337 GLib.debug ("LS sent open");
339 this.jsonrpc_client.send_notification (
340 "textDocument/didOpen",
342 textDocument : this.buildDict (
343 uri: new Variant.string (file.to_url()),
344 languageId : new Variant.string (file.language_id()),
345 version : new GLib.Variant.uint64 ( (uint64) file.version),
346 text : new Variant.string (file.toSource())
351 this.log(LanguageClientAction.OPEN, file.path);
352 } catch( GLib.Error e) {
353 this.log(LanguageClientAction.ERROR_RPC, e.message);
355 GLib.debug ("LS sent open err %s", e.message);
360 public override async void document_save (JsRender.JsRender file)
362 if (!this.isReady()) {
365 // save only really flags the file on the server - to actually force a change update - we need to
366 // flag it as changed.
367 yield this.document_change_force(file, file.toSource());
369 this.change_queue_file = null;
370 GLib.debug ("LS send save");
373 var args = this.buildDict (
374 textDocument : this.buildDict ( ///TextDocumentItem;
375 uri: new GLib.Variant.string (file.to_url()),
376 version : new GLib.Variant.uint64 ( (uint64) file.version)
380 //GLib.debug ("textDocument/save send with %s", Json.to_string (Json.gvariant_serialize (args), true));
384 yield this.jsonrpc_client.send_notification_async (
385 "textDocument/didSave",
389 this.log(LanguageClientAction.SAVE, file.path);
390 } catch( GLib.Error e) {
391 this.log(LanguageClientAction.ERROR_RPC, e.message);
392 GLib.debug ("LS save err %s", e.message);
398 public override void document_close (JsRender.JsRender file)
400 if (!this.isReady()) {
403 this.change_queue_file = null;
405 if (this.open_files.contains(file)) {
406 this.open_files.remove(file);
408 this.log(LanguageClientAction.CLOSE, file.path);
409 GLib.debug ("LS send close");
411 this.jsonrpc_client.send_notification (
412 "textDocument/didChange",
414 textDocument : this.buildDict ( ///TextDocumentItem;
415 uri: new GLib.Variant.string (file.to_url())
421 } catch( GLib.Error e) {
422 this.log(LanguageClientAction.ERROR_RPC, e.message);
423 GLib.debug ("LS close err %s", e.message);
431 public override void document_change (JsRender.JsRender file )
433 if (this.change_queue_file != null && this.change_queue_file.path != file.path) {
434 this.document_change_force.begin(this.change_queue_file, this.change_queue_file_source, (o, res) => {
435 this.document_change_force.end(res);
440 this.change_queue_file = file;
447 public override async void document_change_force (JsRender.JsRender file, string contents)
451 if (!this.isReady()) {
454 this.countdown = 9; // not really relivant..
455 this.change_queue_file = null; // this is more important..
457 if (!this.open_files.contains(file)) {
458 this.document_open(file);
461 GLib.debug ("LS send change %s rev %d", file.path, file.version);
462 var ar = new Json.Array();
463 var obj = new Json.Object();
464 obj.set_string_member("text", contents);
465 ar.add_object_element(obj);
466 var node = new Json.Node(Json.NodeType.ARRAY);
468 this.log(LanguageClientAction.CHANGE, file.path);
470 yield this.jsonrpc_client.send_notification_async (
471 "textDocument/didChange",
473 textDocument : this.buildDict ( ///TextDocumentItem;
474 uri: new GLib.Variant.string (file.to_url()),
475 version : new GLib.Variant.uint64 ( (uint64) file.version)
477 contentChanges : Json.gvariant_deserialize (node, null)
482 } catch( GLib.Error e) {
483 this.log(LanguageClientAction.ERROR_RPC, e.message);
484 GLib.debug ("LS change err %s", e.message);
490 // called by close window (on last window)...
491 public override void exit () throws GLib.Error
493 if (!this.isReady()) {
497 this.log(LanguageClientAction.TERM, "SEND exit");
499 this.jsonrpc_client.send_notification (
507 // not used currently..
508 public override async void shutdown () throws GLib.Error
510 if (!this.isReady()) {
513 this.log(LanguageClientAction.TERM, "SEND shutodwn");
514 this.sent_shutdown = true;
515 Variant? return_value;
516 yield this.jsonrpc_client.call_async (
522 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
524 //public async ??/symbol (string symbol) throws GLib.Error {
526 // and now for the important styff..
530 @triggerType 1 = typing or ctl-spac, 2 = tiggercharactres? 3= inside completion?
532 public override async Lsp.CompletionList? completion(JsRender.JsRender file, int line, int offset , int triggerType = 1) throws GLib.Error
534 /* partial_result_token , work_done_token context = null) */
535 GLib.debug("%s get completion %s @ %d:%d", this.get_type().name(), file.relpath, line, offset);
537 var ret = new Lsp.CompletionList();
539 if (!this.isReady()) {
540 GLib.debug("completion - language server not ready");
543 // make sure completion has the latest info..
544 //if (this.change_queue_file != null && this.change_queue_file.path != file.path) {
545 // this.document_change_real(this.change_queue_file, this.change_queue_file_source);
546 // this.change_queue_file != null;
548 this.log(LanguageClientAction.COMPLETE, "SEND complete %s @ %d:%d".printf(file.relpath, line, offset) );
550 Variant? return_value;
552 var args = this.buildDict (
553 context : this.buildDict ( ///CompletionContext;
554 triggerKind: new GLib.Variant.int32 (triggerType)
555 // triggerCharacter : new GLib.Variant.string ("")
557 textDocument : this.buildDict ( ///TextDocumentItem;
558 uri: new GLib.Variant.string (file.to_url()),
559 version : new GLib.Variant.uint64 ( (uint64) file.version)
561 position : this.buildDict (
562 line : new GLib.Variant.uint64 ( (uint) line) ,
563 character : new GLib.Variant.uint64 ( uint.max(0, (offset -1)))
567 GLib.debug ("textDocument/completion send with %s", Json.to_string (Json.gvariant_serialize (args), true));
569 yield this.jsonrpc_client.call_async (
570 "textDocument/completion",
577 //GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
578 var json = Json.gvariant_serialize (return_value);
581 if (json.get_node_type() == Json.NodeType.OBJECT) {
582 ret = Json.gobject_deserialize (typeof (Lsp.CompletionList), json) as Lsp.CompletionList;
583 this.log(LanguageClientAction.COMPLETE_REPLY, "GOT complete %d items".printf(ret.items.size) );
584 GLib.debug ("LS replied with Object");
588 if (json.get_node_type() != Json.NodeType.ARRAY) {
589 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
590 this.log(LanguageClientAction.ERROR_REPLY, "GOT something else??");
594 var ar = json.get_array();
596 for(var i = 0; i < ar.get_length(); i++ ) {
597 var add= Json.gobject_deserialize ( typeof (Lsp.CompletionItem), ar.get_element(i)) as Lsp.CompletionItem;
601 this.log(LanguageClientAction.COMPLETE_REPLY, "GOT array %d items".printf(ret.items.size) );
602 GLib.debug ("LS replied with Array");
609 //CompletionListInfo.itmems.parse_varient or CompletionListInfo.parsevarient
610 public override async Lsp.Hover hover (JsRender.JsRender file, int line, int offset) throws GLib.Error
612 /* partial_result_token , work_done_token context = null) */
613 GLib.debug("get syntax %s", file.relpath);
614 var ret = new Lsp.Hover();
616 if (!this.isReady()) {
619 Variant? return_value;
620 yield this.jsonrpc_client.call_async (
621 "textDocument/hover",
624 textDocument : this.buildDict ( ///TextDocumentItem;
625 uri: new GLib.Variant.string (file.to_url()),
626 version : new GLib.Variant.uint64 ( (uint64) file.version)
628 position : this.buildDict (
629 line : new GLib.Variant.uint64 ( (uint) line) ,
630 character : new GLib.Variant.uint64 ( uint.max(0, (offset -1)))
639 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
640 var json = Json.gvariant_serialize (return_value);
641 ret = Json.gobject_deserialize ( typeof (Lsp.Hover), json) as Lsp.Hover;
652 public override async Gee.ArrayList<Lsp.DocumentSymbol> documentSymbols (JsRender.JsRender file) throws GLib.Error {
653 /* partial_result_token , work_done_token context = null) */
654 GLib.debug("get documentSymbols %s", file.relpath);
655 var ret = new Gee.ArrayList<Lsp.DocumentSymbol>();
657 if (!this.isReady()) {
660 Variant? return_value;
661 yield this.jsonrpc_client.call_async (
662 "textDocument/documentSymbol",
665 textDocument : this.buildDict ( ///TextDocumentItem;
666 uri: new GLib.Variant.string (file.to_url()),
667 version : new GLib.Variant.uint64 ( (uint64) file.version)
676 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
677 var json = Json.gvariant_serialize (return_value);
681 var ar = json.get_array();
682 GLib.debug ("LS replied with %D items", ar.get_length());
683 for(var i = 0; i < ar.get_length(); i++ ) {
684 var add= Json.gobject_deserialize ( typeof (Lsp.DocumentSymbol), ar.get_element(i)) as Lsp.DocumentSymbol;