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 ();
202 public override void initialize_server() {
204 Variant? return_value;
205 this.jsonrpc_client.call (
208 processId: new Variant.int32 ((int32) Posix.getpid ()),
209 rootPath: new Variant.string (this.project.path),
210 rootUri: new Variant.string (File.new_for_path (this.project.path).get_uri ()),
211 capabilities : this.buildDict (
212 textDocument: this.buildDict (
213 documentSymbol : this.buildDict (
214 hierarchicalDocumentSymbolSupport : new Variant.boolean (true)
222 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
223 this.open_files = new Gee.ArrayList<JsRender.JsRender>((a,b) => {
224 return a.path == b.path;
226 this.initialized = true;
227 this.getting_diagnostics = false;
229 } catch (GLib.Error e) {
230 GLib.debug ("LS replied with error %s", e.message);
240 if (this.launcher == null) {
243 this.getting_diagnostics = false;
244 this.in_close = true;
245 GLib.debug("onClose called");
247 if (this.jsonrpc_client != null) {
249 this.jsonrpc_client.close();
250 } catch (GLib.Error e) {
251 GLib.debug("rpc Error close error %s", e.message);
254 if (this.subprocess_stream != null) {
256 this.subprocess_stream.close();
257 } catch (GLib.Error e) {
258 GLib.debug("stream Error close %s", e.message);
261 if (this.subprocess != null) {
262 this.subprocess.force_exit();
264 if (this.launcher != null) {
265 this.launcher.close();
268 this.launcher = null;
269 this.subprocess = null;
270 this.jsonrpc_client = null;
272 this.in_close = false;
275 public async void restartServer()
281 public override bool isReady()
284 this.log(LanguageClientAction.RESTART,"closed is set - restarting");
285 GLib.debug("server stopped = restarting");
286 this.initialized = false;
288 GLib.MainLoop loop = new GLib.MainLoop ();
289 this.restartServer.begin ((obj, async_res) => {
290 this.restartServer.end(async_res);
293 return false; // can't do an operation yet?
297 if (!this.initialized) {
298 GLib.debug("Server has not been initialized");
301 if (this.sent_shutdown) {
302 GLib.debug("Server has been started its shutting down process");
312 public void onNotification(string method, Variant? return_value)
315 case "textDocument/publishDiagnostics":
316 //GLib.debug("got notification %s : %s", method , Json.to_string (Json.gvariant_serialize (return_value), true));
318 GLib.Idle.add(() => {
319 this.onDiagnostic(return_value);
327 GLib.debug("got notification %s : %s", method , Json.to_string (Json.gvariant_serialize (return_value), true));
331 bool getting_diagnostics = false;
335 public void onDiagnostic(Variant? return_value)
337 //GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
338 var dg = Json.gobject_deserialize (typeof (Lsp.Diagnostics), Json.gvariant_serialize (return_value)) as Lsp.Diagnostics;
339 GLib.debug("got diag for %s", dg.filename);
340 this.log(LanguageClientAction.DIAG, dg.filename);
341 if (this.project.path == dg.filename) {
342 this.getting_diagnostics = false;
343 this.log(LanguageClientAction.DIAG_END, "diagnostics done");
347 this.getting_diagnostics =true;
348 var f = this.project.getByPath(dg.filename);
350 //GLib.debug("no file %s", dg.uri);
351 //this.project.updateErrorsforFile(null);
354 //GLib.debug("got Diagnostics for %s", f.path);
355 f.updateErrors( dg.diagnostics );
360 public override void document_open (JsRender.JsRender file)
362 if (!this.isReady()) {
365 if (this.open_files.contains(file)) {
368 this.open_files.add(file);
371 GLib.debug ("LS sent open");
373 this.jsonrpc_client.send_notification (
374 "textDocument/didOpen",
376 textDocument : this.buildDict (
377 uri: new Variant.string (file.to_url()),
378 languageId : new Variant.string (file.language_id()),
379 version : new GLib.Variant.uint64 ( (uint64) file.version),
380 text : new Variant.string (file.toSource())
385 this.log(LanguageClientAction.OPEN, file.path);
386 } catch( GLib.Error e) {
387 this.log(LanguageClientAction.ERROR_RPC, e.message);
389 GLib.debug ("LS sent open err %s", e.message);
394 public override async void document_save (JsRender.JsRender file)
396 if (!this.isReady()) {
399 // save only really flags the file on the server - to actually force a change update - we need to
400 // flag it as changed.
401 yield this.document_change_force(file, file.toSource());
403 this.change_queue_file = null;
404 GLib.debug ("LS send save");
407 var args = this.buildDict (
408 textDocument : this.buildDict ( ///TextDocumentItem;
409 uri: new GLib.Variant.string (file.to_url()),
410 version : new GLib.Variant.uint64 ( (uint64) file.version)
414 //GLib.debug ("textDocument/save send with %s", Json.to_string (Json.gvariant_serialize (args), true));
418 yield this.jsonrpc_client.send_notification_async (
419 "textDocument/didSave",
423 this.log(LanguageClientAction.SAVE, file.path);
424 } catch( GLib.Error e) {
425 this.log(LanguageClientAction.ERROR_RPC, e.message);
426 GLib.debug ("LS save err %s", e.message);
432 public override void document_close (JsRender.JsRender file)
434 if (!this.isReady()) {
437 this.change_queue_file = null;
439 if (this.open_files.contains(file)) {
440 this.open_files.remove(file);
442 this.log(LanguageClientAction.CLOSE, file.path);
443 GLib.debug ("LS send close");
445 this.jsonrpc_client.send_notification (
446 "textDocument/didChange",
448 textDocument : this.buildDict ( ///TextDocumentItem;
449 uri: new GLib.Variant.string (file.to_url())
455 } catch( GLib.Error e) {
456 this.log(LanguageClientAction.ERROR_RPC, e.message);
457 GLib.debug ("LS close err %s", e.message);
465 public override void document_change (JsRender.JsRender file )
467 if (this.change_queue_file != null && this.change_queue_file.path != file.path) {
468 this.document_change_force.begin(this.change_queue_file, this.change_queue_file_source, (o, res) => {
469 this.document_change_force.end(res);
474 this.change_queue_file = file;
481 public override async void document_change_force (JsRender.JsRender file, string contents)
485 if (!this.isReady()) {
488 this.countdown = -2; // not really relivant..
489 this.change_queue_file = null; // this is more important..
491 if (!this.open_files.contains(file)) {
492 this.document_open(file);
495 GLib.debug ("LS send change %s rev %d", file.path, file.version);
496 var ar = new Json.Array();
497 var obj = new Json.Object();
498 obj.set_string_member("text", contents);
499 ar.add_object_element(obj);
500 var node = new Json.Node(Json.NodeType.ARRAY);
502 this.log(LanguageClientAction.CHANGE, file.path);
504 yield this.jsonrpc_client.send_notification_async (
505 "textDocument/didChange",
507 textDocument : this.buildDict ( ///TextDocumentItem;
508 uri: new GLib.Variant.string (file.to_url()),
509 version : new GLib.Variant.uint64 ( (uint64) file.version)
511 contentChanges : Json.gvariant_deserialize (node, null)
516 } catch( GLib.Error e) {
517 this.log(LanguageClientAction.ERROR_RPC, e.message);
518 GLib.debug ("LS change err %s", e.message);
524 // called by close window (on last window)...
525 public override void exit () throws GLib.Error
527 if (!this.isReady()) {
531 this.log(LanguageClientAction.TERM, "SEND exit");
533 this.jsonrpc_client.send_notification (
541 // not used currently..
542 public override async void shutdown () throws GLib.Error
544 if (!this.isReady()) {
547 this.log(LanguageClientAction.TERM, "SEND shutodwn");
548 this.sent_shutdown = true;
549 Variant? return_value;
550 yield this.jsonrpc_client.call_async (
556 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
558 //public async ??/symbol (string symbol) throws GLib.Error {
560 // and now for the important styff..
564 @triggerType 1 = typing or ctl-spac, 2 = tiggercharactres? 3= inside completion?
566 public override async Lsp.CompletionList? completion(JsRender.JsRender file, int line, int offset , int triggerType = 1) throws GLib.Error
568 /* partial_result_token , work_done_token context = null) */
569 GLib.debug("%s get completion %s @ %d:%d", this.get_type().name(), file.relpath, line, offset);
571 var ret = new Lsp.CompletionList();
573 if (!this.isReady()) {
574 GLib.debug("completion - language server not ready");
577 // make sure completion has the latest info..
578 //if (this.change_queue_file != null && this.change_queue_file.path != file.path) {
579 // this.document_change_real(this.change_queue_file, this.change_queue_file_source);
580 // this.change_queue_file != null;
582 this.log(LanguageClientAction.COMPLETE, "SEND complete %s @ %d:%d".printf(file.relpath, line, offset) );
584 Variant? return_value;
586 var args = this.buildDict (
587 context : this.buildDict ( ///CompletionContext;
588 triggerKind: new GLib.Variant.int32 (triggerType)
589 // triggerCharacter : new GLib.Variant.string ("")
591 textDocument : this.buildDict ( ///TextDocumentItem;
592 uri: new GLib.Variant.string (file.to_url()),
593 version : new GLib.Variant.uint64 ( (uint64) file.version)
595 position : this.buildDict (
596 line : new GLib.Variant.uint64 ( (uint) line) ,
597 character : new GLib.Variant.uint64 ( uint.max(0, (offset -1)))
601 GLib.debug ("textDocument/completion send with %s", Json.to_string (Json.gvariant_serialize (args), true));
603 yield this.jsonrpc_client.call_async (
604 "textDocument/completion",
611 //GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
612 var json = Json.gvariant_serialize (return_value);
615 if (json.get_node_type() == Json.NodeType.OBJECT) {
616 ret = Json.gobject_deserialize (typeof (Lsp.CompletionList), json) as Lsp.CompletionList;
617 this.log(LanguageClientAction.COMPLETE_REPLY, "GOT complete %d items".printf(ret.items.size) );
618 GLib.debug ("LS replied with Object");
622 if (json.get_node_type() != Json.NodeType.ARRAY) {
623 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
624 this.log(LanguageClientAction.ERROR_REPLY, "GOT something else??");
628 var ar = json.get_array();
630 for(var i = 0; i < ar.get_length(); i++ ) {
631 var add= Json.gobject_deserialize ( typeof (Lsp.CompletionItem), ar.get_element(i)) as Lsp.CompletionItem;
635 this.log(LanguageClientAction.COMPLETE_REPLY, "GOT array %d items".printf(ret.items.size) );
636 GLib.debug ("LS replied with Array");
644 static int hover_call_count = 1;
645 bool getting_hover = false;
647 //CompletionListInfo.itmems.parse_varient or CompletionListInfo.parsevarient
648 public override async Lsp.Hover hover (JsRender.JsRender file, int line, int offset) throws GLib.Error
650 /* partial_result_token , work_done_token context = null) */
651 //GLib.debug("get hover %s %d %d", file.relpath, (int)line, (int)offset);
652 var ret = new Lsp.Hover();
654 if (!this.isReady()) {
657 if (this.getting_hover) {
662 var call_id = yield this.queuer(hover_call_count);
664 //GLib.debug("end hover call=%d count=%d", call_id, hover_call_count);
665 if (call_id != hover_call_count) {
666 //GLib.debug("get hover CANCELLED %s %d %d", file.relpath, (int)line, (int)offset);
670 //GLib.debug("get hover RUN %s %d %d", file.relpath, (int)line, (int)offset);
672 this.getting_hover = true;
674 Variant? return_value;
676 yield this.jsonrpc_client.call_async (
677 "textDocument/hover",
680 textDocument : this.buildDict ( ///TextDocumentItem;
681 uri: new GLib.Variant.string (file.to_url()),
682 version : new GLib.Variant.uint64 ( (uint64) file.version)
684 position : this.buildDict (
685 line : new GLib.Variant.uint64 ( (uint) line) ,
686 character : new GLib.Variant.uint64 ( uint.max(0, (offset -1)))
693 } catch(GLib.Error e) {
694 this.getting_hover = false;
697 this.getting_hover = false;
698 GLib.debug ("LS hover replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
699 if (return_value == null) {
703 var json = Json.gvariant_serialize (return_value);
704 if (json.get_node_type() != Json.NodeType.OBJECT) {
709 ret = Json.gobject_deserialize ( typeof (Lsp.Hover), json) as Lsp.Hover;
718 static int doc_symbol_queue_call_count = 1;
722 public override void queueDocumentSymbols (JsRender.JsRender file)
725 this.documentSymbols.begin(file, (o, res) => {
726 var ret = documentSymbols.end(res);
727 file.navigation_tree_updated(ret);
733 bool getting_symbols = false;
735 public override async Gee.ArrayList<Lsp.DocumentSymbol> documentSymbols (JsRender.JsRender file) throws GLib.Error
737 /* partial_result_token , work_done_token context = null) */
738 GLib.debug("get documentSymbols %s", file.relpath);
739 var ret = new Gee.ArrayList<Lsp.DocumentSymbol>();
741 if (!this.isReady()) {
742 GLib.debug("docsymbols not ready");
745 if (this.getting_symbols) {
746 GLib.debug("docsymbols currently getting symbols");
751 doc_symbol_queue_call_count++;
752 var call_id = yield this.queuer(doc_symbol_queue_call_count);
753 if (call_id != doc_symbol_queue_call_count) {
754 GLib.debug("docsymbols call id does not match %d %d" ,call_id , doc_symbol_queue_call_count);
757 this.getting_symbols = true;
759 Variant? return_value;
761 yield this.jsonrpc_client.call_async (
762 "textDocument/documentSymbol",
765 textDocument : this.buildDict ( ///TextDocumentItem;
766 uri: new GLib.Variant.string (file.to_url()),
767 version : new GLib.Variant.uint64 ( (uint64) file.version)
775 this.getting_symbols = false;
778 this.getting_symbols = false;
780 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
781 var json = Json.gvariant_serialize (return_value);
785 var ar = json.get_array();
786 GLib.debug ("LS replied with %D items", ar.get_length());
787 for(var i = 0; i < ar.get_length(); i++ ) {
788 var add= Json.gobject_deserialize ( typeof (Lsp.DocumentSymbol), ar.get_element(i)) as Lsp.DocumentSymbol;
796 // cant seem to get this to show anything!!
797 public override async Gee.ArrayList<Lsp.SignatureInformation> signatureHelp (JsRender.JsRender file, int line, int offset) throws GLib.Error {
798 /* partial_result_token , work_done_token context = null) */
799 GLib.debug("get signatureHelp %s, %d, %d", file.relpath, line, offset);
800 var ret = new Gee.ArrayList<Lsp.SignatureInformation>();
802 if (!this.isReady()) {
805 Variant? return_value;
806 yield this.jsonrpc_client.call_async (
807 "textDocument/signatureHelp",
810 textDocument : this.buildDict ( ///TextDocumentItem;
811 uri: new GLib.Variant.string (file.to_url())
813 position : this.buildDict (
814 line : new GLib.Variant.uint64 ( (uint) line) ,
815 character : new GLib.Variant.uint64 ( uint.max(0, (offset -1)))
824 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
825 var json = Json.gvariant_serialize (return_value);
826 if (json.get_node_type() != Json.NodeType.ARRAY) {
832 var ar = json.get_array();
833 GLib.debug ("LS replied with %D items", ar.get_length());
834 for(var i = 0; i < ar.get_length(); i++ ) {
835 var add= Json.gobject_deserialize ( typeof (Lsp.SignatureInformation), ar.get_element(i)) as Lsp.SignatureInformation;
843 // ok for general symbol search, not much details though.
844 public override async Gee.ArrayList<Lsp.SymbolInformation> symbol (string sym) throws GLib.Error
846 /* partial_result_token , work_done_token context = null) */
847 GLib.debug("get symbol %s,", sym);
848 var ret = new Gee.ArrayList<Lsp.SymbolInformation>();
850 if (!this.isReady()) {
853 Variant? return_value;
854 yield this.jsonrpc_client.call_async (
857 query : new GLib.Variant.string (sym)
863 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));