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;
113 if (this.doc_queue_file == null) {
116 if (this.doc_countdown < -1) {
119 this.doc_countdown--;
121 if (this.doc_countdown < 0){
122 var sendfile = this.doc_queue_file;
123 this.documentSymbols.begin(this.doc_queue_file, (o, res) => {
124 var ret = this.documentSymbols.end(res);
125 sendfile.navigation_tree_updated(ret);
127 this.doc_queue_file = null;
135 public bool initProcess(string process_path)
138 this.log(LanguageClientAction.LAUNCH, process_path);
139 GLib.debug("Launching %s", process_path);
140 this.launcher = new GLib.SubprocessLauncher (SubprocessFlags.STDIN_PIPE | SubprocessFlags.STDOUT_PIPE);
141 var env = GLib.Environ.get();
142 env += "G_MESSAGES_DEBUG=all";
144 this.launcher.set_environ(env);
145 var logpath = GLib.Environment.get_home_dir() + "/.cache/vala-language-server";
147 if (!GLib.FileUtils.test(logpath, GLib.FileTest.IS_DIR)) {
148 Posix.mkdir(logpath, 0700);
150 // not very reliable..
151 //this.launcher.set_stderr_file_path(
153 // (new GLib.DateTime.now_local()).format("%Y-%m-%d") + ".log"
155 //GLib.debug("log lang server to %s", logpath + "/" +
156 // (new GLib.DateTime.now_local()).format("%Y-%m-%d") + ".log");
161 this.subprocess = launcher.spawnv ({ process_path , "2>" , "/tmp/vala-language-server.log" });
163 this.subprocess.wait_async.begin( null, ( obj,res ) => {
165 this.subprocess.wait_async.end(res);
166 } catch (GLib.Error e) {
167 this.log(LanguageClientAction.ERROR_START, e.message);
168 GLib.debug("subprocess startup error %s", e.message);
170 this.log(LanguageClientAction.EXIT, "process ended");
171 GLib.debug("Subprocess ended %s", process_path);
175 var input_stream = this.subprocess.get_stdout_pipe ();
176 var output_stream = this.subprocess.get_stdin_pipe ();
178 if (input_stream is GLib.UnixInputStream && output_stream is GLib.UnixOutputStream) {
180 if (!GLib.Unix.set_fd_nonblocking(((GLib.UnixInputStream)input_stream).fd, true)
181 || !GLib.Unix.set_fd_nonblocking (((GLib.UnixOutputStream)output_stream).fd, true))
183 GLib.debug("could not set pipes to nonblocking");
188 this.subprocess_stream = new GLib.SimpleIOStream (input_stream, output_stream);
189 this.accept_io_stream ( this.subprocess_stream);
190 } catch (GLib.Error e) {
191 this.log(LanguageClientAction.ERROR_START, e.message);
192 GLib.debug("subprocess startup error %s", e.message);
198 bool in_close = false;
199 public override void client_accepted (Jsonrpc.Client client)
201 if (this.jsonrpc_client == null) {
202 this.jsonrpc_client = client;
204 GLib.debug("client accepted connection - calling init server");
205 this.log(LanguageClientAction.ACCEPT, "client accepted");
207 this.jsonrpc_client.notification.connect((method, paramz) => {
208 this.onNotification(method, paramz);
211 this.jsonrpc_client.failed.connect(() => {
212 this.log(LanguageClientAction.ERROR_RPC, "client failed");
213 GLib.debug("language server server has failed");
219 this.initialize_server ();
226 public override void initialize_server() {
228 Variant? return_value;
229 this.jsonrpc_client.call (
232 processId: new Variant.int32 ((int32) Posix.getpid ()),
233 rootPath: new Variant.string (this.project.path),
234 rootUri: new Variant.string (File.new_for_path (this.project.path).get_uri ()),
235 capabilities : this.buildDict (
236 textDocument: this.buildDict (
237 documentSymbol : this.buildDict (
238 hierarchicalDocumentSymbolSupport : new Variant.boolean (true)
246 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
247 this.open_files = new Gee.ArrayList<JsRender.JsRender>((a,b) => {
248 return a.path == b.path;
250 this.initialized = true;
251 this.getting_diagnostics = false;
253 } catch (GLib.Error e) {
254 GLib.debug ("LS replied with error %s", e.message);
264 if (this.launcher == null) {
267 this.getting_diagnostics = false;
268 this.in_close = true;
269 GLib.debug("onClose called");
271 if (this.jsonrpc_client != null) {
273 this.jsonrpc_client.close();
274 } catch (GLib.Error e) {
275 GLib.debug("rpc Error close error %s", e.message);
278 if (this.subprocess_stream != null) {
280 this.subprocess_stream.close();
281 } catch (GLib.Error e) {
282 GLib.debug("stream Error close %s", e.message);
285 if (this.subprocess != null) {
286 this.subprocess.force_exit();
288 if (this.launcher != null) {
289 this.launcher.close();
292 this.launcher = null;
293 this.subprocess = null;
294 this.jsonrpc_client = null;
296 this.in_close = false;
299 public async void restartServer()
305 public bool isReady()
308 this.log(LanguageClientAction.RESTART,"closed is set - restarting");
309 GLib.debug("server stopped = restarting");
310 this.initialized = false;
312 GLib.MainLoop loop = new GLib.MainLoop ();
313 this.restartServer.begin ((obj, async_res) => {
314 this.restartServer.end(async_res);
317 return false; // can't do an operation yet?
321 if (!this.initialized) {
322 GLib.debug("Server has not been initialized");
325 if (this.sent_shutdown) {
326 GLib.debug("Server has been started its shutting down process");
336 public void onNotification(string method, Variant? return_value)
339 case "textDocument/publishDiagnostics":
340 //GLib.debug("got notification %s : %s", method , Json.to_string (Json.gvariant_serialize (return_value), true));
342 GLib.Idle.add(() => {
343 this.onDiagnostic(return_value);
351 GLib.debug("got notification %s : %s", method , Json.to_string (Json.gvariant_serialize (return_value), true));
355 bool getting_diagnostics = false;
359 public void onDiagnostic(Variant? return_value)
361 //GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
362 var dg = Json.gobject_deserialize (typeof (Lsp.Diagnostics), Json.gvariant_serialize (return_value)) as Lsp.Diagnostics;
363 GLib.debug("got diag for %s", dg.filename);
364 this.log(LanguageClientAction.DIAG, dg.filename);
365 if (this.project.path == dg.filename) {
366 this.getting_diagnostics = false;
367 this.log(LanguageClientAction.DIAG_END, "diagnostics done");
371 this.getting_diagnostics =true;
372 var f = this.project.getByPath(dg.filename);
374 //GLib.debug("no file %s", dg.uri);
375 //this.project.updateErrorsforFile(null);
378 //GLib.debug("got Diagnostics for %s", f.path);
379 f.updateErrors( dg.diagnostics );
384 public override void document_open (JsRender.JsRender file)
386 if (!this.isReady()) {
389 if (this.open_files.contains(file)) {
392 this.open_files.add(file);
395 GLib.debug ("LS sent open");
397 this.jsonrpc_client.send_notification (
398 "textDocument/didOpen",
400 textDocument : this.buildDict (
401 uri: new Variant.string (file.to_url()),
402 languageId : new Variant.string (file.language_id()),
403 version : new GLib.Variant.uint64 ( (uint64) file.version),
404 text : new Variant.string (file.toSource())
409 this.log(LanguageClientAction.OPEN, file.path);
410 } catch( GLib.Error e) {
411 this.log(LanguageClientAction.ERROR_RPC, e.message);
413 GLib.debug ("LS sent open err %s", e.message);
418 public override async void document_save (JsRender.JsRender file)
420 if (!this.isReady()) {
423 // save only really flags the file on the server - to actually force a change update - we need to
424 // flag it as changed.
425 yield this.document_change_force(file, file.toSource());
427 this.change_queue_file = null;
428 GLib.debug ("LS send save");
431 var args = this.buildDict (
432 textDocument : this.buildDict ( ///TextDocumentItem;
433 uri: new GLib.Variant.string (file.to_url()),
434 version : new GLib.Variant.uint64 ( (uint64) file.version)
438 //GLib.debug ("textDocument/save send with %s", Json.to_string (Json.gvariant_serialize (args), true));
442 yield this.jsonrpc_client.send_notification_async (
443 "textDocument/didSave",
447 this.log(LanguageClientAction.SAVE, file.path);
448 } catch( GLib.Error e) {
449 this.log(LanguageClientAction.ERROR_RPC, e.message);
450 GLib.debug ("LS save err %s", e.message);
456 public override void document_close (JsRender.JsRender file)
458 if (!this.isReady()) {
461 this.change_queue_file = null;
463 if (this.open_files.contains(file)) {
464 this.open_files.remove(file);
466 this.log(LanguageClientAction.CLOSE, file.path);
467 GLib.debug ("LS send close");
469 this.jsonrpc_client.send_notification (
470 "textDocument/didChange",
472 textDocument : this.buildDict ( ///TextDocumentItem;
473 uri: new GLib.Variant.string (file.to_url())
479 } catch( GLib.Error e) {
480 this.log(LanguageClientAction.ERROR_RPC, e.message);
481 GLib.debug ("LS close err %s", e.message);
489 public override void document_change (JsRender.JsRender file )
491 if (this.change_queue_file != null && this.change_queue_file.path != file.path) {
492 this.document_change_force.begin(this.change_queue_file, this.change_queue_file_source, (o, res) => {
493 this.document_change_force.end(res);
498 this.change_queue_file = file;
505 public override async void document_change_force (JsRender.JsRender file, string contents)
509 if (!this.isReady()) {
512 this.countdown = -2; // not really relivant..
513 this.change_queue_file = null; // this is more important..
515 if (!this.open_files.contains(file)) {
516 this.document_open(file);
519 GLib.debug ("LS send change %s rev %d", file.path, file.version);
520 var ar = new Json.Array();
521 var obj = new Json.Object();
522 obj.set_string_member("text", contents);
523 ar.add_object_element(obj);
524 var node = new Json.Node(Json.NodeType.ARRAY);
526 this.log(LanguageClientAction.CHANGE, file.path);
528 yield this.jsonrpc_client.send_notification_async (
529 "textDocument/didChange",
531 textDocument : this.buildDict ( ///TextDocumentItem;
532 uri: new GLib.Variant.string (file.to_url()),
533 version : new GLib.Variant.uint64 ( (uint64) file.version)
535 contentChanges : Json.gvariant_deserialize (node, null)
540 } catch( GLib.Error e) {
541 this.log(LanguageClientAction.ERROR_RPC, e.message);
542 GLib.debug ("LS change err %s", e.message);
548 // called by close window (on last window)...
549 public override void exit () throws GLib.Error
551 if (!this.isReady()) {
555 this.log(LanguageClientAction.TERM, "SEND exit");
557 this.jsonrpc_client.send_notification (
565 // not used currently..
566 public override async void shutdown () throws GLib.Error
568 if (!this.isReady()) {
571 this.log(LanguageClientAction.TERM, "SEND shutodwn");
572 this.sent_shutdown = true;
573 Variant? return_value;
574 yield this.jsonrpc_client.call_async (
580 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
582 //public async ??/symbol (string symbol) throws GLib.Error {
584 // and now for the important styff..
588 @triggerType 1 = typing or ctl-spac, 2 = tiggercharactres? 3= inside completion?
590 public override async Lsp.CompletionList? completion(JsRender.JsRender file, int line, int offset , int triggerType = 1) throws GLib.Error
592 /* partial_result_token , work_done_token context = null) */
593 GLib.debug("%s get completion %s @ %d:%d", this.get_type().name(), file.relpath, line, offset);
595 var ret = new Lsp.CompletionList();
597 if (!this.isReady()) {
598 GLib.debug("completion - language server not ready");
601 // make sure completion has the latest info..
602 //if (this.change_queue_file != null && this.change_queue_file.path != file.path) {
603 // this.document_change_real(this.change_queue_file, this.change_queue_file_source);
604 // this.change_queue_file != null;
606 this.log(LanguageClientAction.COMPLETE, "SEND complete %s @ %d:%d".printf(file.relpath, line, offset) );
608 Variant? return_value;
610 var args = this.buildDict (
611 context : this.buildDict ( ///CompletionContext;
612 triggerKind: new GLib.Variant.int32 (triggerType)
613 // triggerCharacter : new GLib.Variant.string ("")
615 textDocument : this.buildDict ( ///TextDocumentItem;
616 uri: new GLib.Variant.string (file.to_url()),
617 version : new GLib.Variant.uint64 ( (uint64) file.version)
619 position : this.buildDict (
620 line : new GLib.Variant.uint64 ( (uint) line) ,
621 character : new GLib.Variant.uint64 ( uint.max(0, (offset -1)))
625 GLib.debug ("textDocument/completion send with %s", Json.to_string (Json.gvariant_serialize (args), true));
627 yield this.jsonrpc_client.call_async (
628 "textDocument/completion",
635 //GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
636 var json = Json.gvariant_serialize (return_value);
639 if (json.get_node_type() == Json.NodeType.OBJECT) {
640 ret = Json.gobject_deserialize (typeof (Lsp.CompletionList), json) as Lsp.CompletionList;
641 this.log(LanguageClientAction.COMPLETE_REPLY, "GOT complete %d items".printf(ret.items.size) );
642 GLib.debug ("LS replied with Object");
646 if (json.get_node_type() != Json.NodeType.ARRAY) {
647 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
648 this.log(LanguageClientAction.ERROR_REPLY, "GOT something else??");
652 var ar = json.get_array();
654 for(var i = 0; i < ar.get_length(); i++ ) {
655 var add= Json.gobject_deserialize ( typeof (Lsp.CompletionItem), ar.get_element(i)) as Lsp.CompletionItem;
659 this.log(LanguageClientAction.COMPLETE_REPLY, "GOT array %d items".printf(ret.items.size) );
660 GLib.debug ("LS replied with Array");
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()) {
682 var call_id = yield this.queuer(hover_call_count);
684 //GLib.debug("end hover call=%d count=%d", call_id, hover_call_count);
685 if (call_id != hover_call_count) {
686 //GLib.debug("get hover CANCELLED %s %d %d", file.relpath, (int)line, (int)offset);
690 //GLib.debug("get hover RUN %s %d %d", file.relpath, (int)line, (int)offset);
694 Variant? return_value;
695 yield this.jsonrpc_client.call_async (
696 "textDocument/hover",
699 textDocument : this.buildDict ( ///TextDocumentItem;
700 uri: new GLib.Variant.string (file.to_url()),
701 version : new GLib.Variant.uint64 ( (uint64) file.version)
703 position : this.buildDict (
704 line : new GLib.Variant.uint64 ( (uint) line) ,
705 character : new GLib.Variant.uint64 ( uint.max(0, (offset -1)))
712 GLib.debug ("LS hover replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
713 if (return_value == null) {
717 var json = Json.gvariant_serialize (return_value);
718 if (json.get_node_type() != Json.NodeType.OBJECT) {
723 ret = Json.gobject_deserialize ( typeof (Lsp.Hover), json) as Lsp.Hover;
732 static int doc_symbol_queue_call_count = 1;
736 public override void queueDocumentSymbols (JsRender.JsRender file)
740 doc_symbol_queue_call_count++;
741 var call_id = yield this.queuer(doc_symbol_queue_call_count);
742 if (call_id != doc_symbol_queue_call_count) {
745 this.documentSymbols.begin(file, (o, res) => {
746 var ret = documentSymbols.end(res);
747 file.navigation_tree_updated(ret);
755 public override async Gee.ArrayList<Lsp.DocumentSymbol> documentSymbols (JsRender.JsRender file) throws GLib.Error
757 /* partial_result_token , work_done_token context = null) */
758 GLib.debug("get documentSymbols %s", file.relpath);
759 var ret = new Gee.ArrayList<Lsp.DocumentSymbol>();
761 if (!this.isReady()) {
764 Variant? return_value;
765 yield this.jsonrpc_client.call_async (
766 "textDocument/documentSymbol",
769 textDocument : this.buildDict ( ///TextDocumentItem;
770 uri: new GLib.Variant.string (file.to_url()),
771 version : new GLib.Variant.uint64 ( (uint64) file.version)
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));