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();
71 void run_change_queue()
74 if (this.change_queue_file == null) {
77 if (this.countdown < -1) {
80 if (this.getting_diagnostics) {
86 if (this.countdown < 0){
87 this.document_change_force.begin(this.change_queue_file, this.change_queue_file_source, (o, res) => {
88 this.document_change_force.end(res);
90 this.change_queue_file = null;
100 if (this.doc_queue_file == null) {
103 if (this.doc_countdown < -1) {
106 this.doc_countdown--;
108 if (this.doc_countdown < 0){
109 var sendfile = this.doc_queue_file;
110 this.documentSymbols.begin(this.doc_queue_file, (o, res) => {
111 var ret = this.documentSymbols.end(res);
112 sendfile.navigation_tree_updated(ret);
114 this.doc_queue_file = null;
122 public bool initProcess(string process_path)
125 this.log(LanguageClientAction.LAUNCH, process_path);
126 GLib.debug("Launching %s", process_path);
127 this.launcher = new GLib.SubprocessLauncher (SubprocessFlags.STDIN_PIPE | SubprocessFlags.STDOUT_PIPE);
128 var env = GLib.Environ.get();
129 env += "G_MESSAGES_DEBUG=all";
131 this.launcher.set_environ(env);
132 var logpath = GLib.Environment.get_home_dir() + "/.cache/vala-language-server";
134 if (!GLib.FileUtils.test(logpath, GLib.FileTest.IS_DIR)) {
135 Posix.mkdir(logpath, 0700);
137 // not very reliable..
138 //this.launcher.set_stderr_file_path(
140 // (new GLib.DateTime.now_local()).format("%Y-%m-%d") + ".log"
142 //GLib.debug("log lang server to %s", logpath + "/" +
143 // (new GLib.DateTime.now_local()).format("%Y-%m-%d") + ".log");
148 this.subprocess = launcher.spawnv ({ process_path , "2>" , "/tmp/vala-language-server.log" });
150 this.subprocess.wait_async.begin( null, ( obj,res ) => {
152 this.subprocess.wait_async.end(res);
153 } catch (GLib.Error e) {
154 this.log(LanguageClientAction.ERROR_START, e.message);
155 GLib.debug("subprocess startup error %s", e.message);
157 this.log(LanguageClientAction.EXIT, "process ended");
158 GLib.debug("Subprocess ended %s", process_path);
162 var input_stream = this.subprocess.get_stdout_pipe ();
163 var output_stream = this.subprocess.get_stdin_pipe ();
165 if (input_stream is GLib.UnixInputStream && output_stream is GLib.UnixOutputStream) {
167 if (!GLib.Unix.set_fd_nonblocking(((GLib.UnixInputStream)input_stream).fd, true)
168 || !GLib.Unix.set_fd_nonblocking (((GLib.UnixOutputStream)output_stream).fd, true))
170 GLib.debug("could not set pipes to nonblocking");
175 this.subprocess_stream = new GLib.SimpleIOStream (input_stream, output_stream);
176 this.accept_io_stream ( this.subprocess_stream);
177 } catch (GLib.Error e) {
178 this.log(LanguageClientAction.ERROR_START, e.message);
179 GLib.debug("subprocess startup error %s", e.message);
185 bool in_close = false;
186 public override void client_accepted (Jsonrpc.Client client)
188 if (this.jsonrpc_client == null) {
189 this.jsonrpc_client = client;
191 GLib.debug("client accepted connection - calling init server");
192 this.log(LanguageClientAction.ACCEPT, "client accepted");
194 this.jsonrpc_client.notification.connect((method, paramz) => {
195 this.onNotification(method, paramz);
198 this.jsonrpc_client.failed.connect(() => {
199 this.log(LanguageClientAction.ERROR_RPC, "client failed");
200 GLib.debug("language server server has failed");
206 this.initialize_server ();
213 public override void initialize_server() {
215 Variant? return_value;
216 this.jsonrpc_client.call (
219 processId: new Variant.int32 ((int32) Posix.getpid ()),
220 rootPath: new Variant.string (this.project.path),
221 rootUri: new Variant.string (File.new_for_path (this.project.path).get_uri ()),
222 capabilities : this.buildDict (
223 textDocument: this.buildDict (
224 documentSymbol : this.buildDict (
225 hierarchicalDocumentSymbolSupport : new Variant.boolean (true)
233 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
234 this.open_files = new Gee.ArrayList<JsRender.JsRender>((a,b) => {
235 return a.path == b.path;
237 this.initialized = true;
238 this.getting_diagnostics = false;
240 } catch (GLib.Error e) {
241 GLib.debug ("LS replied with error %s", e.message);
251 if (this.launcher == null) {
254 this.getting_diagnostics = false;
255 this.in_close = true;
256 GLib.debug("onClose called");
258 if (this.jsonrpc_client != null) {
260 this.jsonrpc_client.close();
261 } catch (GLib.Error e) {
262 GLib.debug("rpc Error close error %s", e.message);
265 if (this.subprocess_stream != null) {
267 this.subprocess_stream.close();
268 } catch (GLib.Error e) {
269 GLib.debug("stream Error close %s", e.message);
272 if (this.subprocess != null) {
273 this.subprocess.force_exit();
275 if (this.launcher != null) {
276 this.launcher.close();
279 this.launcher = null;
280 this.subprocess = null;
281 this.jsonrpc_client = null;
283 this.in_close = false;
286 public async void restartServer()
292 public bool isReady()
295 this.log(LanguageClientAction.RESTART,"closed is set - restarting");
296 GLib.debug("server stopped = restarting");
297 this.initialized = false;
299 GLib.MainLoop loop = new GLib.MainLoop ();
300 this.restartServer.begin ((obj, async_res) => {
301 this.restartServer.end(async_res);
304 return false; // can't do an operation yet?
308 if (!this.initialized) {
309 GLib.debug("Server has not been initialized");
312 if (this.sent_shutdown) {
313 GLib.debug("Server has been started its shutting down process");
323 public void onNotification(string method, Variant? return_value)
326 case "textDocument/publishDiagnostics":
327 //GLib.debug("got notification %s : %s", method , Json.to_string (Json.gvariant_serialize (return_value), true));
329 GLib.Idle.add(() => {
330 this.onDiagnostic(return_value);
338 GLib.debug("got notification %s : %s", method , Json.to_string (Json.gvariant_serialize (return_value), true));
342 bool getting_diagnostics = false;
346 public void onDiagnostic(Variant? return_value)
348 //GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
349 var dg = Json.gobject_deserialize (typeof (Lsp.Diagnostics), Json.gvariant_serialize (return_value)) as Lsp.Diagnostics;
350 GLib.debug("got diag for %s", dg.filename);
351 this.log(LanguageClientAction.DIAG, dg.filename);
352 if (this.project.path == dg.filename) {
353 this.getting_diagnostics = false;
354 this.log(LanguageClientAction.DIAG_END, "diagnostics done");
358 this.getting_diagnostics =true;
359 var f = this.project.getByPath(dg.filename);
361 //GLib.debug("no file %s", dg.uri);
362 //this.project.updateErrorsforFile(null);
365 //GLib.debug("got Diagnostics for %s", f.path);
366 f.updateErrors( dg.diagnostics );
371 public override void document_open (JsRender.JsRender file)
373 if (!this.isReady()) {
376 if (this.open_files.contains(file)) {
379 this.open_files.add(file);
382 GLib.debug ("LS sent open");
384 this.jsonrpc_client.send_notification (
385 "textDocument/didOpen",
387 textDocument : this.buildDict (
388 uri: new Variant.string (file.to_url()),
389 languageId : new Variant.string (file.language_id()),
390 version : new GLib.Variant.uint64 ( (uint64) file.version),
391 text : new Variant.string (file.toSource())
396 this.log(LanguageClientAction.OPEN, file.path);
397 } catch( GLib.Error e) {
398 this.log(LanguageClientAction.ERROR_RPC, e.message);
400 GLib.debug ("LS sent open err %s", e.message);
405 public override async void document_save (JsRender.JsRender file)
407 if (!this.isReady()) {
410 // save only really flags the file on the server - to actually force a change update - we need to
411 // flag it as changed.
412 yield this.document_change_force(file, file.toSource());
414 this.change_queue_file = null;
415 GLib.debug ("LS send save");
418 var args = this.buildDict (
419 textDocument : this.buildDict ( ///TextDocumentItem;
420 uri: new GLib.Variant.string (file.to_url()),
421 version : new GLib.Variant.uint64 ( (uint64) file.version)
425 //GLib.debug ("textDocument/save send with %s", Json.to_string (Json.gvariant_serialize (args), true));
429 yield this.jsonrpc_client.send_notification_async (
430 "textDocument/didSave",
434 this.log(LanguageClientAction.SAVE, file.path);
435 } catch( GLib.Error e) {
436 this.log(LanguageClientAction.ERROR_RPC, e.message);
437 GLib.debug ("LS save err %s", e.message);
443 public override void document_close (JsRender.JsRender file)
445 if (!this.isReady()) {
448 this.change_queue_file = null;
450 if (this.open_files.contains(file)) {
451 this.open_files.remove(file);
453 this.log(LanguageClientAction.CLOSE, file.path);
454 GLib.debug ("LS send close");
456 this.jsonrpc_client.send_notification (
457 "textDocument/didChange",
459 textDocument : this.buildDict ( ///TextDocumentItem;
460 uri: new GLib.Variant.string (file.to_url())
466 } catch( GLib.Error e) {
467 this.log(LanguageClientAction.ERROR_RPC, e.message);
468 GLib.debug ("LS close err %s", e.message);
476 public override void document_change (JsRender.JsRender file )
478 if (this.change_queue_file != null && this.change_queue_file.path != file.path) {
479 this.document_change_force.begin(this.change_queue_file, this.change_queue_file_source, (o, res) => {
480 this.document_change_force.end(res);
485 this.change_queue_file = file;
492 public override async void document_change_force (JsRender.JsRender file, string contents)
496 if (!this.isReady()) {
499 this.countdown = -2; // not really relivant..
500 this.change_queue_file = null; // this is more important..
502 if (!this.open_files.contains(file)) {
503 this.document_open(file);
506 GLib.debug ("LS send change %s rev %d", file.path, file.version);
507 var ar = new Json.Array();
508 var obj = new Json.Object();
509 obj.set_string_member("text", contents);
510 ar.add_object_element(obj);
511 var node = new Json.Node(Json.NodeType.ARRAY);
513 this.log(LanguageClientAction.CHANGE, file.path);
515 yield this.jsonrpc_client.send_notification_async (
516 "textDocument/didChange",
518 textDocument : this.buildDict ( ///TextDocumentItem;
519 uri: new GLib.Variant.string (file.to_url()),
520 version : new GLib.Variant.uint64 ( (uint64) file.version)
522 contentChanges : Json.gvariant_deserialize (node, null)
527 } catch( GLib.Error e) {
528 this.log(LanguageClientAction.ERROR_RPC, e.message);
529 GLib.debug ("LS change err %s", e.message);
535 // called by close window (on last window)...
536 public override void exit () throws GLib.Error
538 if (!this.isReady()) {
542 this.log(LanguageClientAction.TERM, "SEND exit");
544 this.jsonrpc_client.send_notification (
552 // not used currently..
553 public override async void shutdown () throws GLib.Error
555 if (!this.isReady()) {
558 this.log(LanguageClientAction.TERM, "SEND shutodwn");
559 this.sent_shutdown = true;
560 Variant? return_value;
561 yield this.jsonrpc_client.call_async (
567 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
569 //public async ??/symbol (string symbol) throws GLib.Error {
571 // and now for the important styff..
575 @triggerType 1 = typing or ctl-spac, 2 = tiggercharactres? 3= inside completion?
577 public override async Lsp.CompletionList? completion(JsRender.JsRender file, int line, int offset , int triggerType = 1) throws GLib.Error
579 /* partial_result_token , work_done_token context = null) */
580 GLib.debug("%s get completion %s @ %d:%d", this.get_type().name(), file.relpath, line, offset);
582 var ret = new Lsp.CompletionList();
584 if (!this.isReady()) {
585 GLib.debug("completion - language server not ready");
588 // make sure completion has the latest info..
589 //if (this.change_queue_file != null && this.change_queue_file.path != file.path) {
590 // this.document_change_real(this.change_queue_file, this.change_queue_file_source);
591 // this.change_queue_file != null;
593 this.log(LanguageClientAction.COMPLETE, "SEND complete %s @ %d:%d".printf(file.relpath, line, offset) );
595 Variant? return_value;
597 var args = this.buildDict (
598 context : this.buildDict ( ///CompletionContext;
599 triggerKind: new GLib.Variant.int32 (triggerType)
600 // triggerCharacter : new GLib.Variant.string ("")
602 textDocument : this.buildDict ( ///TextDocumentItem;
603 uri: new GLib.Variant.string (file.to_url()),
604 version : new GLib.Variant.uint64 ( (uint64) file.version)
606 position : this.buildDict (
607 line : new GLib.Variant.uint64 ( (uint) line) ,
608 character : new GLib.Variant.uint64 ( uint.max(0, (offset -1)))
612 GLib.debug ("textDocument/completion send with %s", Json.to_string (Json.gvariant_serialize (args), true));
614 yield this.jsonrpc_client.call_async (
615 "textDocument/completion",
622 //GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
623 var json = Json.gvariant_serialize (return_value);
626 if (json.get_node_type() == Json.NodeType.OBJECT) {
627 ret = Json.gobject_deserialize (typeof (Lsp.CompletionList), json) as Lsp.CompletionList;
628 this.log(LanguageClientAction.COMPLETE_REPLY, "GOT complete %d items".printf(ret.items.size) );
629 GLib.debug ("LS replied with Object");
633 if (json.get_node_type() != Json.NodeType.ARRAY) {
634 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
635 this.log(LanguageClientAction.ERROR_REPLY, "GOT something else??");
639 var ar = json.get_array();
641 for(var i = 0; i < ar.get_length(); i++ ) {
642 var add= Json.gobject_deserialize ( typeof (Lsp.CompletionItem), ar.get_element(i)) as Lsp.CompletionItem;
646 this.log(LanguageClientAction.COMPLETE_REPLY, "GOT array %d items".printf(ret.items.size) );
647 GLib.debug ("LS replied with Array");
653 async int hover_queue()
655 SourceFunc cb = this.hover_queue.callback;
657 var call_id = hover_call_count;
659 GLib.Timeout.add(500, () => {
668 static int hover_call_count = 1;
671 //CompletionListInfo.itmems.parse_varient or CompletionListInfo.parsevarient
672 public override async Lsp.Hover hover (JsRender.JsRender file, int line, int offset) throws GLib.Error
674 /* partial_result_token , work_done_token context = null) */
675 //GLib.debug("get hover %s %d %d", file.relpath, (int)line, (int)offset);
676 var ret = new Lsp.Hover();
678 if (!this.isReady()) {
681 var call_id = yield this.hover_queue();
683 //GLib.debug("end hover call=%d count=%d", call_id, hover_call_count);
684 if (call_id != hover_call_count) {
685 //GLib.debug("get hover CANCELLED %s %d %d", file.relpath, (int)line, (int)offset);
689 //GLib.debug("get hover RUN %s %d %d", file.relpath, (int)line, (int)offset);
693 Variant? return_value;
694 yield this.jsonrpc_client.call_async (
695 "textDocument/hover",
698 textDocument : this.buildDict ( ///TextDocumentItem;
699 uri: new GLib.Variant.string (file.to_url()),
700 version : new GLib.Variant.uint64 ( (uint64) file.version)
702 position : this.buildDict (
703 line : new GLib.Variant.uint64 ( (uint) line) ,
704 character : new GLib.Variant.uint64 ( uint.max(0, (offset -1)))
711 //GLib.debug ("LS hover replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
712 if (return_value == null) {
716 var json = Json.gvariant_serialize (return_value);
717 if (json.get_node_type() != Json.NodeType.OBJECT) {
722 ret = Json.gobject_deserialize ( typeof (Lsp.Hover), json) as Lsp.Hover;
730 public override void queueDocumentSymbols (JsRender.JsRender file)
732 if (this.doc_queue_file != null && this.doc_queue_file.path != file.path) {
733 var sendfile = this.doc_queue_file;
734 this.documentSymbols.begin(this.doc_queue_file, (o, res) => {
735 var ret = documentSymbols.end(res);
736 sendfile.navigation_tree_updated(ret);
740 this.doc_countdown = 2;
741 this.doc_queue_file = file;
745 public override async Gee.ArrayList<Lsp.DocumentSymbol> documentSymbols (JsRender.JsRender file) throws GLib.Error {
746 /* partial_result_token , work_done_token context = null) */
747 GLib.debug("get documentSymbols %s", file.relpath);
748 var ret = new Gee.ArrayList<Lsp.DocumentSymbol>();
750 if (!this.isReady()) {
753 Variant? return_value;
754 yield this.jsonrpc_client.call_async (
755 "textDocument/documentSymbol",
758 textDocument : this.buildDict ( ///TextDocumentItem;
759 uri: new GLib.Variant.string (file.to_url()),
760 version : new GLib.Variant.uint64 ( (uint64) file.version)
769 //GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
770 var json = Json.gvariant_serialize (return_value);
774 var ar = json.get_array();
775 GLib.debug ("LS replied with %D items", ar.get_length());
776 for(var i = 0; i < ar.get_length(); i++ ) {
777 var add= Json.gobject_deserialize ( typeof (Lsp.DocumentSymbol), ar.get_element(i)) as Lsp.DocumentSymbol;
785 public override async Gee.ArrayList<Lsp.SignatureInformation> signatureHelp (JsRender.JsRender file, int line, int offset) throws GLib.Error {
786 /* partial_result_token , work_done_token context = null) */
787 GLib.debug("get signatureHelp %s, %d, %d", file.relpath, line, offset);
788 var ret = new Gee.ArrayList<Lsp.SignatureInformation>();
790 if (!this.isReady()) {
793 Variant? return_value;
794 yield this.jsonrpc_client.call_async (
795 "textDocument/signatureHelp",
798 textDocument : this.buildDict ( ///TextDocumentItem;
799 uri: new GLib.Variant.string (file.to_url())
801 position : this.buildDict (
802 line : new GLib.Variant.uint64 ( (uint) line) ,
803 character : new GLib.Variant.uint64 ( uint.max(0, (offset -1)))
812 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
813 var json = Json.gvariant_serialize (return_value);
814 if (json.get_node_type() != Json.NodeType.ARRAY) {
820 var ar = json.get_array();
821 GLib.debug ("LS replied with %D items", ar.get_length());
822 for(var i = 0; i < ar.get_length(); i++ ) {
823 var add= Json.gobject_deserialize ( typeof (Lsp.SignatureInformation), ar.get_element(i)) as Lsp.SignatureInformation;