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 //GLib.debug("got notification %s : %s", method , Json.to_string (Json.gvariant_serialize (return_value), true));
245 this.onDiagnostic(return_value);
251 GLib.debug("got notification %s : %s", method , Json.to_string (Json.gvariant_serialize (return_value), true));
258 public void onDiagnostic(Variant? return_value)
260 //GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
261 var dg = Json.gobject_deserialize (typeof (Lsp.Diagnostics), Json.gvariant_serialize (return_value)) as Lsp.Diagnostics;
262 this.log(LanguageClientAction.DIAG, dg.filename);
263 var f = this.project.getByPath(dg.filename);
265 //GLib.debug("no file %s", dg.uri);
266 //this.project.updateErrorsforFile(null);
269 f.updateErrors( dg.diagnostics );
274 public override void document_open (JsRender.JsRender file)
276 if (!this.isReady()) {
279 if (!this.open_files.contains(file)) {
280 this.open_files.add(file);
283 GLib.debug ("LS sent open");
285 this.jsonrpc_client.send_notification (
286 "textDocument/didOpen",
288 textDocument : this.buildDict (
289 uri: new Variant.string (file.to_url()),
290 languageId : new Variant.string (file.language_id()),
291 version : new GLib.Variant.uint64 ( (uint64) file.version),
292 text : new Variant.string (file.toSource())
297 this.log(LanguageClientAction.OPEN, file.path);
298 } catch( GLib.Error e) {
299 this.log(LanguageClientAction.ERROR_RPC, e.message);
301 GLib.debug ("LS sent open err %s", e.message);
306 public override async void document_save (JsRender.JsRender file)
308 if (!this.isReady()) {
311 // save only really flags the file on the server - to actually force a change update - we need to
312 // flag it as changed.
313 yield this.document_change_force(file, file.toSource());
315 this.change_queue_file = null;
316 GLib.debug ("LS send save");
319 var args = this.buildDict (
320 textDocument : this.buildDict ( ///TextDocumentItem;
321 uri: new GLib.Variant.string (file.to_url()),
322 version : new GLib.Variant.uint64 ( (uint64) file.version)
326 //GLib.debug ("textDocument/save send with %s", Json.to_string (Json.gvariant_serialize (args), true));
330 yield this.jsonrpc_client.send_notification_async (
331 "textDocument/didSave",
335 this.log(LanguageClientAction.SAVE, file.path);
336 } catch( GLib.Error e) {
337 this.log(LanguageClientAction.ERROR_RPC, e.message);
338 GLib.debug ("LS save err %s", e.message);
344 public override void document_close (JsRender.JsRender file)
346 if (!this.isReady()) {
349 this.change_queue_file = null;
351 if (this.open_files.contains(file)) {
352 this.open_files.remove(file);
354 this.log(LanguageClientAction.CLOSE, file.path);
355 GLib.debug ("LS send close");
357 this.jsonrpc_client.send_notification (
358 "textDocument/didChange",
360 textDocument : this.buildDict ( ///TextDocumentItem;
361 uri: new GLib.Variant.string (file.to_url())
367 } catch( GLib.Error e) {
368 this.log(LanguageClientAction.ERROR_RPC, e.message);
369 GLib.debug ("LS close err %s", e.message);
377 public override void document_change (JsRender.JsRender file )
379 if (this.change_queue_file != null && this.change_queue_file.path != file.path) {
380 this.document_change_force.begin(this.change_queue_file, this.change_queue_file_source, (o, res) => {
381 this.document_change_force.end(res);
386 this.change_queue_file = file;
393 public override async void document_change_force (JsRender.JsRender file, string contents)
395 if (!this.isReady()) {
400 GLib.debug ("LS send change");
401 var ar = new Json.Array();
402 var obj = new Json.Object();
403 obj.set_string_member("text", contents);
404 ar.add_object_element(obj);
405 var node = new Json.Node(Json.NodeType.ARRAY);
407 this.log(LanguageClientAction.CHANGE, file.path);
409 yield this.jsonrpc_client.send_notification_async (
410 "textDocument/didChange",
412 textDocument : this.buildDict ( ///TextDocumentItem;
413 uri: new GLib.Variant.string (file.to_url()),
414 version : new GLib.Variant.uint64 ( (uint64) file.version)
416 contentChanges : Json.gvariant_deserialize (node, null)
421 } catch( GLib.Error e) {
422 this.log(LanguageClientAction.ERROR_RPC, e.message);
423 GLib.debug ("LS change err %s", e.message);
429 // called by close window (on last window)...
430 public override void exit () throws GLib.Error
432 if (!this.isReady()) {
436 this.log(LanguageClientAction.TERM, "SEND exit");
438 this.jsonrpc_client.send_notification (
446 // not used currently..
447 public override async void shutdown () throws GLib.Error
449 if (!this.isReady()) {
452 this.log(LanguageClientAction.TERM, "SEND shutodwn");
453 this.sent_shutdown = true;
454 Variant? return_value;
455 yield this.jsonrpc_client.call_async (
461 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
463 //public async ??/symbol (string symbol) throws GLib.Error {
465 // and now for the important styff..
469 @triggerType 1 = typing or ctl-spac, 2 = tiggercharactres? 3= inside completion?
471 public override async Lsp.CompletionList? completion(JsRender.JsRender file, int line, int offset , int triggerType = 1) throws GLib.Error
473 /* partial_result_token , work_done_token context = null) */
474 GLib.debug("%s get completion %s @ %d:%d", this.get_type().name(), file.relpath, line, offset);
476 var ret = new Lsp.CompletionList();
478 if (!this.isReady()) {
479 GLib.debug("completion - language server not ready");
482 // make sure completion has the latest info..
483 //if (this.change_queue_file != null && this.change_queue_file.path != file.path) {
484 // this.document_change_real(this.change_queue_file, this.change_queue_file_source);
485 // this.change_queue_file != null;
487 this.log(LanguageClientAction.COMPLETE, "SEND complete %s @ %d:%d".printf(file.relpath, line, offset) );
489 Variant? return_value;
491 var args = this.buildDict (
492 context : this.buildDict ( ///CompletionContext;
493 triggerKind: new GLib.Variant.int32 (triggerType)
494 // triggerCharacter : new GLib.Variant.string ("")
496 textDocument : this.buildDict ( ///TextDocumentItem;
497 uri: new GLib.Variant.string (file.to_url()),
498 version : new GLib.Variant.uint64 ( (uint64) file.version)
500 position : this.buildDict (
501 line : new GLib.Variant.uint64 ( (uint) line) ,
502 character : new GLib.Variant.uint64 ( uint.max(0, (offset -1)))
506 GLib.debug ("textDocument/completion send with %s", Json.to_string (Json.gvariant_serialize (args), true));
508 yield this.jsonrpc_client.call_async (
509 "textDocument/completion",
516 //GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
517 var json = Json.gvariant_serialize (return_value);
520 if (json.get_node_type() == Json.NodeType.OBJECT) {
521 ret = Json.gobject_deserialize (typeof (Lsp.CompletionList), json) as Lsp.CompletionList;
522 this.log(LanguageClientAction.COMPLETE_REPLY, "GOT complete %d items".printf(ret.items.size) );
523 GLib.debug ("LS replied with Object");
527 if (json.get_node_type() != Json.NodeType.ARRAY) {
528 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
529 this.log(LanguageClientAction.ERROR_REPLY, "GOT something else??");
533 var ar = json.get_array();
535 for(var i = 0; i < ar.get_length(); i++ ) {
536 var add= Json.gobject_deserialize ( typeof (Lsp.CompletionItem), ar.get_element(i)) as Lsp.CompletionItem;
540 this.log(LanguageClientAction.COMPLETE_REPLY, "GOT array %d items".printf(ret.items.size) );
541 GLib.debug ("LS replied with Array");
546 //CompletionListInfo.itmems.parse_varient or CompletionListInfo.parsevarient
547 public override async Gee.ArrayList<Lsp.DocumentSymbol> syntax (JsRender.JsRender file) throws GLib.Error
549 /* partial_result_token , work_done_token context = null) */
550 GLib.debug("get syntax %s", file.relpath);
551 var ret = new Gee.ArrayList<Lsp.DocumentSymbol>();
553 if (!this.isReady()) {
556 Variant? return_value;
557 yield this.jsonrpc_client.call_async (
558 "textDocument/documentSymbol",
561 textDocument : this.buildDict ( ///TextDocumentItem;
562 uri: new GLib.Variant.string (file.to_url()),
563 version : new GLib.Variant.uint64 ( (uint64) file.version)
572 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
573 var json = Json.gvariant_serialize (return_value);
577 var ar = json.get_array();
578 for(var i = 0; i < ar.get_length(); i++ ) {
579 var add= Json.gobject_deserialize ( typeof (Lsp.DocumentSymbol), ar.get_element(i)) as Lsp.DocumentSymbol;