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.
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.open_files = new Gee.ArrayList<JsRender.JsRender>((a,b) => {
155 return a.path == b.path;
157 this.initialized = true;
159 } catch (GLib.Error e) {
160 GLib.debug ("LS replied with error %s", e.message);
170 if (this.launcher == null) {
173 this.in_close = true;
174 GLib.debug("onClose called");
176 if (this.jsonrpc_client != null) {
178 this.jsonrpc_client.close();
179 } catch (GLib.Error e) {
180 GLib.debug("rpc Error close error %s", e.message);
183 if (this.subprocess_stream != null) {
185 this.subprocess_stream.close();
186 } catch (GLib.Error e) {
187 GLib.debug("stream Error close %s", e.message);
190 if (this.subprocess != null) {
191 this.subprocess.force_exit();
193 if (this.launcher != null) {
194 this.launcher.close();
197 this.launcher = null;
198 this.subprocess = null;
199 this.jsonrpc_client = null;
201 this.in_close = false;
204 public async void restartServer()
210 public bool isReady()
213 this.log(LanguageClientAction.RESTART,"closed is set - restarting");
214 GLib.debug("server stopped = restarting");
215 this.initialized = false;
217 GLib.MainLoop loop = new GLib.MainLoop ();
218 this.restartServer.begin ((obj, async_res) => {
219 this.restartServer.end(async_res);
222 return false; // can't do an operation yet?
226 if (!this.initialized) {
227 GLib.debug("Server has not been initialized");
230 if (this.sent_shutdown) {
231 GLib.debug("Server has been started its shutting down process");
241 public void onNotification(string method, Variant? return_value)
244 case "textDocument/publishDiagnostics":
245 //GLib.debug("got notification %s : %s", method , Json.to_string (Json.gvariant_serialize (return_value), true));
246 this.onDiagnostic(return_value);
252 GLib.debug("got notification %s : %s", method , Json.to_string (Json.gvariant_serialize (return_value), true));
259 public void onDiagnostic(Variant? return_value)
261 //GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
262 var dg = Json.gobject_deserialize (typeof (Lsp.Diagnostics), Json.gvariant_serialize (return_value)) as Lsp.Diagnostics;
263 this.log(LanguageClientAction.DIAG, dg.filename);
264 var f = this.project.getByPath(dg.filename);
266 //GLib.debug("no file %s", dg.uri);
267 //this.project.updateErrorsforFile(null);
270 //GLib.debug("got Diagnostics for %s", f.path);
271 f.updateErrors( dg.diagnostics );
276 public override void document_open (JsRender.JsRender file)
278 if (!this.isReady()) {
281 if (this.open_files.contains(file)) {
284 this.open_files.add(file);
287 GLib.debug ("LS sent open");
289 this.jsonrpc_client.send_notification (
290 "textDocument/didOpen",
292 textDocument : this.buildDict (
293 uri: new Variant.string (file.to_url()),
294 languageId : new Variant.string (file.language_id()),
295 version : new GLib.Variant.uint64 ( (uint64) file.version),
296 text : new Variant.string (file.toSource())
301 this.log(LanguageClientAction.OPEN, file.path);
302 } catch( GLib.Error e) {
303 this.log(LanguageClientAction.ERROR_RPC, e.message);
305 GLib.debug ("LS sent open err %s", e.message);
310 public override async void document_save (JsRender.JsRender file)
312 if (!this.isReady()) {
315 // save only really flags the file on the server - to actually force a change update - we need to
316 // flag it as changed.
317 yield this.document_change_force(file, file.toSource());
319 this.change_queue_file = null;
320 GLib.debug ("LS send save");
323 var args = this.buildDict (
324 textDocument : this.buildDict ( ///TextDocumentItem;
325 uri: new GLib.Variant.string (file.to_url()),
326 version : new GLib.Variant.uint64 ( (uint64) file.version)
330 //GLib.debug ("textDocument/save send with %s", Json.to_string (Json.gvariant_serialize (args), true));
334 yield this.jsonrpc_client.send_notification_async (
335 "textDocument/didSave",
339 this.log(LanguageClientAction.SAVE, file.path);
340 } catch( GLib.Error e) {
341 this.log(LanguageClientAction.ERROR_RPC, e.message);
342 GLib.debug ("LS save err %s", e.message);
348 public override void document_close (JsRender.JsRender file)
350 if (!this.isReady()) {
353 this.change_queue_file = null;
355 if (this.open_files.contains(file)) {
356 this.open_files.remove(file);
358 this.log(LanguageClientAction.CLOSE, file.path);
359 GLib.debug ("LS send close");
361 this.jsonrpc_client.send_notification (
362 "textDocument/didChange",
364 textDocument : this.buildDict ( ///TextDocumentItem;
365 uri: new GLib.Variant.string (file.to_url())
371 } catch( GLib.Error e) {
372 this.log(LanguageClientAction.ERROR_RPC, e.message);
373 GLib.debug ("LS close err %s", e.message);
381 public override void document_change (JsRender.JsRender file )
383 if (this.change_queue_file != null && this.change_queue_file.path != file.path) {
384 this.document_change_force.begin(this.change_queue_file, this.change_queue_file_source, (o, res) => {
385 this.document_change_force.end(res);
390 this.change_queue_file = file;
397 public override async void document_change_force (JsRender.JsRender file, string contents)
399 if (!this.isReady()) {
402 if (!this.open_files.contains(file)) {
403 this.document_open(file);
406 GLib.debug ("LS send change %s rev %d", file.path, file.version);
407 var ar = new Json.Array();
408 var obj = new Json.Object();
409 obj.set_string_member("text", contents);
410 ar.add_object_element(obj);
411 var node = new Json.Node(Json.NodeType.ARRAY);
413 this.log(LanguageClientAction.CHANGE, file.path);
415 yield this.jsonrpc_client.send_notification_async (
416 "textDocument/didChange",
418 textDocument : this.buildDict ( ///TextDocumentItem;
419 uri: new GLib.Variant.string (file.to_url()),
420 version : new GLib.Variant.uint64 ( (uint64) file.version)
422 contentChanges : Json.gvariant_deserialize (node, null)
427 } catch( GLib.Error e) {
428 this.log(LanguageClientAction.ERROR_RPC, e.message);
429 GLib.debug ("LS change err %s", e.message);
435 // called by close window (on last window)...
436 public override void exit () throws GLib.Error
438 if (!this.isReady()) {
442 this.log(LanguageClientAction.TERM, "SEND exit");
444 this.jsonrpc_client.send_notification (
452 // not used currently..
453 public override async void shutdown () throws GLib.Error
455 if (!this.isReady()) {
458 this.log(LanguageClientAction.TERM, "SEND shutodwn");
459 this.sent_shutdown = true;
460 Variant? return_value;
461 yield this.jsonrpc_client.call_async (
467 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
469 //public async ??/symbol (string symbol) throws GLib.Error {
471 // and now for the important styff..
475 @triggerType 1 = typing or ctl-spac, 2 = tiggercharactres? 3= inside completion?
477 public override async Lsp.CompletionList? completion(JsRender.JsRender file, int line, int offset , int triggerType = 1) throws GLib.Error
479 /* partial_result_token , work_done_token context = null) */
480 GLib.debug("%s get completion %s @ %d:%d", this.get_type().name(), file.relpath, line, offset);
482 var ret = new Lsp.CompletionList();
484 if (!this.isReady()) {
485 GLib.debug("completion - language server not ready");
488 // make sure completion has the latest info..
489 //if (this.change_queue_file != null && this.change_queue_file.path != file.path) {
490 // this.document_change_real(this.change_queue_file, this.change_queue_file_source);
491 // this.change_queue_file != null;
493 this.log(LanguageClientAction.COMPLETE, "SEND complete %s @ %d:%d".printf(file.relpath, line, offset) );
495 Variant? return_value;
497 var args = this.buildDict (
498 context : this.buildDict ( ///CompletionContext;
499 triggerKind: new GLib.Variant.int32 (triggerType)
500 // triggerCharacter : new GLib.Variant.string ("")
502 textDocument : this.buildDict ( ///TextDocumentItem;
503 uri: new GLib.Variant.string (file.to_url()),
504 version : new GLib.Variant.uint64 ( (uint64) file.version)
506 position : this.buildDict (
507 line : new GLib.Variant.uint64 ( (uint) line) ,
508 character : new GLib.Variant.uint64 ( uint.max(0, (offset -1)))
512 GLib.debug ("textDocument/completion send with %s", Json.to_string (Json.gvariant_serialize (args), true));
514 yield this.jsonrpc_client.call_async (
515 "textDocument/completion",
522 //GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
523 var json = Json.gvariant_serialize (return_value);
526 if (json.get_node_type() == Json.NodeType.OBJECT) {
527 ret = Json.gobject_deserialize (typeof (Lsp.CompletionList), json) as Lsp.CompletionList;
528 this.log(LanguageClientAction.COMPLETE_REPLY, "GOT complete %d items".printf(ret.items.size) );
529 GLib.debug ("LS replied with Object");
533 if (json.get_node_type() != Json.NodeType.ARRAY) {
534 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
535 this.log(LanguageClientAction.ERROR_REPLY, "GOT something else??");
539 var ar = json.get_array();
541 for(var i = 0; i < ar.get_length(); i++ ) {
542 var add= Json.gobject_deserialize ( typeof (Lsp.CompletionItem), ar.get_element(i)) as Lsp.CompletionItem;
546 this.log(LanguageClientAction.COMPLETE_REPLY, "GOT array %d items".printf(ret.items.size) );
547 GLib.debug ("LS replied with Array");
552 //CompletionListInfo.itmems.parse_varient or CompletionListInfo.parsevarient
553 public override async Gee.ArrayList<Lsp.DocumentSymbol> syntax (JsRender.JsRender file) throws GLib.Error
555 /* partial_result_token , work_done_token context = null) */
556 GLib.debug("get syntax %s", file.relpath);
557 var ret = new Gee.ArrayList<Lsp.DocumentSymbol>();
559 if (!this.isReady()) {
562 Variant? return_value;
563 yield this.jsonrpc_client.call_async (
564 "textDocument/documentSymbol",
567 textDocument : this.buildDict ( ///TextDocumentItem;
568 uri: new GLib.Variant.string (file.to_url()),
569 version : new GLib.Variant.uint64 ( (uint64) file.version)
578 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
579 var json = Json.gvariant_serialize (return_value);
583 var ar = json.get_array();
584 for(var i = 0; i < ar.get_length(); i++ ) {
585 var add= Json.gobject_deserialize ( typeof (Lsp.DocumentSymbol), ar.get_element(i)) as Lsp.DocumentSymbol;