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) {
58 if (this.countdown < 0){
59 this.document_change_force.begin(this.change_queue_file, this.change_queue_file_source, (o, res) => {
60 this.document_change_force.end(res);
62 this.change_queue_file = null;
72 public bool initProcess(string process_path)
75 this.log(LanguageClientAction.LAUNCH, process_path);
76 GLib.debug("Launching %s", process_path);
77 this.launcher = new GLib.SubprocessLauncher (SubprocessFlags.STDIN_PIPE | SubprocessFlags.STDOUT_PIPE);
78 this.launcher.set_environ(GLib.Environ.get());
82 this.subprocess = launcher.spawnv ({ process_path });
84 this.subprocess.wait_async.begin( null, ( obj,res ) => {
86 this.subprocess.wait_async.end(res);
87 } catch (GLib.Error e) {
88 this.log(LanguageClientAction.ERROR_START, e.message);
89 GLib.debug("subprocess startup error %s", e.message);
91 this.log(LanguageClientAction.EXIT, "process ended");
92 GLib.debug("Subprocess ended %s", process_path);
96 var input_stream = this.subprocess.get_stdout_pipe ();
97 var output_stream = this.subprocess.get_stdin_pipe ();
99 if (input_stream is GLib.UnixInputStream && output_stream is GLib.UnixOutputStream) {
101 if (!GLib.Unix.set_fd_nonblocking(((GLib.UnixInputStream)input_stream).fd, true)
102 || !GLib.Unix.set_fd_nonblocking (((GLib.UnixOutputStream)output_stream).fd, true))
104 GLib.debug("could not set pipes to nonblocking");
109 this.subprocess_stream = new GLib.SimpleIOStream (input_stream, output_stream);
110 this.accept_io_stream ( this.subprocess_stream);
111 } catch (GLib.Error e) {
112 this.log(LanguageClientAction.ERROR_START, e.message);
113 GLib.debug("subprocess startup error %s", e.message);
119 bool in_close = false;
120 public override void client_accepted (Jsonrpc.Client client)
122 if (this.jsonrpc_client == null) {
123 this.jsonrpc_client = client;
125 GLib.debug("client accepted connection - calling init server");
126 this.log(LanguageClientAction.ACCEPT, "client accepted");
128 this.jsonrpc_client.notification.connect((method, paramz) => {
129 this.onNotification(method, paramz);
132 this.jsonrpc_client.failed.connect(() => {
133 this.log(LanguageClientAction.ERROR_RPC, "client failed");
136 GLib.debug("language server server has failed");
139 this.initialize_server ();
146 public override void initialize_server() {
148 Variant? return_value;
149 this.jsonrpc_client.call (
152 processId: new Variant.int32 ((int32) Posix.getpid ()),
153 rootPath: new Variant.string (this.project.path),
154 rootUri: new Variant.string (File.new_for_path (this.project.path).get_uri ())
159 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
160 this.open_files = new Gee.ArrayList<JsRender.JsRender>((a,b) => {
161 return a.path == b.path;
163 this.initialized = true;
165 } catch (GLib.Error e) {
166 GLib.debug ("LS replied with error %s", e.message);
176 if (this.launcher == null) {
179 this.in_close = true;
180 GLib.debug("onClose called");
182 if (this.jsonrpc_client != null) {
184 this.jsonrpc_client.close();
185 } catch (GLib.Error e) {
186 GLib.debug("rpc Error close error %s", e.message);
189 if (this.subprocess_stream != null) {
191 this.subprocess_stream.close();
192 } catch (GLib.Error e) {
193 GLib.debug("stream Error close %s", e.message);
196 if (this.subprocess != null) {
197 this.subprocess.force_exit();
199 if (this.launcher != null) {
200 this.launcher.close();
203 this.launcher = null;
204 this.subprocess = null;
205 this.jsonrpc_client = null;
207 this.in_close = false;
210 public async void restartServer()
216 public bool isReady()
219 this.log(LanguageClientAction.RESTART,"closed is set - restarting");
220 GLib.debug("server stopped = restarting");
221 this.initialized = false;
223 GLib.MainLoop loop = new GLib.MainLoop ();
224 this.restartServer.begin ((obj, async_res) => {
225 this.restartServer.end(async_res);
228 return false; // can't do an operation yet?
232 if (!this.initialized) {
233 GLib.debug("Server has not been initialized");
236 if (this.sent_shutdown) {
237 GLib.debug("Server has been started its shutting down process");
247 public void onNotification(string method, Variant? return_value)
250 case "textDocument/publishDiagnostics":
251 //GLib.debug("got notification %s : %s", method , Json.to_string (Json.gvariant_serialize (return_value), true));
252 this.onDiagnostic(return_value);
258 GLib.debug("got notification %s : %s", method , Json.to_string (Json.gvariant_serialize (return_value), true));
265 public void onDiagnostic(Variant? return_value)
267 //GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
268 var dg = Json.gobject_deserialize (typeof (Lsp.Diagnostics), Json.gvariant_serialize (return_value)) as Lsp.Diagnostics;
269 this.log(LanguageClientAction.DIAG, dg.filename);
270 var f = this.project.getByPath(dg.filename);
272 //GLib.debug("no file %s", dg.uri);
273 //this.project.updateErrorsforFile(null);
276 //GLib.debug("got Diagnostics for %s", f.path);
277 f.updateErrors( dg.diagnostics );
282 public override void document_open (JsRender.JsRender file)
284 if (!this.isReady()) {
287 if (this.open_files.contains(file)) {
290 this.open_files.add(file);
293 GLib.debug ("LS sent open");
295 this.jsonrpc_client.send_notification (
296 "textDocument/didOpen",
298 textDocument : this.buildDict (
299 uri: new Variant.string (file.to_url()),
300 languageId : new Variant.string (file.language_id()),
301 version : new GLib.Variant.uint64 ( (uint64) file.version),
302 text : new Variant.string (file.toSource())
307 this.log(LanguageClientAction.OPEN, file.path);
308 } catch( GLib.Error e) {
309 this.log(LanguageClientAction.ERROR_RPC, e.message);
311 GLib.debug ("LS sent open err %s", e.message);
316 public override async void document_save (JsRender.JsRender file)
318 if (!this.isReady()) {
321 // save only really flags the file on the server - to actually force a change update - we need to
322 // flag it as changed.
323 yield this.document_change_force(file, file.toSource());
325 this.change_queue_file = null;
326 GLib.debug ("LS send save");
329 var args = this.buildDict (
330 textDocument : this.buildDict ( ///TextDocumentItem;
331 uri: new GLib.Variant.string (file.to_url()),
332 version : new GLib.Variant.uint64 ( (uint64) file.version)
336 //GLib.debug ("textDocument/save send with %s", Json.to_string (Json.gvariant_serialize (args), true));
340 yield this.jsonrpc_client.send_notification_async (
341 "textDocument/didSave",
345 this.log(LanguageClientAction.SAVE, file.path);
346 } catch( GLib.Error e) {
347 this.log(LanguageClientAction.ERROR_RPC, e.message);
348 GLib.debug ("LS save err %s", e.message);
354 public override void document_close (JsRender.JsRender file)
356 if (!this.isReady()) {
359 this.change_queue_file = null;
361 if (this.open_files.contains(file)) {
362 this.open_files.remove(file);
364 this.log(LanguageClientAction.CLOSE, file.path);
365 GLib.debug ("LS send close");
367 this.jsonrpc_client.send_notification (
368 "textDocument/didChange",
370 textDocument : this.buildDict ( ///TextDocumentItem;
371 uri: new GLib.Variant.string (file.to_url())
377 } catch( GLib.Error e) {
378 this.log(LanguageClientAction.ERROR_RPC, e.message);
379 GLib.debug ("LS close err %s", e.message);
387 public override void document_change (JsRender.JsRender file )
389 if (this.change_queue_file != null && this.change_queue_file.path != file.path) {
390 this.document_change_force.begin(this.change_queue_file, this.change_queue_file_source, (o, res) => {
391 this.document_change_force.end(res);
396 this.change_queue_file = file;
403 public override async void document_change_force (JsRender.JsRender file, string contents)
407 if (!this.isReady()) {
410 this.countdown = 9; // not really relivant..
411 this.change_queue_file = null; // this is more important..
413 if (!this.open_files.contains(file)) {
414 this.document_open(file);
417 GLib.debug ("LS send change %s rev %d", file.path, file.version);
418 var ar = new Json.Array();
419 var obj = new Json.Object();
420 obj.set_string_member("text", contents);
421 ar.add_object_element(obj);
422 var node = new Json.Node(Json.NodeType.ARRAY);
424 this.log(LanguageClientAction.CHANGE, file.path);
426 yield this.jsonrpc_client.send_notification_async (
427 "textDocument/didChange",
429 textDocument : this.buildDict ( ///TextDocumentItem;
430 uri: new GLib.Variant.string (file.to_url()),
431 version : new GLib.Variant.uint64 ( (uint64) file.version)
433 contentChanges : Json.gvariant_deserialize (node, null)
438 } catch( GLib.Error e) {
439 this.log(LanguageClientAction.ERROR_RPC, e.message);
440 GLib.debug ("LS change err %s", e.message);
446 // called by close window (on last window)...
447 public override void exit () throws GLib.Error
449 if (!this.isReady()) {
453 this.log(LanguageClientAction.TERM, "SEND exit");
455 this.jsonrpc_client.send_notification (
463 // not used currently..
464 public override async void shutdown () throws GLib.Error
466 if (!this.isReady()) {
469 this.log(LanguageClientAction.TERM, "SEND shutodwn");
470 this.sent_shutdown = true;
471 Variant? return_value;
472 yield this.jsonrpc_client.call_async (
478 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
480 //public async ??/symbol (string symbol) throws GLib.Error {
482 // and now for the important styff..
486 @triggerType 1 = typing or ctl-spac, 2 = tiggercharactres? 3= inside completion?
488 public override async Lsp.CompletionList? completion(JsRender.JsRender file, int line, int offset , int triggerType = 1) throws GLib.Error
490 /* partial_result_token , work_done_token context = null) */
491 GLib.debug("%s get completion %s @ %d:%d", this.get_type().name(), file.relpath, line, offset);
493 var ret = new Lsp.CompletionList();
495 if (!this.isReady()) {
496 GLib.debug("completion - language server not ready");
499 // make sure completion has the latest info..
500 //if (this.change_queue_file != null && this.change_queue_file.path != file.path) {
501 // this.document_change_real(this.change_queue_file, this.change_queue_file_source);
502 // this.change_queue_file != null;
504 this.log(LanguageClientAction.COMPLETE, "SEND complete %s @ %d:%d".printf(file.relpath, line, offset) );
506 Variant? return_value;
508 var args = this.buildDict (
509 context : this.buildDict ( ///CompletionContext;
510 triggerKind: new GLib.Variant.int32 (triggerType)
511 // triggerCharacter : new GLib.Variant.string ("")
513 textDocument : this.buildDict ( ///TextDocumentItem;
514 uri: new GLib.Variant.string (file.to_url()),
515 version : new GLib.Variant.uint64 ( (uint64) file.version)
517 position : this.buildDict (
518 line : new GLib.Variant.uint64 ( (uint) line) ,
519 character : new GLib.Variant.uint64 ( uint.max(0, (offset -1)))
523 GLib.debug ("textDocument/completion send with %s", Json.to_string (Json.gvariant_serialize (args), true));
525 yield this.jsonrpc_client.call_async (
526 "textDocument/completion",
533 //GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
534 var json = Json.gvariant_serialize (return_value);
537 if (json.get_node_type() == Json.NodeType.OBJECT) {
538 ret = Json.gobject_deserialize (typeof (Lsp.CompletionList), json) as Lsp.CompletionList;
539 this.log(LanguageClientAction.COMPLETE_REPLY, "GOT complete %d items".printf(ret.items.size) );
540 GLib.debug ("LS replied with Object");
544 if (json.get_node_type() != Json.NodeType.ARRAY) {
545 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
546 this.log(LanguageClientAction.ERROR_REPLY, "GOT something else??");
550 var ar = json.get_array();
552 for(var i = 0; i < ar.get_length(); i++ ) {
553 var add= Json.gobject_deserialize ( typeof (Lsp.CompletionItem), ar.get_element(i)) as Lsp.CompletionItem;
557 this.log(LanguageClientAction.COMPLETE_REPLY, "GOT array %d items".printf(ret.items.size) );
558 GLib.debug ("LS replied with Array");
563 //CompletionListInfo.itmems.parse_varient or CompletionListInfo.parsevarient
564 public override async Gee.ArrayList<Lsp.DocumentSymbol> syntax (JsRender.JsRender file) throws GLib.Error
566 /* partial_result_token , work_done_token context = null) */
567 GLib.debug("get syntax %s", file.relpath);
568 var ret = new Gee.ArrayList<Lsp.DocumentSymbol>();
570 if (!this.isReady()) {
573 Variant? return_value;
574 yield this.jsonrpc_client.call_async (
575 "textDocument/documentSymbol",
578 textDocument : this.buildDict ( ///TextDocumentItem;
579 uri: new GLib.Variant.string (file.to_url()),
580 version : new GLib.Variant.uint64 ( (uint64) file.version)
589 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
590 var json = Json.gvariant_serialize (return_value);
594 var ar = json.get_array();
595 for(var i = 0; i < ar.get_length(); i++ ) {
596 var add= Json.gobject_deserialize ( typeof (Lsp.DocumentSymbol), ar.get_element(i)) as Lsp.DocumentSymbol;