3 public class LanguageClientVala : LanguageClient {
5 protected bool initialized = false;
6 bool sent_shutdown = false;
7 uint change_queue_id = 0;
10 private bool _closed = false;
12 get { return this._closed ; }
14 GLib.debug("closed has been set? to %s" , value ? "TRUE" : "FALSE" );
18 private GLib.SubprocessLauncher launcher = null;
19 private GLib.Subprocess? subprocess = null;
20 private IOStream? subprocess_stream = null;
21 public Jsonrpc.Client? jsonrpc_client = null;
23 Gee.ArrayList<JsRender.JsRender> open_files;
24 private JsRender.JsRender? _change_queue_file = null;
25 private string change_queue_file_source = "";
27 JsRender.JsRender? change_queue_file {
29 this.change_queue_file_source = value == null ? "" : value.toSource();
30 this._change_queue_file = value;
33 return this._change_queue_file;
38 var exe = GLib.Environment.find_program_in_path( "vala-language-server");
40 GLib.warning("could not find vala-language-server");
44 this.initProcess(exe);
48 public LanguageClientVala(Project.Project project)
50 // extend versions will proably call initialize to start and connect to server.
53 this.change_queue_id = GLib.Timeout.add_seconds(1, () => {
54 if (this.change_queue_file == null) {
57 if (this.getting_diagnostics) {
63 if (this.countdown < 0){
64 this.document_change_force.begin(this.change_queue_file, this.change_queue_file_source, (o, res) => {
65 this.document_change_force.end(res);
67 this.change_queue_file = null;
77 public bool initProcess(string process_path)
80 this.log(LanguageClientAction.LAUNCH, process_path);
81 GLib.debug("Launching %s", process_path);
82 this.launcher = new GLib.SubprocessLauncher (SubprocessFlags.STDIN_PIPE | SubprocessFlags.STDOUT_PIPE);
83 var env = GLib.Environ.get();
84 env += "G_MESSAGES_DEBUG=all";
86 this.launcher.set_environ(env);
87 var logpath = GLib.Environment.get_home_dir() + "/.cache/vala-language-server";
89 if (!GLib.FileUtils.test(logpath, GLib.FileTest.IS_DIR)) {
90 Posix.mkdir(logpath, 0700);
92 // not very reliable..
93 //this.launcher.set_stderr_file_path(
95 // (new GLib.DateTime.now_local()).format("%Y-%m-%d") + ".log"
97 //GLib.debug("log lang server to %s", logpath + "/" +
98 // (new GLib.DateTime.now_local()).format("%Y-%m-%d") + ".log");
103 this.subprocess = launcher.spawnv ({ process_path , "2>" , "/tmp/vala-language-server.log" });
105 this.subprocess.wait_async.begin( null, ( obj,res ) => {
107 this.subprocess.wait_async.end(res);
108 } catch (GLib.Error e) {
109 this.log(LanguageClientAction.ERROR_START, e.message);
110 GLib.debug("subprocess startup error %s", e.message);
112 this.log(LanguageClientAction.EXIT, "process ended");
113 GLib.debug("Subprocess ended %s", process_path);
117 var input_stream = this.subprocess.get_stdout_pipe ();
118 var output_stream = this.subprocess.get_stdin_pipe ();
120 if (input_stream is GLib.UnixInputStream && output_stream is GLib.UnixOutputStream) {
122 if (!GLib.Unix.set_fd_nonblocking(((GLib.UnixInputStream)input_stream).fd, true)
123 || !GLib.Unix.set_fd_nonblocking (((GLib.UnixOutputStream)output_stream).fd, true))
125 GLib.debug("could not set pipes to nonblocking");
130 this.subprocess_stream = new GLib.SimpleIOStream (input_stream, output_stream);
131 this.accept_io_stream ( this.subprocess_stream);
132 } catch (GLib.Error e) {
133 this.log(LanguageClientAction.ERROR_START, e.message);
134 GLib.debug("subprocess startup error %s", e.message);
140 bool in_close = false;
141 public override void client_accepted (Jsonrpc.Client client)
143 if (this.jsonrpc_client == null) {
144 this.jsonrpc_client = client;
146 GLib.debug("client accepted connection - calling init server");
147 this.log(LanguageClientAction.ACCEPT, "client accepted");
149 this.jsonrpc_client.notification.connect((method, paramz) => {
150 this.onNotification(method, paramz);
153 this.jsonrpc_client.failed.connect(() => {
154 this.log(LanguageClientAction.ERROR_RPC, "client failed");
155 GLib.debug("language server server has failed");
161 this.initialize_server ();
168 public override void initialize_server() {
170 Variant? return_value;
171 this.jsonrpc_client.call (
174 processId: new Variant.int32 ((int32) Posix.getpid ()),
175 rootPath: new Variant.string (this.project.path),
176 rootUri: new Variant.string (File.new_for_path (this.project.path).get_uri ())
181 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
182 this.open_files = new Gee.ArrayList<JsRender.JsRender>((a,b) => {
183 return a.path == b.path;
185 this.initialized = true;
186 this.getting_diagnostics = false;
188 } catch (GLib.Error e) {
189 GLib.debug ("LS replied with error %s", e.message);
199 if (this.launcher == null) {
202 this.getting_diagnostics = false;
203 this.in_close = true;
204 GLib.debug("onClose called");
206 if (this.jsonrpc_client != null) {
208 this.jsonrpc_client.close();
209 } catch (GLib.Error e) {
210 GLib.debug("rpc Error close error %s", e.message);
213 if (this.subprocess_stream != null) {
215 this.subprocess_stream.close();
216 } catch (GLib.Error e) {
217 GLib.debug("stream Error close %s", e.message);
220 if (this.subprocess != null) {
221 this.subprocess.force_exit();
223 if (this.launcher != null) {
224 this.launcher.close();
227 this.launcher = null;
228 this.subprocess = null;
229 this.jsonrpc_client = null;
231 this.in_close = false;
234 public async void restartServer()
240 public bool isReady()
243 this.log(LanguageClientAction.RESTART,"closed is set - restarting");
244 GLib.debug("server stopped = restarting");
245 this.initialized = false;
247 GLib.MainLoop loop = new GLib.MainLoop ();
248 this.restartServer.begin ((obj, async_res) => {
249 this.restartServer.end(async_res);
252 return false; // can't do an operation yet?
256 if (!this.initialized) {
257 GLib.debug("Server has not been initialized");
260 if (this.sent_shutdown) {
261 GLib.debug("Server has been started its shutting down process");
271 public void onNotification(string method, Variant? return_value)
274 case "textDocument/publishDiagnostics":
275 //GLib.debug("got notification %s : %s", method , Json.to_string (Json.gvariant_serialize (return_value), true));
277 GLib.Idle.add(() => {
278 this.onDiagnostic(return_value);
286 GLib.debug("got notification %s : %s", method , Json.to_string (Json.gvariant_serialize (return_value), true));
290 bool getting_diagnostics = false;
294 public void onDiagnostic(Variant? return_value)
296 //GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
297 var dg = Json.gobject_deserialize (typeof (Lsp.Diagnostics), Json.gvariant_serialize (return_value)) as Lsp.Diagnostics;
298 GLib.debug("got diag for %s", dg.filename);
299 this.log(LanguageClientAction.DIAG, dg.filename);
300 if (this.project.path == dg.filename) {
301 this.getting_diagnostics = false;
302 this.log(LanguageClientAction.DIAG_END, "diagnostics done");
306 this.getting_diagnostics =true;
307 var f = this.project.getByPath(dg.filename);
309 //GLib.debug("no file %s", dg.uri);
310 //this.project.updateErrorsforFile(null);
313 //GLib.debug("got Diagnostics for %s", f.path);
314 f.updateErrors( dg.diagnostics );
319 public override void document_open (JsRender.JsRender file)
321 if (!this.isReady()) {
324 if (this.open_files.contains(file)) {
327 this.open_files.add(file);
330 GLib.debug ("LS sent open");
332 this.jsonrpc_client.send_notification (
333 "textDocument/didOpen",
335 textDocument : this.buildDict (
336 uri: new Variant.string (file.to_url()),
337 languageId : new Variant.string (file.language_id()),
338 version : new GLib.Variant.uint64 ( (uint64) file.version),
339 text : new Variant.string (file.toSource())
344 this.log(LanguageClientAction.OPEN, file.path);
345 } catch( GLib.Error e) {
346 this.log(LanguageClientAction.ERROR_RPC, e.message);
348 GLib.debug ("LS sent open err %s", e.message);
353 public override async void document_save (JsRender.JsRender file)
355 if (!this.isReady()) {
358 // save only really flags the file on the server - to actually force a change update - we need to
359 // flag it as changed.
360 yield this.document_change_force(file, file.toSource());
362 this.change_queue_file = null;
363 GLib.debug ("LS send save");
366 var args = this.buildDict (
367 textDocument : this.buildDict ( ///TextDocumentItem;
368 uri: new GLib.Variant.string (file.to_url()),
369 version : new GLib.Variant.uint64 ( (uint64) file.version)
373 //GLib.debug ("textDocument/save send with %s", Json.to_string (Json.gvariant_serialize (args), true));
377 yield this.jsonrpc_client.send_notification_async (
378 "textDocument/didSave",
382 this.log(LanguageClientAction.SAVE, file.path);
383 } catch( GLib.Error e) {
384 this.log(LanguageClientAction.ERROR_RPC, e.message);
385 GLib.debug ("LS save err %s", e.message);
391 public override void document_close (JsRender.JsRender file)
393 if (!this.isReady()) {
396 this.change_queue_file = null;
398 if (this.open_files.contains(file)) {
399 this.open_files.remove(file);
401 this.log(LanguageClientAction.CLOSE, file.path);
402 GLib.debug ("LS send close");
404 this.jsonrpc_client.send_notification (
405 "textDocument/didChange",
407 textDocument : this.buildDict ( ///TextDocumentItem;
408 uri: new GLib.Variant.string (file.to_url())
414 } catch( GLib.Error e) {
415 this.log(LanguageClientAction.ERROR_RPC, e.message);
416 GLib.debug ("LS close err %s", e.message);
424 public override void document_change (JsRender.JsRender file )
426 if (this.change_queue_file != null && this.change_queue_file.path != file.path) {
427 this.document_change_force.begin(this.change_queue_file, this.change_queue_file_source, (o, res) => {
428 this.document_change_force.end(res);
433 this.change_queue_file = file;
440 public override async void document_change_force (JsRender.JsRender file, string contents)
444 if (!this.isReady()) {
447 this.countdown = 9; // not really relivant..
448 this.change_queue_file = null; // this is more important..
450 if (!this.open_files.contains(file)) {
451 this.document_open(file);
454 GLib.debug ("LS send change %s rev %d", file.path, file.version);
455 var ar = new Json.Array();
456 var obj = new Json.Object();
457 obj.set_string_member("text", contents);
458 ar.add_object_element(obj);
459 var node = new Json.Node(Json.NodeType.ARRAY);
461 this.log(LanguageClientAction.CHANGE, file.path);
463 yield this.jsonrpc_client.send_notification_async (
464 "textDocument/didChange",
466 textDocument : this.buildDict ( ///TextDocumentItem;
467 uri: new GLib.Variant.string (file.to_url()),
468 version : new GLib.Variant.uint64 ( (uint64) file.version)
470 contentChanges : Json.gvariant_deserialize (node, null)
475 } catch( GLib.Error e) {
476 this.log(LanguageClientAction.ERROR_RPC, e.message);
477 GLib.debug ("LS change err %s", e.message);
483 // called by close window (on last window)...
484 public override void exit () throws GLib.Error
486 if (!this.isReady()) {
490 this.log(LanguageClientAction.TERM, "SEND exit");
492 this.jsonrpc_client.send_notification (
500 // not used currently..
501 public override async void shutdown () throws GLib.Error
503 if (!this.isReady()) {
506 this.log(LanguageClientAction.TERM, "SEND shutodwn");
507 this.sent_shutdown = true;
508 Variant? return_value;
509 yield this.jsonrpc_client.call_async (
515 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
517 //public async ??/symbol (string symbol) throws GLib.Error {
519 // and now for the important styff..
523 @triggerType 1 = typing or ctl-spac, 2 = tiggercharactres? 3= inside completion?
525 public override async Lsp.CompletionList? completion(JsRender.JsRender file, int line, int offset , int triggerType = 1) throws GLib.Error
527 /* partial_result_token , work_done_token context = null) */
528 GLib.debug("%s get completion %s @ %d:%d", this.get_type().name(), file.relpath, line, offset);
530 var ret = new Lsp.CompletionList();
532 if (!this.isReady()) {
533 GLib.debug("completion - language server not ready");
536 // make sure completion has the latest info..
537 //if (this.change_queue_file != null && this.change_queue_file.path != file.path) {
538 // this.document_change_real(this.change_queue_file, this.change_queue_file_source);
539 // this.change_queue_file != null;
541 this.log(LanguageClientAction.COMPLETE, "SEND complete %s @ %d:%d".printf(file.relpath, line, offset) );
543 Variant? return_value;
545 var args = this.buildDict (
546 context : this.buildDict ( ///CompletionContext;
547 triggerKind: new GLib.Variant.int32 (triggerType)
548 // triggerCharacter : new GLib.Variant.string ("")
550 textDocument : this.buildDict ( ///TextDocumentItem;
551 uri: new GLib.Variant.string (file.to_url()),
552 version : new GLib.Variant.uint64 ( (uint64) file.version)
554 position : this.buildDict (
555 line : new GLib.Variant.uint64 ( (uint) line) ,
556 character : new GLib.Variant.uint64 ( uint.max(0, (offset -1)))
560 GLib.debug ("textDocument/completion send with %s", Json.to_string (Json.gvariant_serialize (args), true));
562 yield this.jsonrpc_client.call_async (
563 "textDocument/completion",
570 //GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
571 var json = Json.gvariant_serialize (return_value);
574 if (json.get_node_type() == Json.NodeType.OBJECT) {
575 ret = Json.gobject_deserialize (typeof (Lsp.CompletionList), json) as Lsp.CompletionList;
576 this.log(LanguageClientAction.COMPLETE_REPLY, "GOT complete %d items".printf(ret.items.size) );
577 GLib.debug ("LS replied with Object");
581 if (json.get_node_type() != Json.NodeType.ARRAY) {
582 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
583 this.log(LanguageClientAction.ERROR_REPLY, "GOT something else??");
587 var ar = json.get_array();
589 for(var i = 0; i < ar.get_length(); i++ ) {
590 var add= Json.gobject_deserialize ( typeof (Lsp.CompletionItem), ar.get_element(i)) as Lsp.CompletionItem;
594 this.log(LanguageClientAction.COMPLETE_REPLY, "GOT array %d items".printf(ret.items.size) );
595 GLib.debug ("LS replied with Array");
600 //CompletionListInfo.itmems.parse_varient or CompletionListInfo.parsevarient
601 public override async Gee.ArrayList<Lsp.DocumentSymbol> syntax (JsRender.JsRender file) throws GLib.Error
603 /* partial_result_token , work_done_token context = null) */
604 GLib.debug("get syntax %s", file.relpath);
605 var ret = new Gee.ArrayList<Lsp.DocumentSymbol>();
607 if (!this.isReady()) {
610 Variant? return_value;
611 yield this.jsonrpc_client.call_async (
612 "textDocument/documentSymbol",
615 textDocument : this.buildDict ( ///TextDocumentItem;
616 uri: new GLib.Variant.string (file.to_url()),
617 version : new GLib.Variant.uint64 ( (uint64) file.version)
626 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
627 var json = Json.gvariant_serialize (return_value);
631 var ar = json.get_array();
632 for(var i = 0; i < ar.get_length(); i++ ) {
633 var add= Json.gobject_deserialize ( typeof (Lsp.DocumentSymbol), ar.get_element(i)) as Lsp.DocumentSymbol;