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 this.initProcess("/usr/bin/vala-language-server");
42 public LanguageClientVala(Project.Project project)
44 // extend versions will proably call initialize to start and connect to server.
46 this.open_files = new Gee.ArrayList<JsRender.JsRender>();
47 this.change_queue_id = GLib.Timeout.add_seconds(1, () => {
48 if (this.change_queue_file == null) {
52 if (this.countdown < 0){
53 this.document_change_force.begin(this.change_queue_file, this.change_queue_file_source, (o, res) => {
54 this.document_change_force.end(res);
56 this.change_queue_file = null;
66 public bool initProcess(string process_path)
69 this.log(LanguageClientAction.LAUNCH, process_path);
70 GLib.debug("Launching %s", process_path);
71 this.launcher = new GLib.SubprocessLauncher (SubprocessFlags.STDIN_PIPE | SubprocessFlags.STDOUT_PIPE);
72 this.launcher.set_environ(GLib.Environ.get());
76 this.subprocess = launcher.spawnv ({ process_path });
78 this.subprocess.wait_async.begin( null, ( obj,res ) => {
80 this.subprocess.wait_async.end(res);
81 } catch (GLib.Error e) {
82 this.log(LanguageClientAction.ERROR_START, e.message);
83 GLib.debug("subprocess startup error %s", e.message);
85 this.log(LanguageClientAction.EXIT, "process ended");
86 GLib.debug("Subprocess ended %s", process_path);
90 var input_stream = this.subprocess.get_stdout_pipe ();
91 var output_stream = this.subprocess.get_stdin_pipe ();
93 if (input_stream is GLib.UnixInputStream && output_stream is GLib.UnixOutputStream) {
95 if (!GLib.Unix.set_fd_nonblocking(((GLib.UnixInputStream)input_stream).fd, true)
96 || !GLib.Unix.set_fd_nonblocking (((GLib.UnixOutputStream)output_stream).fd, true))
98 GLib.debug("could not set pipes to nonblocking");
103 this.subprocess_stream = new GLib.SimpleIOStream (input_stream, output_stream);
104 this.accept_io_stream ( this.subprocess_stream);
105 } catch (GLib.Error e) {
106 this.log(LanguageClientAction.ERROR_START, e.message);
107 GLib.debug("subprocess startup error %s", e.message);
113 bool in_close = false;
114 public override void client_accepted (Jsonrpc.Client client)
116 if (this.jsonrpc_client == null) {
117 this.jsonrpc_client = client;
119 GLib.debug("client accepted connection - calling init server");
120 this.log(LanguageClientAction.ACCEPT, "client accepted");
122 this.jsonrpc_client.notification.connect((method, paramz) => {
123 this.onNotification(method, paramz);
126 this.jsonrpc_client.failed.connect(() => {
127 this.log(LanguageClientAction.ERROR_RPC, "client failed");
130 GLib.debug("language server server has failed");
133 this.initialize_server ();
140 public override void initialize_server() {
142 Variant? return_value;
143 this.jsonrpc_client.call (
146 processId: new Variant.int32 ((int32) Posix.getpid ()),
147 rootPath: new Variant.string (this.project.path),
148 rootUri: new Variant.string (File.new_for_path (this.project.path).get_uri ())
153 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
154 this.initialized = true;
156 } catch (GLib.Error e) {
157 GLib.debug ("LS replied with error %s", e.message);
167 if (this.launcher == null) {
170 this.in_close = true;
171 GLib.debug("onClose called");
173 if (this.jsonrpc_client != null) {
175 this.jsonrpc_client.close();
176 } catch (GLib.Error e) {
177 GLib.debug("rpc Error close error %s", e.message);
180 if (this.subprocess_stream != null) {
182 this.subprocess_stream.close();
183 } catch (GLib.Error e) {
184 GLib.debug("stream Error close %s", e.message);
187 if (this.subprocess != null) {
188 this.subprocess.force_exit();
190 if (this.launcher != null) {
191 this.launcher.close();
194 this.launcher = null;
195 this.subprocess = null;
196 this.jsonrpc_client = null;
198 this.in_close = false;
201 public async void restartServer()
204 foreach(var f in this.open_files) {
205 this.document_open(f);
209 public bool isReady()
212 this.log(LanguageClientAction.RESTART,"closed is set - restarting");
213 GLib.debug("server stopped = restarting");
214 this.initialized = false;
216 GLib.MainLoop loop = new GLib.MainLoop ();
217 this.restartServer.begin ((obj, async_res) => {
218 this.restartServer.end(async_res);
221 return false; // can't do an operation yet?
225 if (!this.initialized) {
226 GLib.debug("Server has not been initialized");
229 if (this.sent_shutdown) {
230 GLib.debug("Server has been started its shutting down process");
240 public void onNotification(string method, Variant? return_value)
243 case "textDocument/publishDiagnostics":
244 this.onDiagnostic(return_value);
250 GLib.debug("got notification %s : %s", method , Json.to_string (Json.gvariant_serialize (return_value), true));
257 public void onDiagnostic(Variant? return_value)
260 var dg = Json.gobject_deserialize (typeof (Lsp.Diagnostics), Json.gvariant_serialize (return_value)) as Lsp.Diagnostics;
261 this.log(LanguageClientAction.DIAG, dg.filename);
262 var f = this.project.getByPath(dg.filename);
264 //GLib.debug("no file %s", dg.uri);
265 this.project.updateErrorsforFile(null);
268 foreach(var v in f.errorsByType.values) {
271 foreach(var diag in dg.diagnostics) {
272 var ce = new CompileError.new_from_diagnostic(f, diag);
273 if (!f.errorsByType.has_key(ce.category)) {
274 f.errorsByType.set(ce.category, new GLib.ListStore(typeof(CompileError)));
276 f.errorsByType.get(ce.category).append(ce);
278 f.project.updateErrorsforFile(f);
282 public override void document_open (JsRender.JsRender file)
284 if (!this.isReady()) {
287 if (!this.open_files.contains(file)) {
288 this.open_files.add(file);
291 GLib.debug ("LS sent open");
293 this.jsonrpc_client.send_notification (
294 "textDocument/didOpen",
296 textDocument : this.buildDict (
297 uri: new Variant.string (file.to_url()),
298 languageId : new Variant.string (file.language_id()),
299 version : new GLib.Variant.uint64 ( (uint64) file.version),
300 text : new Variant.string (file.toSource())
305 this.log(LanguageClientAction.OPEN, file.path);
306 } catch( GLib.Error e) {
307 this.log(LanguageClientAction.ERROR_RPC, e.message);
309 GLib.debug ("LS sent open err %s", e.message);
314 public override async void document_save (JsRender.JsRender file)
316 if (!this.isReady()) {
319 // save only really flags the file on the server - to actually force a change update - we need to
320 // flag it as changed.
321 yield this.document_change_force(file, file.toSource());
323 this.change_queue_file = null;
324 GLib.debug ("LS send save");
327 var args = this.buildDict (
328 textDocument : this.buildDict ( ///TextDocumentItem;
329 uri: new GLib.Variant.string (file.to_url()),
330 version : new GLib.Variant.uint64 ( (uint64) file.version)
334 //GLib.debug ("textDocument/save send with %s", Json.to_string (Json.gvariant_serialize (args), true));
338 yield this.jsonrpc_client.send_notification_async (
339 "textDocument/didSave",
343 this.log(LanguageClientAction.SAVE, file.path);
344 } catch( GLib.Error e) {
345 this.log(LanguageClientAction.ERROR_RPC, e.message);
346 GLib.debug ("LS save err %s", e.message);
352 public override void document_close (JsRender.JsRender file)
354 if (!this.isReady()) {
357 this.change_queue_file = null;
359 if (this.open_files.contains(file)) {
360 this.open_files.remove(file);
362 this.log(LanguageClientAction.CLOSE, file.path);
363 GLib.debug ("LS send close");
365 this.jsonrpc_client.send_notification (
366 "textDocument/didChange",
368 textDocument : this.buildDict ( ///TextDocumentItem;
369 uri: new GLib.Variant.string (file.to_url())
375 } catch( GLib.Error e) {
376 this.log(LanguageClientAction.ERROR_RPC, e.message);
377 GLib.debug ("LS close err %s", e.message);
385 public override void document_change (JsRender.JsRender file )
387 if (this.change_queue_file != null && this.change_queue_file.path != file.path) {
388 this.document_change_force.begin(this.change_queue_file, this.change_queue_file_source, (o, res) => {
389 this.document_change_force.end(res);
394 this.change_queue_file = file;
401 public override async void document_change_force (JsRender.JsRender file, string contents)
403 if (!this.isReady()) {
408 GLib.debug ("LS send change");
409 var ar = new Json.Array();
410 var obj = new Json.Object();
411 obj.set_string_member("text", contents);
412 ar.add_object_element(obj);
413 var node = new Json.Node(Json.NodeType.ARRAY);
415 this.log(LanguageClientAction.CHANGE, file.path);
417 yield this.jsonrpc_client.send_notification_async (
418 "textDocument/didChange",
420 textDocument : this.buildDict ( ///TextDocumentItem;
421 uri: new GLib.Variant.string (file.to_url()),
422 version : new GLib.Variant.uint64 ( (uint64) file.version)
424 contentChanges : Json.gvariant_deserialize (node, null)
429 } catch( GLib.Error e) {
430 this.log(LanguageClientAction.ERROR_RPC, e.message);
431 GLib.debug ("LS change err %s", e.message);
437 // called by close window (on last window)...
438 public override void exit () throws GLib.Error
440 if (!this.isReady()) {
444 this.log(LanguageClientAction.TERM, "SEND exit");
446 this.jsonrpc_client.send_notification (
454 // not used currently..
455 public override async void shutdown () throws GLib.Error
457 if (!this.isReady()) {
460 this.log(LanguageClientAction.TERM, "SEND shutodwn");
461 this.sent_shutdown = true;
462 Variant? return_value;
463 yield this.jsonrpc_client.call_async (
469 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
471 //public async ??/symbol (string symbol) throws GLib.Error {
473 // and now for the important styff..
477 @triggerType 1 = typing or ctl-spac, 2 = tiggercharactres? 3= inside completion?
479 public override async Lsp.CompletionList? completion(JsRender.JsRender file, int line, int offset , int triggerType = 1) throws GLib.Error
481 /* partial_result_token , work_done_token context = null) */
482 GLib.debug("%s get completion %s @ %d:%d", this.get_type().name(), file.relpath, line, offset);
484 var ret = new Lsp.CompletionList();
486 if (!this.isReady()) {
487 GLib.debug("completion - language server not ready");
490 // make sure completion has the latest info..
491 //if (this.change_queue_file != null && this.change_queue_file.path != file.path) {
492 // this.document_change_real(this.change_queue_file, this.change_queue_file_source);
493 // this.change_queue_file != null;
495 this.log(LanguageClientAction.COMPLETE, "SEND complete %s @ %d:%d".printf(file.relpath, line, offset) );
497 Variant? return_value;
499 var args = this.buildDict (
500 context : this.buildDict ( ///CompletionContext;
501 triggerKind: new GLib.Variant.int32 (triggerType)
502 // triggerCharacter : new GLib.Variant.string ("")
504 textDocument : this.buildDict ( ///TextDocumentItem;
505 uri: new GLib.Variant.string (file.to_url()),
506 version : new GLib.Variant.uint64 ( (uint64) file.version)
508 position : this.buildDict (
509 line : new GLib.Variant.uint64 ( (uint) line) ,
510 character : new GLib.Variant.uint64 ( uint.max(0, (offset -1)))
514 GLib.debug ("textDocument/completion send with %s", Json.to_string (Json.gvariant_serialize (args), true));
516 yield this.jsonrpc_client.call_async (
517 "textDocument/completion",
524 //GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
525 var json = Json.gvariant_serialize (return_value);
528 if (json.get_node_type() == Json.NodeType.OBJECT) {
529 ret = Json.gobject_deserialize (typeof (Lsp.CompletionList), json) as Lsp.CompletionList;
530 this.log(LanguageClientAction.COMPLETE_REPLY, "GOT complete %d items".printf(ret.items.size) );
531 GLib.debug ("LS replied with Object");
535 if (json.get_node_type() != Json.NodeType.ARRAY) {
536 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
537 this.log(LanguageClientAction.ERROR_REPLY, "GOT something else??");
541 var ar = json.get_array();
543 for(var i = 0; i < ar.get_length(); i++ ) {
544 var add= Json.gobject_deserialize ( typeof (Lsp.CompletionItem), ar.get_element(i)) as Lsp.CompletionItem;
548 this.log(LanguageClientAction.COMPLETE_REPLY, "GOT array %d items".printf(ret.items.size) );
549 GLib.debug ("LS replied with Array");
554 //CompletionListInfo.itmems.parse_varient or CompletionListInfo.parsevarient
555 public override async Gee.ArrayList<Lsp.DocumentSymbol> syntax (JsRender.JsRender file) throws GLib.Error
557 /* partial_result_token , work_done_token context = null) */
558 GLib.debug("get syntax %s", file.relpath);
559 var ret = new Gee.ArrayList<Lsp.DocumentSymbol>();
561 if (!this.isReady()) {
564 Variant? return_value;
565 yield this.jsonrpc_client.call_async (
566 "textDocument/documentSymbol",
569 textDocument : this.buildDict ( ///TextDocumentItem;
570 uri: new GLib.Variant.string (file.to_url()),
571 version : new GLib.Variant.uint64 ( (uint64) file.version)
580 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
581 var json = Json.gvariant_serialize (return_value);
585 var ar = json.get_array();
586 for(var i = 0; i < ar.get_length(); i++ ) {
587 var add= Json.gobject_deserialize ( typeof (Lsp.DocumentSymbol), ar.get_element(i)) as Lsp.DocumentSymbol;