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 this.launcher.set_environ(GLib.Environ.get());
87 this.subprocess = launcher.spawnv ({ process_path });
89 this.subprocess.wait_async.begin( null, ( obj,res ) => {
91 this.subprocess.wait_async.end(res);
92 } catch (GLib.Error e) {
93 this.log(LanguageClientAction.ERROR_START, e.message);
94 GLib.debug("subprocess startup error %s", e.message);
96 this.log(LanguageClientAction.EXIT, "process ended");
97 GLib.debug("Subprocess ended %s", process_path);
101 var input_stream = this.subprocess.get_stdout_pipe ();
102 var output_stream = this.subprocess.get_stdin_pipe ();
104 if (input_stream is GLib.UnixInputStream && output_stream is GLib.UnixOutputStream) {
106 if (!GLib.Unix.set_fd_nonblocking(((GLib.UnixInputStream)input_stream).fd, true)
107 || !GLib.Unix.set_fd_nonblocking (((GLib.UnixOutputStream)output_stream).fd, true))
109 GLib.debug("could not set pipes to nonblocking");
114 this.subprocess_stream = new GLib.SimpleIOStream (input_stream, output_stream);
115 this.accept_io_stream ( this.subprocess_stream);
116 } catch (GLib.Error e) {
117 this.log(LanguageClientAction.ERROR_START, e.message);
118 GLib.debug("subprocess startup error %s", e.message);
124 bool in_close = false;
125 public override void client_accepted (Jsonrpc.Client client)
127 if (this.jsonrpc_client == null) {
128 this.jsonrpc_client = client;
130 GLib.debug("client accepted connection - calling init server");
131 this.log(LanguageClientAction.ACCEPT, "client accepted");
133 this.jsonrpc_client.notification.connect((method, paramz) => {
134 this.onNotification(method, paramz);
137 this.jsonrpc_client.failed.connect(() => {
138 this.log(LanguageClientAction.ERROR_RPC, "client failed");
141 GLib.debug("language server server has failed");
144 this.initialize_server ();
151 public override void initialize_server() {
153 Variant? return_value;
154 this.jsonrpc_client.call (
157 processId: new Variant.int32 ((int32) Posix.getpid ()),
158 rootPath: new Variant.string (this.project.path),
159 rootUri: new Variant.string (File.new_for_path (this.project.path).get_uri ())
164 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
165 this.open_files = new Gee.ArrayList<JsRender.JsRender>((a,b) => {
166 return a.path == b.path;
168 this.initialized = true;
169 this.getting_diagnostics = false;
171 } catch (GLib.Error e) {
172 GLib.debug ("LS replied with error %s", e.message);
182 if (this.launcher == null) {
185 this.getting_diagnostics = false;
186 this.in_close = true;
187 GLib.debug("onClose called");
189 if (this.jsonrpc_client != null) {
191 this.jsonrpc_client.close();
192 } catch (GLib.Error e) {
193 GLib.debug("rpc Error close error %s", e.message);
196 if (this.subprocess_stream != null) {
198 this.subprocess_stream.close();
199 } catch (GLib.Error e) {
200 GLib.debug("stream Error close %s", e.message);
203 if (this.subprocess != null) {
204 this.subprocess.force_exit();
206 if (this.launcher != null) {
207 this.launcher.close();
210 this.launcher = null;
211 this.subprocess = null;
212 this.jsonrpc_client = null;
214 this.in_close = false;
217 public async void restartServer()
223 public bool isReady()
226 this.log(LanguageClientAction.RESTART,"closed is set - restarting");
227 GLib.debug("server stopped = restarting");
228 this.initialized = false;
230 GLib.MainLoop loop = new GLib.MainLoop ();
231 this.restartServer.begin ((obj, async_res) => {
232 this.restartServer.end(async_res);
235 return false; // can't do an operation yet?
239 if (!this.initialized) {
240 GLib.debug("Server has not been initialized");
243 if (this.sent_shutdown) {
244 GLib.debug("Server has been started its shutting down process");
254 public void onNotification(string method, Variant? return_value)
257 case "textDocument/publishDiagnostics":
258 //GLib.debug("got notification %s : %s", method , Json.to_string (Json.gvariant_serialize (return_value), true));
259 this.onDiagnostic(return_value);
265 GLib.debug("got notification %s : %s", method , Json.to_string (Json.gvariant_serialize (return_value), true));
269 bool getting_diagnostics = false;
273 public void onDiagnostic(Variant? return_value)
275 //GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
276 var dg = Json.gobject_deserialize (typeof (Lsp.Diagnostics), Json.gvariant_serialize (return_value)) as Lsp.Diagnostics;
277 GLib.debug("got diag for %s", dg.filename);
278 this.log(LanguageClientAction.DIAG, dg.filename);
279 if (this.project.path == dg.filename) {
280 this.getting_diagnostics = false;
281 this.log(LanguageClientAction.DIAG_END, "diagnostics done");
285 this.getting_diagnostics =true;
286 var f = this.project.getByPath(dg.filename);
288 //GLib.debug("no file %s", dg.uri);
289 //this.project.updateErrorsforFile(null);
292 //GLib.debug("got Diagnostics for %s", f.path);
293 f.updateErrors( dg.diagnostics );
298 public override void document_open (JsRender.JsRender file)
300 if (!this.isReady()) {
303 if (this.open_files.contains(file)) {
306 this.open_files.add(file);
309 GLib.debug ("LS sent open");
311 this.jsonrpc_client.send_notification (
312 "textDocument/didOpen",
314 textDocument : this.buildDict (
315 uri: new Variant.string (file.to_url()),
316 languageId : new Variant.string (file.language_id()),
317 version : new GLib.Variant.uint64 ( (uint64) file.version),
318 text : new Variant.string (file.toSource())
323 this.log(LanguageClientAction.OPEN, file.path);
324 } catch( GLib.Error e) {
325 this.log(LanguageClientAction.ERROR_RPC, e.message);
327 GLib.debug ("LS sent open err %s", e.message);
332 public override async void document_save (JsRender.JsRender file)
334 if (!this.isReady()) {
337 // save only really flags the file on the server - to actually force a change update - we need to
338 // flag it as changed.
339 yield this.document_change_force(file, file.toSource());
341 this.change_queue_file = null;
342 GLib.debug ("LS send save");
345 var args = this.buildDict (
346 textDocument : this.buildDict ( ///TextDocumentItem;
347 uri: new GLib.Variant.string (file.to_url()),
348 version : new GLib.Variant.uint64 ( (uint64) file.version)
352 //GLib.debug ("textDocument/save send with %s", Json.to_string (Json.gvariant_serialize (args), true));
356 yield this.jsonrpc_client.send_notification_async (
357 "textDocument/didSave",
361 this.log(LanguageClientAction.SAVE, file.path);
362 } catch( GLib.Error e) {
363 this.log(LanguageClientAction.ERROR_RPC, e.message);
364 GLib.debug ("LS save err %s", e.message);
370 public override void document_close (JsRender.JsRender file)
372 if (!this.isReady()) {
375 this.change_queue_file = null;
377 if (this.open_files.contains(file)) {
378 this.open_files.remove(file);
380 this.log(LanguageClientAction.CLOSE, file.path);
381 GLib.debug ("LS send close");
383 this.jsonrpc_client.send_notification (
384 "textDocument/didChange",
386 textDocument : this.buildDict ( ///TextDocumentItem;
387 uri: new GLib.Variant.string (file.to_url())
393 } catch( GLib.Error e) {
394 this.log(LanguageClientAction.ERROR_RPC, e.message);
395 GLib.debug ("LS close err %s", e.message);
403 public override void document_change (JsRender.JsRender file )
405 if (this.change_queue_file != null && this.change_queue_file.path != file.path) {
406 this.document_change_force.begin(this.change_queue_file, this.change_queue_file_source, (o, res) => {
407 this.document_change_force.end(res);
412 this.change_queue_file = file;
419 public override async void document_change_force (JsRender.JsRender file, string contents)
423 if (!this.isReady()) {
426 this.countdown = 9; // not really relivant..
427 this.change_queue_file = null; // this is more important..
429 if (!this.open_files.contains(file)) {
430 this.document_open(file);
433 GLib.debug ("LS send change %s rev %d", file.path, file.version);
434 var ar = new Json.Array();
435 var obj = new Json.Object();
436 obj.set_string_member("text", contents);
437 ar.add_object_element(obj);
438 var node = new Json.Node(Json.NodeType.ARRAY);
440 this.log(LanguageClientAction.CHANGE, file.path);
442 yield this.jsonrpc_client.send_notification_async (
443 "textDocument/didChange",
445 textDocument : this.buildDict ( ///TextDocumentItem;
446 uri: new GLib.Variant.string (file.to_url()),
447 version : new GLib.Variant.uint64 ( (uint64) file.version)
449 contentChanges : Json.gvariant_deserialize (node, null)
454 } catch( GLib.Error e) {
455 this.log(LanguageClientAction.ERROR_RPC, e.message);
456 GLib.debug ("LS change err %s", e.message);
462 // called by close window (on last window)...
463 public override void exit () throws GLib.Error
465 if (!this.isReady()) {
469 this.log(LanguageClientAction.TERM, "SEND exit");
471 this.jsonrpc_client.send_notification (
479 // not used currently..
480 public override async void shutdown () throws GLib.Error
482 if (!this.isReady()) {
485 this.log(LanguageClientAction.TERM, "SEND shutodwn");
486 this.sent_shutdown = true;
487 Variant? return_value;
488 yield this.jsonrpc_client.call_async (
494 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
496 //public async ??/symbol (string symbol) throws GLib.Error {
498 // and now for the important styff..
502 @triggerType 1 = typing or ctl-spac, 2 = tiggercharactres? 3= inside completion?
504 public override async Lsp.CompletionList? completion(JsRender.JsRender file, int line, int offset , int triggerType = 1) throws GLib.Error
506 /* partial_result_token , work_done_token context = null) */
507 GLib.debug("%s get completion %s @ %d:%d", this.get_type().name(), file.relpath, line, offset);
509 var ret = new Lsp.CompletionList();
511 if (!this.isReady()) {
512 GLib.debug("completion - language server not ready");
515 // make sure completion has the latest info..
516 //if (this.change_queue_file != null && this.change_queue_file.path != file.path) {
517 // this.document_change_real(this.change_queue_file, this.change_queue_file_source);
518 // this.change_queue_file != null;
520 this.log(LanguageClientAction.COMPLETE, "SEND complete %s @ %d:%d".printf(file.relpath, line, offset) );
522 Variant? return_value;
524 var args = this.buildDict (
525 context : this.buildDict ( ///CompletionContext;
526 triggerKind: new GLib.Variant.int32 (triggerType)
527 // triggerCharacter : new GLib.Variant.string ("")
529 textDocument : this.buildDict ( ///TextDocumentItem;
530 uri: new GLib.Variant.string (file.to_url()),
531 version : new GLib.Variant.uint64 ( (uint64) file.version)
533 position : this.buildDict (
534 line : new GLib.Variant.uint64 ( (uint) line) ,
535 character : new GLib.Variant.uint64 ( uint.max(0, (offset -1)))
539 GLib.debug ("textDocument/completion send with %s", Json.to_string (Json.gvariant_serialize (args), true));
541 yield this.jsonrpc_client.call_async (
542 "textDocument/completion",
549 //GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
550 var json = Json.gvariant_serialize (return_value);
553 if (json.get_node_type() == Json.NodeType.OBJECT) {
554 ret = Json.gobject_deserialize (typeof (Lsp.CompletionList), json) as Lsp.CompletionList;
555 this.log(LanguageClientAction.COMPLETE_REPLY, "GOT complete %d items".printf(ret.items.size) );
556 GLib.debug ("LS replied with Object");
560 if (json.get_node_type() != Json.NodeType.ARRAY) {
561 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
562 this.log(LanguageClientAction.ERROR_REPLY, "GOT something else??");
566 var ar = json.get_array();
568 for(var i = 0; i < ar.get_length(); i++ ) {
569 var add= Json.gobject_deserialize ( typeof (Lsp.CompletionItem), ar.get_element(i)) as Lsp.CompletionItem;
573 this.log(LanguageClientAction.COMPLETE_REPLY, "GOT array %d items".printf(ret.items.size) );
574 GLib.debug ("LS replied with Array");
579 //CompletionListInfo.itmems.parse_varient or CompletionListInfo.parsevarient
580 public override async Gee.ArrayList<Lsp.DocumentSymbol> syntax (JsRender.JsRender file) throws GLib.Error
582 /* partial_result_token , work_done_token context = null) */
583 GLib.debug("get syntax %s", file.relpath);
584 var ret = new Gee.ArrayList<Lsp.DocumentSymbol>();
586 if (!this.isReady()) {
589 Variant? return_value;
590 yield this.jsonrpc_client.call_async (
591 "textDocument/documentSymbol",
594 textDocument : this.buildDict ( ///TextDocumentItem;
595 uri: new GLib.Variant.string (file.to_url()),
596 version : new GLib.Variant.uint64 ( (uint64) file.version)
605 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
606 var json = Json.gvariant_serialize (return_value);
610 var ar = json.get_array();
611 for(var i = 0; i < ar.get_length(); i++ ) {
612 var add= Json.gobject_deserialize ( typeof (Lsp.DocumentSymbol), ar.get_element(i)) as Lsp.DocumentSymbol;