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_seconds(1, () => {
61 this.run_change_queue()
71 void run_change_queue()
74 if (this.change_queue_file == null) {
77 if (this.getting_diagnostics) {
83 if (this.countdown < 0){
84 this.document_change_force.begin(this.change_queue_file, this.change_queue_file_source, (o, res) => {
85 this.document_change_force.end(res);
87 this.change_queue_file = null;
97 if (this.doc_queue_file == null) {
101 this.doc_countdown--;
104 if (this.doc_countdown < 0){
105 var sendfile = this.doc_queue_file;
106 this.documentSymbols.begin(this.doc_queue_file (o, res) => {
107 var ret = documentSymbols.end(res);
108 //sendfile.navigation_tree_updated(res);
110 this.doc_queue_file = null;
115 public bool initProcess(string process_path)
118 this.log(LanguageClientAction.LAUNCH, process_path);
119 GLib.debug("Launching %s", process_path);
120 this.launcher = new GLib.SubprocessLauncher (SubprocessFlags.STDIN_PIPE | SubprocessFlags.STDOUT_PIPE);
121 var env = GLib.Environ.get();
122 env += "G_MESSAGES_DEBUG=all";
124 this.launcher.set_environ(env);
125 var logpath = GLib.Environment.get_home_dir() + "/.cache/vala-language-server";
127 if (!GLib.FileUtils.test(logpath, GLib.FileTest.IS_DIR)) {
128 Posix.mkdir(logpath, 0700);
130 // not very reliable..
131 //this.launcher.set_stderr_file_path(
133 // (new GLib.DateTime.now_local()).format("%Y-%m-%d") + ".log"
135 //GLib.debug("log lang server to %s", logpath + "/" +
136 // (new GLib.DateTime.now_local()).format("%Y-%m-%d") + ".log");
141 this.subprocess = launcher.spawnv ({ process_path , "2>" , "/tmp/vala-language-server.log" });
143 this.subprocess.wait_async.begin( null, ( obj,res ) => {
145 this.subprocess.wait_async.end(res);
146 } catch (GLib.Error e) {
147 this.log(LanguageClientAction.ERROR_START, e.message);
148 GLib.debug("subprocess startup error %s", e.message);
150 this.log(LanguageClientAction.EXIT, "process ended");
151 GLib.debug("Subprocess ended %s", process_path);
155 var input_stream = this.subprocess.get_stdout_pipe ();
156 var output_stream = this.subprocess.get_stdin_pipe ();
158 if (input_stream is GLib.UnixInputStream && output_stream is GLib.UnixOutputStream) {
160 if (!GLib.Unix.set_fd_nonblocking(((GLib.UnixInputStream)input_stream).fd, true)
161 || !GLib.Unix.set_fd_nonblocking (((GLib.UnixOutputStream)output_stream).fd, true))
163 GLib.debug("could not set pipes to nonblocking");
168 this.subprocess_stream = new GLib.SimpleIOStream (input_stream, output_stream);
169 this.accept_io_stream ( this.subprocess_stream);
170 } catch (GLib.Error e) {
171 this.log(LanguageClientAction.ERROR_START, e.message);
172 GLib.debug("subprocess startup error %s", e.message);
178 bool in_close = false;
179 public override void client_accepted (Jsonrpc.Client client)
181 if (this.jsonrpc_client == null) {
182 this.jsonrpc_client = client;
184 GLib.debug("client accepted connection - calling init server");
185 this.log(LanguageClientAction.ACCEPT, "client accepted");
187 this.jsonrpc_client.notification.connect((method, paramz) => {
188 this.onNotification(method, paramz);
191 this.jsonrpc_client.failed.connect(() => {
192 this.log(LanguageClientAction.ERROR_RPC, "client failed");
193 GLib.debug("language server server has failed");
199 this.initialize_server ();
206 public override void initialize_server() {
208 Variant? return_value;
209 this.jsonrpc_client.call (
212 processId: new Variant.int32 ((int32) Posix.getpid ()),
213 rootPath: new Variant.string (this.project.path),
214 rootUri: new Variant.string (File.new_for_path (this.project.path).get_uri ()),
215 capabilities : this.buildDict (
216 textDocument: this.buildDict (
217 documentSymbol : this.buildDict (
218 hierarchicalDocumentSymbolSupport : new Variant.boolean (true)
226 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
227 this.open_files = new Gee.ArrayList<JsRender.JsRender>((a,b) => {
228 return a.path == b.path;
230 this.initialized = true;
231 this.getting_diagnostics = false;
233 } catch (GLib.Error e) {
234 GLib.debug ("LS replied with error %s", e.message);
244 if (this.launcher == null) {
247 this.getting_diagnostics = false;
248 this.in_close = true;
249 GLib.debug("onClose called");
251 if (this.jsonrpc_client != null) {
253 this.jsonrpc_client.close();
254 } catch (GLib.Error e) {
255 GLib.debug("rpc Error close error %s", e.message);
258 if (this.subprocess_stream != null) {
260 this.subprocess_stream.close();
261 } catch (GLib.Error e) {
262 GLib.debug("stream Error close %s", e.message);
265 if (this.subprocess != null) {
266 this.subprocess.force_exit();
268 if (this.launcher != null) {
269 this.launcher.close();
272 this.launcher = null;
273 this.subprocess = null;
274 this.jsonrpc_client = null;
276 this.in_close = false;
279 public async void restartServer()
285 public bool isReady()
288 this.log(LanguageClientAction.RESTART,"closed is set - restarting");
289 GLib.debug("server stopped = restarting");
290 this.initialized = false;
292 GLib.MainLoop loop = new GLib.MainLoop ();
293 this.restartServer.begin ((obj, async_res) => {
294 this.restartServer.end(async_res);
297 return false; // can't do an operation yet?
301 if (!this.initialized) {
302 GLib.debug("Server has not been initialized");
305 if (this.sent_shutdown) {
306 GLib.debug("Server has been started its shutting down process");
316 public void onNotification(string method, Variant? return_value)
319 case "textDocument/publishDiagnostics":
320 //GLib.debug("got notification %s : %s", method , Json.to_string (Json.gvariant_serialize (return_value), true));
322 GLib.Idle.add(() => {
323 this.onDiagnostic(return_value);
331 GLib.debug("got notification %s : %s", method , Json.to_string (Json.gvariant_serialize (return_value), true));
335 bool getting_diagnostics = false;
339 public void onDiagnostic(Variant? return_value)
341 //GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
342 var dg = Json.gobject_deserialize (typeof (Lsp.Diagnostics), Json.gvariant_serialize (return_value)) as Lsp.Diagnostics;
343 GLib.debug("got diag for %s", dg.filename);
344 this.log(LanguageClientAction.DIAG, dg.filename);
345 if (this.project.path == dg.filename) {
346 this.getting_diagnostics = false;
347 this.log(LanguageClientAction.DIAG_END, "diagnostics done");
351 this.getting_diagnostics =true;
352 var f = this.project.getByPath(dg.filename);
354 //GLib.debug("no file %s", dg.uri);
355 //this.project.updateErrorsforFile(null);
358 //GLib.debug("got Diagnostics for %s", f.path);
359 f.updateErrors( dg.diagnostics );
364 public override void document_open (JsRender.JsRender file)
366 if (!this.isReady()) {
369 if (this.open_files.contains(file)) {
372 this.open_files.add(file);
375 GLib.debug ("LS sent open");
377 this.jsonrpc_client.send_notification (
378 "textDocument/didOpen",
380 textDocument : this.buildDict (
381 uri: new Variant.string (file.to_url()),
382 languageId : new Variant.string (file.language_id()),
383 version : new GLib.Variant.uint64 ( (uint64) file.version),
384 text : new Variant.string (file.toSource())
389 this.log(LanguageClientAction.OPEN, file.path);
390 } catch( GLib.Error e) {
391 this.log(LanguageClientAction.ERROR_RPC, e.message);
393 GLib.debug ("LS sent open err %s", e.message);
398 public override async void document_save (JsRender.JsRender file)
400 if (!this.isReady()) {
403 // save only really flags the file on the server - to actually force a change update - we need to
404 // flag it as changed.
405 yield this.document_change_force(file, file.toSource());
407 this.change_queue_file = null;
408 GLib.debug ("LS send save");
411 var args = this.buildDict (
412 textDocument : this.buildDict ( ///TextDocumentItem;
413 uri: new GLib.Variant.string (file.to_url()),
414 version : new GLib.Variant.uint64 ( (uint64) file.version)
418 //GLib.debug ("textDocument/save send with %s", Json.to_string (Json.gvariant_serialize (args), true));
422 yield this.jsonrpc_client.send_notification_async (
423 "textDocument/didSave",
427 this.log(LanguageClientAction.SAVE, file.path);
428 } catch( GLib.Error e) {
429 this.log(LanguageClientAction.ERROR_RPC, e.message);
430 GLib.debug ("LS save err %s", e.message);
436 public override void document_close (JsRender.JsRender file)
438 if (!this.isReady()) {
441 this.change_queue_file = null;
443 if (this.open_files.contains(file)) {
444 this.open_files.remove(file);
446 this.log(LanguageClientAction.CLOSE, file.path);
447 GLib.debug ("LS send close");
449 this.jsonrpc_client.send_notification (
450 "textDocument/didChange",
452 textDocument : this.buildDict ( ///TextDocumentItem;
453 uri: new GLib.Variant.string (file.to_url())
459 } catch( GLib.Error e) {
460 this.log(LanguageClientAction.ERROR_RPC, e.message);
461 GLib.debug ("LS close err %s", e.message);
469 public override void document_change (JsRender.JsRender file )
471 if (this.change_queue_file != null && this.change_queue_file.path != file.path) {
472 this.document_change_force.begin(this.change_queue_file, this.change_queue_file_source, (o, res) => {
473 this.document_change_force.end(res);
478 this.change_queue_file = file;
485 public override async void document_change_force (JsRender.JsRender file, string contents)
489 if (!this.isReady()) {
492 this.countdown = 9; // not really relivant..
493 this.change_queue_file = null; // this is more important..
495 if (!this.open_files.contains(file)) {
496 this.document_open(file);
499 GLib.debug ("LS send change %s rev %d", file.path, file.version);
500 var ar = new Json.Array();
501 var obj = new Json.Object();
502 obj.set_string_member("text", contents);
503 ar.add_object_element(obj);
504 var node = new Json.Node(Json.NodeType.ARRAY);
506 this.log(LanguageClientAction.CHANGE, file.path);
508 yield this.jsonrpc_client.send_notification_async (
509 "textDocument/didChange",
511 textDocument : this.buildDict ( ///TextDocumentItem;
512 uri: new GLib.Variant.string (file.to_url()),
513 version : new GLib.Variant.uint64 ( (uint64) file.version)
515 contentChanges : Json.gvariant_deserialize (node, null)
520 } catch( GLib.Error e) {
521 this.log(LanguageClientAction.ERROR_RPC, e.message);
522 GLib.debug ("LS change err %s", e.message);
528 // called by close window (on last window)...
529 public override void exit () throws GLib.Error
531 if (!this.isReady()) {
535 this.log(LanguageClientAction.TERM, "SEND exit");
537 this.jsonrpc_client.send_notification (
545 // not used currently..
546 public override async void shutdown () throws GLib.Error
548 if (!this.isReady()) {
551 this.log(LanguageClientAction.TERM, "SEND shutodwn");
552 this.sent_shutdown = true;
553 Variant? return_value;
554 yield this.jsonrpc_client.call_async (
560 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
562 //public async ??/symbol (string symbol) throws GLib.Error {
564 // and now for the important styff..
568 @triggerType 1 = typing or ctl-spac, 2 = tiggercharactres? 3= inside completion?
570 public override async Lsp.CompletionList? completion(JsRender.JsRender file, int line, int offset , int triggerType = 1) throws GLib.Error
572 /* partial_result_token , work_done_token context = null) */
573 GLib.debug("%s get completion %s @ %d:%d", this.get_type().name(), file.relpath, line, offset);
575 var ret = new Lsp.CompletionList();
577 if (!this.isReady()) {
578 GLib.debug("completion - language server not ready");
581 // make sure completion has the latest info..
582 //if (this.change_queue_file != null && this.change_queue_file.path != file.path) {
583 // this.document_change_real(this.change_queue_file, this.change_queue_file_source);
584 // this.change_queue_file != null;
586 this.log(LanguageClientAction.COMPLETE, "SEND complete %s @ %d:%d".printf(file.relpath, line, offset) );
588 Variant? return_value;
590 var args = this.buildDict (
591 context : this.buildDict ( ///CompletionContext;
592 triggerKind: new GLib.Variant.int32 (triggerType)
593 // triggerCharacter : new GLib.Variant.string ("")
595 textDocument : this.buildDict ( ///TextDocumentItem;
596 uri: new GLib.Variant.string (file.to_url()),
597 version : new GLib.Variant.uint64 ( (uint64) file.version)
599 position : this.buildDict (
600 line : new GLib.Variant.uint64 ( (uint) line) ,
601 character : new GLib.Variant.uint64 ( uint.max(0, (offset -1)))
605 GLib.debug ("textDocument/completion send with %s", Json.to_string (Json.gvariant_serialize (args), true));
607 yield this.jsonrpc_client.call_async (
608 "textDocument/completion",
615 //GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
616 var json = Json.gvariant_serialize (return_value);
619 if (json.get_node_type() == Json.NodeType.OBJECT) {
620 ret = Json.gobject_deserialize (typeof (Lsp.CompletionList), json) as Lsp.CompletionList;
621 this.log(LanguageClientAction.COMPLETE_REPLY, "GOT complete %d items".printf(ret.items.size) );
622 GLib.debug ("LS replied with Object");
626 if (json.get_node_type() != Json.NodeType.ARRAY) {
627 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
628 this.log(LanguageClientAction.ERROR_REPLY, "GOT something else??");
632 var ar = json.get_array();
634 for(var i = 0; i < ar.get_length(); i++ ) {
635 var add= Json.gobject_deserialize ( typeof (Lsp.CompletionItem), ar.get_element(i)) as Lsp.CompletionItem;
639 this.log(LanguageClientAction.COMPLETE_REPLY, "GOT array %d items".printf(ret.items.size) );
640 GLib.debug ("LS replied with Array");
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 syntax %s", file.relpath);
652 var ret = new Lsp.Hover();
654 if (!this.isReady()) {
657 Variant? return_value;
658 yield this.jsonrpc_client.call_async (
659 "textDocument/hover",
662 textDocument : this.buildDict ( ///TextDocumentItem;
663 uri: new GLib.Variant.string (file.to_url()),
664 version : new GLib.Variant.uint64 ( (uint64) file.version)
666 position : this.buildDict (
667 line : new GLib.Variant.uint64 ( (uint) line) ,
668 character : new GLib.Variant.uint64 ( uint.max(0, (offset -1)))
677 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
678 var json = Json.gvariant_serialize (return_value);
679 ret = Json.gobject_deserialize ( typeof (Lsp.Hover), json) as Lsp.Hover;
687 public override void queueDocumentSymbols (JsRender.JsRender file)
689 if (this.doc_queue_file != null && this.doc_queue_file.path != file.path) {
690 var sendfile = this.doc_queue_file,;
691 this.documentSymbols.begin(this.doc_queue_file, (o, res) => {
692 var ret = documentSymbols.end(res);
693 //sendfile.navigation_tree_updated(res);
697 this.doc_countdown = 3;
698 this.doc_queue_file = file;
702 public override async Gee.ArrayList<Lsp.DocumentSymbol> documentSymbols (JsRender.JsRender file) throws GLib.Error {
703 /* partial_result_token , work_done_token context = null) */
704 GLib.debug("get documentSymbols %s", file.relpath);
705 var ret = new Gee.ArrayList<Lsp.DocumentSymbol>();
707 if (!this.isReady()) {
710 Variant? return_value;
711 yield this.jsonrpc_client.call_async (
712 "textDocument/documentSymbol",
715 textDocument : this.buildDict ( ///TextDocumentItem;
716 uri: new GLib.Variant.string (file.to_url()),
717 version : new GLib.Variant.uint64 ( (uint64) file.version)
726 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
727 var json = Json.gvariant_serialize (return_value);
731 var ar = json.get_array();
732 GLib.debug ("LS replied with %D items", ar.get_length());
733 for(var i = 0; i < ar.get_length(); i++ ) {
734 var add= Json.gobject_deserialize ( typeof (Lsp.DocumentSymbol), ar.get_element(i)) as Lsp.DocumentSymbol;