3 public class LanguageClientVala : LanguageClient {
4 protected bool initialized = false;
5 bool sent_shutdown = false;
6 uint change_queue_id = 0;
9 private bool _closed = false;
11 get { return this._closed ; }
13 GLib.debug("closed has been set? to %s" , value ? "TRUE" : "FALSE" );
17 private GLib.SubprocessLauncher launcher = null;
18 private GLib.Subprocess? subprocess = null;
19 private IOStream? subprocess_stream = null;
20 public Jsonrpc.Client? jsonrpc_client = null;
23 Gee.ArrayList<JsRender.JsRender> open_files;
24 private JsRender.JsRender? _change_queue_file = null;
25 int doc_countdown = 0;
26 private string change_queue_file_source = "";
27 private JsRender.JsRender? doc_queue_file = null;
30 JsRender.JsRender? change_queue_file {
32 this.change_queue_file_source = value == null ? "" : value.toSource();
33 this._change_queue_file = value;
36 return this._change_queue_file;
44 var exe = GLib.Environment.find_program_in_path( "vala-language-server");
46 GLib.warning("could not find vala-language-server");
50 this.initProcess(exe);
54 public LanguageClientVala(Project.Project project)
56 // extend versions will proably call initialize to start and connect to server.
59 if (this.change_queue_id == 0 ) {
60 this.change_queue_id = GLib.Timeout.add(500, () => {
61 this.run_change_queue();
70 void run_change_queue()
73 if (this.change_queue_file == null) {
76 if (this.countdown < -1) {
79 if (this.getting_diagnostics) {
85 if (this.countdown < 0){
86 this.document_change_force.begin(this.change_queue_file, this.change_queue_file_source, (o, res) => {
87 this.document_change_force.end(res);
89 this.change_queue_file = null;
95 async int queuer(int cnt)
97 SourceFunc cb = this.queuer.callback;
99 GLib.Timeout.add(500, () => {
100 GLib.Idle.add((owned) cb);
107 static int doc_queue_id = 0;
112 public bool initProcess(string process_path)
115 this.log(LanguageClientAction.LAUNCH, process_path);
116 GLib.debug("Launching %s", process_path);
117 this.launcher = new GLib.SubprocessLauncher (SubprocessFlags.STDIN_PIPE | SubprocessFlags.STDOUT_PIPE);
118 var env = GLib.Environ.get();
119 env += "G_MESSAGES_DEBUG=all";
121 this.launcher.set_environ(env);
122 var logpath = GLib.Environment.get_home_dir() + "/.cache/vala-language-server";
124 if (!GLib.FileUtils.test(logpath, GLib.FileTest.IS_DIR)) {
125 Posix.mkdir(logpath, 0700);
127 // not very reliable..
128 //this.launcher.set_stderr_file_path(
130 // (new GLib.DateTime.now_local()).format("%Y-%m-%d") + ".log"
132 //GLib.debug("log lang server to %s", logpath + "/" +
133 // (new GLib.DateTime.now_local()).format("%Y-%m-%d") + ".log");
138 this.subprocess = launcher.spawnv ({ process_path , "2>" , "/tmp/vala-language-server.log" });
140 this.subprocess.wait_async.begin( null, ( obj,res ) => {
142 this.subprocess.wait_async.end(res);
143 } catch (GLib.Error e) {
144 this.log(LanguageClientAction.ERROR_START, e.message);
145 GLib.debug("subprocess startup error %s", e.message);
147 this.log(LanguageClientAction.EXIT, "process ended");
148 GLib.debug("Subprocess ended %s", process_path);
152 var input_stream = this.subprocess.get_stdout_pipe ();
153 var output_stream = this.subprocess.get_stdin_pipe ();
155 if (input_stream is GLib.UnixInputStream && output_stream is GLib.UnixOutputStream) {
157 if (!GLib.Unix.set_fd_nonblocking(((GLib.UnixInputStream)input_stream).fd, true)
158 || !GLib.Unix.set_fd_nonblocking (((GLib.UnixOutputStream)output_stream).fd, true))
160 GLib.debug("could not set pipes to nonblocking");
165 this.subprocess_stream = new GLib.SimpleIOStream (input_stream, output_stream);
166 this.accept_io_stream ( this.subprocess_stream);
167 } catch (GLib.Error e) {
168 this.log(LanguageClientAction.ERROR_START, e.message);
169 GLib.debug("subprocess startup error %s", e.message);
175 bool in_close = false;
176 public override void client_accepted (Jsonrpc.Client client)
178 if (this.jsonrpc_client == null) {
179 this.jsonrpc_client = client;
181 GLib.debug("client accepted connection - calling init server");
182 this.log(LanguageClientAction.ACCEPT, "client accepted");
184 this.jsonrpc_client.notification.connect((method, paramz) => {
185 this.onNotification(method, paramz);
188 this.jsonrpc_client.failed.connect(() => {
189 this.log(LanguageClientAction.ERROR_RPC, "client failed");
190 GLib.debug("language server server has failed");
196 this.initialize_server ();
203 public override void initialize_server() {
205 Variant? return_value;
206 this.jsonrpc_client.call (
209 processId: new Variant.int32 ((int32) Posix.getpid ()),
210 rootPath: new Variant.string (this.project.path),
211 rootUri: new Variant.string (File.new_for_path (this.project.path).get_uri ()),
212 capabilities : this.buildDict (
213 textDocument: this.buildDict (
214 documentSymbol : this.buildDict (
215 hierarchicalDocumentSymbolSupport : new Variant.boolean (true)
223 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
224 this.open_files = new Gee.ArrayList<JsRender.JsRender>((a,b) => {
225 return a.path == b.path;
227 this.initialized = true;
228 this.getting_diagnostics = false;
230 } catch (GLib.Error e) {
231 GLib.debug ("LS replied with error %s", e.message);
241 if (this.launcher == null) {
244 this.getting_diagnostics = false;
245 this.in_close = true;
246 GLib.debug("onClose called");
248 if (this.jsonrpc_client != null) {
250 this.jsonrpc_client.close();
251 } catch (GLib.Error e) {
252 GLib.debug("rpc Error close error %s", e.message);
255 if (this.subprocess_stream != null) {
257 this.subprocess_stream.close();
258 } catch (GLib.Error e) {
259 GLib.debug("stream Error close %s", e.message);
262 if (this.subprocess != null) {
263 this.subprocess.force_exit();
265 if (this.launcher != null) {
266 this.launcher.close();
269 this.launcher = null;
270 this.subprocess = null;
271 this.jsonrpc_client = null;
273 this.in_close = false;
276 public async void restartServer()
282 public bool isReady()
285 this.log(LanguageClientAction.RESTART,"closed is set - restarting");
286 GLib.debug("server stopped = restarting");
287 this.initialized = false;
289 GLib.MainLoop loop = new GLib.MainLoop ();
290 this.restartServer.begin ((obj, async_res) => {
291 this.restartServer.end(async_res);
294 return false; // can't do an operation yet?
298 if (!this.initialized) {
299 GLib.debug("Server has not been initialized");
302 if (this.sent_shutdown) {
303 GLib.debug("Server has been started its shutting down process");
313 public void onNotification(string method, Variant? return_value)
316 case "textDocument/publishDiagnostics":
317 //GLib.debug("got notification %s : %s", method , Json.to_string (Json.gvariant_serialize (return_value), true));
319 GLib.Idle.add(() => {
320 this.onDiagnostic(return_value);
328 GLib.debug("got notification %s : %s", method , Json.to_string (Json.gvariant_serialize (return_value), true));
332 bool getting_diagnostics = false;
336 public void onDiagnostic(Variant? return_value)
338 //GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
339 var dg = Json.gobject_deserialize (typeof (Lsp.Diagnostics), Json.gvariant_serialize (return_value)) as Lsp.Diagnostics;
340 GLib.debug("got diag for %s", dg.filename);
341 this.log(LanguageClientAction.DIAG, dg.filename);
342 if (this.project.path == dg.filename) {
343 this.getting_diagnostics = false;
344 this.log(LanguageClientAction.DIAG_END, "diagnostics done");
348 this.getting_diagnostics =true;
349 var f = this.project.getByPath(dg.filename);
351 //GLib.debug("no file %s", dg.uri);
352 //this.project.updateErrorsforFile(null);
355 //GLib.debug("got Diagnostics for %s", f.path);
356 f.updateErrors( dg.diagnostics );
361 public override void document_open (JsRender.JsRender file)
363 if (!this.isReady()) {
366 if (this.open_files.contains(file)) {
369 this.open_files.add(file);
372 GLib.debug ("LS sent open");
374 this.jsonrpc_client.send_notification (
375 "textDocument/didOpen",
377 textDocument : this.buildDict (
378 uri: new Variant.string (file.to_url()),
379 languageId : new Variant.string (file.language_id()),
380 version : new GLib.Variant.uint64 ( (uint64) file.version),
381 text : new Variant.string (file.toSource())
386 this.log(LanguageClientAction.OPEN, file.path);
387 } catch( GLib.Error e) {
388 this.log(LanguageClientAction.ERROR_RPC, e.message);
390 GLib.debug ("LS sent open err %s", e.message);
395 public override async void document_save (JsRender.JsRender file)
397 if (!this.isReady()) {
400 // save only really flags the file on the server - to actually force a change update - we need to
401 // flag it as changed.
402 yield this.document_change_force(file, file.toSource());
404 this.change_queue_file = null;
405 GLib.debug ("LS send save");
408 var args = this.buildDict (
409 textDocument : this.buildDict ( ///TextDocumentItem;
410 uri: new GLib.Variant.string (file.to_url()),
411 version : new GLib.Variant.uint64 ( (uint64) file.version)
415 //GLib.debug ("textDocument/save send with %s", Json.to_string (Json.gvariant_serialize (args), true));
419 yield this.jsonrpc_client.send_notification_async (
420 "textDocument/didSave",
424 this.log(LanguageClientAction.SAVE, file.path);
425 } catch( GLib.Error e) {
426 this.log(LanguageClientAction.ERROR_RPC, e.message);
427 GLib.debug ("LS save err %s", e.message);
433 public override void document_close (JsRender.JsRender file)
435 if (!this.isReady()) {
438 this.change_queue_file = null;
440 if (this.open_files.contains(file)) {
441 this.open_files.remove(file);
443 this.log(LanguageClientAction.CLOSE, file.path);
444 GLib.debug ("LS send close");
446 this.jsonrpc_client.send_notification (
447 "textDocument/didChange",
449 textDocument : this.buildDict ( ///TextDocumentItem;
450 uri: new GLib.Variant.string (file.to_url())
456 } catch( GLib.Error e) {
457 this.log(LanguageClientAction.ERROR_RPC, e.message);
458 GLib.debug ("LS close err %s", e.message);
466 public override void document_change (JsRender.JsRender file )
468 if (this.change_queue_file != null && this.change_queue_file.path != file.path) {
469 this.document_change_force.begin(this.change_queue_file, this.change_queue_file_source, (o, res) => {
470 this.document_change_force.end(res);
475 this.change_queue_file = file;
482 public override async void document_change_force (JsRender.JsRender file, string contents)
486 if (!this.isReady()) {
489 this.countdown = -2; // not really relivant..
490 this.change_queue_file = null; // this is more important..
492 if (!this.open_files.contains(file)) {
493 this.document_open(file);
496 GLib.debug ("LS send change %s rev %d", file.path, file.version);
497 var ar = new Json.Array();
498 var obj = new Json.Object();
499 obj.set_string_member("text", contents);
500 ar.add_object_element(obj);
501 var node = new Json.Node(Json.NodeType.ARRAY);
503 this.log(LanguageClientAction.CHANGE, file.path);
505 yield this.jsonrpc_client.send_notification_async (
506 "textDocument/didChange",
508 textDocument : this.buildDict ( ///TextDocumentItem;
509 uri: new GLib.Variant.string (file.to_url()),
510 version : new GLib.Variant.uint64 ( (uint64) file.version)
512 contentChanges : Json.gvariant_deserialize (node, null)
517 } catch( GLib.Error e) {
518 this.log(LanguageClientAction.ERROR_RPC, e.message);
519 GLib.debug ("LS change err %s", e.message);
525 // called by close window (on last window)...
526 public override void exit () throws GLib.Error
528 if (!this.isReady()) {
532 this.log(LanguageClientAction.TERM, "SEND exit");
534 this.jsonrpc_client.send_notification (
542 // not used currently..
543 public override async void shutdown () throws GLib.Error
545 if (!this.isReady()) {
548 this.log(LanguageClientAction.TERM, "SEND shutodwn");
549 this.sent_shutdown = true;
550 Variant? return_value;
551 yield this.jsonrpc_client.call_async (
557 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
559 //public async ??/symbol (string symbol) throws GLib.Error {
561 // and now for the important styff..
565 @triggerType 1 = typing or ctl-spac, 2 = tiggercharactres? 3= inside completion?
567 public override async Lsp.CompletionList? completion(JsRender.JsRender file, int line, int offset , int triggerType = 1) throws GLib.Error
569 /* partial_result_token , work_done_token context = null) */
570 GLib.debug("%s get completion %s @ %d:%d", this.get_type().name(), file.relpath, line, offset);
572 var ret = new Lsp.CompletionList();
574 if (!this.isReady()) {
575 GLib.debug("completion - language server not ready");
578 // make sure completion has the latest info..
579 //if (this.change_queue_file != null && this.change_queue_file.path != file.path) {
580 // this.document_change_real(this.change_queue_file, this.change_queue_file_source);
581 // this.change_queue_file != null;
583 this.log(LanguageClientAction.COMPLETE, "SEND complete %s @ %d:%d".printf(file.relpath, line, offset) );
585 Variant? return_value;
587 var args = this.buildDict (
588 context : this.buildDict ( ///CompletionContext;
589 triggerKind: new GLib.Variant.int32 (triggerType)
590 // triggerCharacter : new GLib.Variant.string ("")
592 textDocument : this.buildDict ( ///TextDocumentItem;
593 uri: new GLib.Variant.string (file.to_url()),
594 version : new GLib.Variant.uint64 ( (uint64) file.version)
596 position : this.buildDict (
597 line : new GLib.Variant.uint64 ( (uint) line) ,
598 character : new GLib.Variant.uint64 ( uint.max(0, (offset -1)))
602 GLib.debug ("textDocument/completion send with %s", Json.to_string (Json.gvariant_serialize (args), true));
604 yield this.jsonrpc_client.call_async (
605 "textDocument/completion",
612 //GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
613 var json = Json.gvariant_serialize (return_value);
616 if (json.get_node_type() == Json.NodeType.OBJECT) {
617 ret = Json.gobject_deserialize (typeof (Lsp.CompletionList), json) as Lsp.CompletionList;
618 this.log(LanguageClientAction.COMPLETE_REPLY, "GOT complete %d items".printf(ret.items.size) );
619 GLib.debug ("LS replied with Object");
623 if (json.get_node_type() != Json.NodeType.ARRAY) {
624 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
625 this.log(LanguageClientAction.ERROR_REPLY, "GOT something else??");
629 var ar = json.get_array();
631 for(var i = 0; i < ar.get_length(); i++ ) {
632 var add= Json.gobject_deserialize ( typeof (Lsp.CompletionItem), ar.get_element(i)) as Lsp.CompletionItem;
636 this.log(LanguageClientAction.COMPLETE_REPLY, "GOT array %d items".printf(ret.items.size) );
637 GLib.debug ("LS replied with Array");
645 static int hover_call_count = 1;
646 bool getting_hover = false;
648 //CompletionListInfo.itmems.parse_varient or CompletionListInfo.parsevarient
649 public override async Lsp.Hover hover (JsRender.JsRender file, int line, int offset) throws GLib.Error
651 /* partial_result_token , work_done_token context = null) */
652 //GLib.debug("get hover %s %d %d", file.relpath, (int)line, (int)offset);
653 var ret = new Lsp.Hover();
655 if (!this.isReady()) {
658 if (this.getting_hover) {
663 var call_id = yield this.queuer(hover_call_count);
665 //GLib.debug("end hover call=%d count=%d", call_id, hover_call_count);
666 if (call_id != hover_call_count) {
667 //GLib.debug("get hover CANCELLED %s %d %d", file.relpath, (int)line, (int)offset);
671 //GLib.debug("get hover RUN %s %d %d", file.relpath, (int)line, (int)offset);
673 this.getting_hover = true;
675 Variant? return_value;
677 yield this.jsonrpc_client.call_async (
678 "textDocument/hover",
681 textDocument : this.buildDict ( ///TextDocumentItem;
682 uri: new GLib.Variant.string (file.to_url()),
683 version : new GLib.Variant.uint64 ( (uint64) file.version)
685 position : this.buildDict (
686 line : new GLib.Variant.uint64 ( (uint) line) ,
687 character : new GLib.Variant.uint64 ( uint.max(0, (offset -1)))
694 } catch(GLib.Error e) {
695 this.getting_hover = false;
698 this.getting_hover = false;
699 GLib.debug ("LS hover replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
700 if (return_value == null) {
704 var json = Json.gvariant_serialize (return_value);
705 if (json.get_node_type() != Json.NodeType.OBJECT) {
710 ret = Json.gobject_deserialize ( typeof (Lsp.Hover), json) as Lsp.Hover;
719 static int doc_symbol_queue_call_count = 1;
723 public override void queueDocumentSymbols (JsRender.JsRender file)
726 this.documentSymbols.begin(file, (o, res) => {
727 var ret = documentSymbols.end(res);
728 file.navigation_tree_updated(ret);
734 bool getting_symbols = false;
736 public override async Gee.ArrayList<Lsp.DocumentSymbol> documentSymbols (JsRender.JsRender file) throws GLib.Error
738 /* partial_result_token , work_done_token context = null) */
739 GLib.debug("get documentSymbols %s", file.relpath);
740 var ret = new Gee.ArrayList<Lsp.DocumentSymbol>();
742 if (!this.isReady()) {
745 if (this.getting_symbols) {
750 doc_symbol_queue_call_count++;
751 var call_id = yield this.queuer(doc_symbol_queue_call_count);
752 if (call_id != doc_symbol_queue_call_count) {
756 this.getting_symbols = true;
758 Variant? return_value;
760 yield this.jsonrpc_client.call_async (
761 "textDocument/documentSymbol",
764 textDocument : this.buildDict ( ///TextDocumentItem;
765 uri: new GLib.Variant.string (file.to_url()),
766 version : new GLib.Variant.uint64 ( (uint64) file.version)
774 this.getting_symbols = false;
777 this.getting_symbols = false;
779 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
780 var json = Json.gvariant_serialize (return_value);
784 var ar = json.get_array();
785 GLib.debug ("LS replied with %D items", ar.get_length());
786 for(var i = 0; i < ar.get_length(); i++ ) {
787 var add= Json.gobject_deserialize ( typeof (Lsp.DocumentSymbol), ar.get_element(i)) as Lsp.DocumentSymbol;
795 // cant seem to get this to show anything!!
796 public override async Gee.ArrayList<Lsp.SignatureInformation> signatureHelp (JsRender.JsRender file, int line, int offset) throws GLib.Error {
797 /* partial_result_token , work_done_token context = null) */
798 GLib.debug("get signatureHelp %s, %d, %d", file.relpath, line, offset);
799 var ret = new Gee.ArrayList<Lsp.SignatureInformation>();
801 if (!this.isReady()) {
804 Variant? return_value;
805 yield this.jsonrpc_client.call_async (
806 "textDocument/signatureHelp",
809 textDocument : this.buildDict ( ///TextDocumentItem;
810 uri: new GLib.Variant.string (file.to_url())
812 position : this.buildDict (
813 line : new GLib.Variant.uint64 ( (uint) line) ,
814 character : new GLib.Variant.uint64 ( uint.max(0, (offset -1)))
823 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
824 var json = Json.gvariant_serialize (return_value);
825 if (json.get_node_type() != Json.NodeType.ARRAY) {
831 var ar = json.get_array();
832 GLib.debug ("LS replied with %D items", ar.get_length());
833 for(var i = 0; i < ar.get_length(); i++ ) {
834 var add= Json.gobject_deserialize ( typeof (Lsp.SignatureInformation), ar.get_element(i)) as Lsp.SignatureInformation;
842 // ok for general symbol search, not much details though.
843 public override async Gee.ArrayList<Lsp.SymbolInformation> symbol (string sym) throws GLib.Error
845 /* partial_result_token , work_done_token context = null) */
846 GLib.debug("get symbol %s,", sym);
847 var ret = new Gee.ArrayList<Lsp.SymbolInformation>();
849 if (!this.isReady()) {
852 Variant? return_value;
853 yield this.jsonrpc_client.call_async (
856 query : new GLib.Variant.string (sym)
862 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));