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(this.change_queue_file, this.change_queue_file_source);
54 this.change_queue_file = null;
64 public bool initProcess(string process_path)
67 this.log(LanguageClientAction.LAUNCH, process_path);
68 GLib.debug("Launching %s", process_path);
69 this.launcher = new GLib.SubprocessLauncher (SubprocessFlags.STDIN_PIPE | SubprocessFlags.STDOUT_PIPE);
70 this.launcher.set_environ(GLib.Environ.get());
74 this.subprocess = launcher.spawnv ({ process_path });
76 this.subprocess.wait_async.begin( null, ( obj,res ) => {
78 this.subprocess.wait_async.end(res);
79 } catch (GLib.Error e) {
80 this.log(LanguageClientAction.ERROR_START, e.message);
81 GLib.debug("subprocess startup error %s", e.message);
83 this.log(LanguageClientAction.EXIT, "process ended");
84 GLib.debug("Subprocess ended %s", process_path);
88 var input_stream = this.subprocess.get_stdout_pipe ();
89 var output_stream = this.subprocess.get_stdin_pipe ();
91 if (input_stream is GLib.UnixInputStream && output_stream is GLib.UnixOutputStream) {
93 if (!GLib.Unix.set_fd_nonblocking(((GLib.UnixInputStream)input_stream).fd, true)
94 || !GLib.Unix.set_fd_nonblocking (((GLib.UnixOutputStream)output_stream).fd, true))
96 GLib.debug("could not set pipes to nonblocking");
101 this.subprocess_stream = new GLib.SimpleIOStream (input_stream, output_stream);
102 this.accept_io_stream ( this.subprocess_stream);
103 } catch (GLib.Error e) {
104 this.log(LanguageClientAction.ERROR_START, e.message);
105 GLib.debug("subprocess startup error %s", e.message);
111 bool in_close = false;
112 public override void client_accepted (Jsonrpc.Client client)
114 if (this.jsonrpc_client == null) {
115 this.jsonrpc_client = client;
117 GLib.debug("client accepted connection - calling init server");
118 this.log(LanguageClientAction.ACCEPT, "client accepted");
120 this.jsonrpc_client.notification.connect((method, paramz) => {
121 this.onNotification(method, paramz);
124 this.jsonrpc_client.failed.connect(() => {
125 this.log(LanguageClientAction.ERROR_RPC, "client failed");
128 GLib.debug("language server server has failed");
131 this.initialize_server ();
138 public override void initialize_server() {
140 Variant? return_value;
141 this.jsonrpc_client.call (
144 processId: new Variant.int32 ((int32) Posix.getpid ()),
145 rootPath: new Variant.string (this.project.path),
146 rootUri: new Variant.string (File.new_for_path (this.project.path).get_uri ())
151 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
152 this.initialized = true;
154 } catch (GLib.Error e) {
155 GLib.debug ("LS replied with error %s", e.message);
165 if (this.launcher == null) {
168 this.in_close = true;
169 GLib.debug("onClose called");
171 if (this.jsonrpc_client != null) {
173 this.jsonrpc_client.close();
174 } catch (GLib.Error e) {
175 GLib.debug("rpc Error close error %s", e.message);
178 if (this.subprocess_stream != null) {
180 this.subprocess_stream.close();
181 } catch (GLib.Error e) {
182 GLib.debug("stream Error close %s", e.message);
185 if (this.subprocess != null) {
186 this.subprocess.force_exit();
188 if (this.launcher != null) {
189 this.launcher.close();
192 this.launcher = null;
193 this.subprocess = null;
194 this.jsonrpc_client = null;
196 this.in_close = false;
200 public bool isReady()
203 this.log(LanguageClientAction.RESTART,"closed is set - restarting");
204 GLib.debug("server stopped = restarting");
205 this.initialized = false;
208 foreach(var f in this.open_files) {
209 this.document_open(f);
211 return false; // can't do an operation yet?
215 if (!this.initialized) {
216 GLib.debug("Server has not been initialized");
219 if (this.sent_shutdown) {
220 GLib.debug("Server has been started its shutting down process");
230 public void onNotification(string method, Variant? return_value)
233 case "textDocument/publishDiagnostics":
234 this.onDiagnostic(return_value);
240 GLib.debug("got notification %s : %s", method , Json.to_string (Json.gvariant_serialize (return_value), true));
247 public void onDiagnostic(Variant? return_value)
250 var dg = Json.gobject_deserialize (typeof (Lsp.Diagnostics), Json.gvariant_serialize (return_value)) as Lsp.Diagnostics;
251 this.log(LanguageClientAction.DIAG, dg.filename);
252 var f = this.project.getByPath(dg.filename);
254 //GLib.debug("no file %s", dg.uri);
255 this.project.updateErrorsforFile(null);
258 foreach(var v in f.errorsByType.values) {
261 foreach(var diag in dg.diagnostics) {
262 var ce = new CompileError.new_from_diagnostic(f, diag);
263 if (!f.errorsByType.has_key(ce.category)) {
264 f.errorsByType.set(ce.category, new GLib.ListStore(typeof(CompileError)));
266 f.errorsByType.get(ce.category).append(ce);
268 f.project.updateErrorsforFile(f);
272 public override void document_open (JsRender.JsRender file)
274 if (!this.isReady()) {
277 if (!this.open_files.contains(file)) {
278 this.open_files.add(file);
281 GLib.debug ("LS sent open");
283 this.jsonrpc_client.send_notification (
284 "textDocument/didOpen",
286 textDocument : this.buildDict (
287 uri: new Variant.string (file.to_url()),
288 languageId : new Variant.string (file.language_id()),
289 version : new GLib.Variant.uint64 ( (uint64) file.version),
290 text : new Variant.string (file.toSource())
295 this.log(LanguageClientAction.OPEN, file.path);
296 } catch( GLib.Error e) {
297 this.log(LanguageClientAction.ERROR_RPC, e.message);
299 GLib.debug ("LS sent open err %s", e.message);
304 public override void document_save (JsRender.JsRender file)
306 if (!this.isReady()) {
309 this.change_queue_file = null;
310 GLib.debug ("LS send save");
312 this.jsonrpc_client.send_notification (
313 "textDocument/didChange",
315 textDocument : this.buildDict ( ///TextDocumentItem;
316 uri: new GLib.Variant.string (file.to_url()),
317 version : new GLib.Variant.uint64 ( (uint64) file.version)
322 this.log(LanguageClientAction.SAVE, file.path);
323 } catch( GLib.Error e) {
324 this.log(LanguageClientAction.ERROR_RPC, e.message);
325 GLib.debug ("LS save err %s", e.message);
331 public override void document_close (JsRender.JsRender file)
333 if (!this.isReady()) {
336 this.change_queue_file = null;
338 if (this.open_files.contains(file)) {
339 this.open_files.remove(file);
341 this.log(LanguageClientAction.CLOSE, file.path);
342 GLib.debug ("LS send close");
344 this.jsonrpc_client.send_notification (
345 "textDocument/didChange",
347 textDocument : this.buildDict ( ///TextDocumentItem;
348 uri: new GLib.Variant.string (file.to_url())
354 } catch( GLib.Error e) {
355 this.log(LanguageClientAction.ERROR_RPC, e.message);
356 GLib.debug ("LS close err %s", e.message);
364 public override void document_change (JsRender.JsRender file )
366 if (this.change_queue_file != null && this.change_queue_file.path != file.path) {
367 this.document_change_force(this.change_queue_file, this.change_queue_file_source);
371 this.change_queue_file = file;
378 public override void document_change_force (JsRender.JsRender file, string contents)
380 if (!this.isReady()) {
385 GLib.debug ("LS send change");
386 var ar = new Json.Array();
387 var obj = new Json.Object();
388 obj.set_string_member("text", contents);
389 ar.add_object_element(obj);
390 var node = new Json.Node(Json.NodeType.ARRAY);
392 this.log(LanguageClientAction.CHANGE, file.path);
394 this.jsonrpc_client.send_notification (
395 "textDocument/didChange",
397 textDocument : this.buildDict ( ///TextDocumentItem;
398 uri: new GLib.Variant.string (file.to_url()),
399 version : new GLib.Variant.uint64 ( (uint64) file.version)
401 contentChanges : Json.gvariant_deserialize (node, null)
406 } catch( GLib.Error e) {
407 this.log(LanguageClientAction.ERROR_RPC, e.message);
408 GLib.debug ("LS change err %s", e.message);
414 // called by close window (on last window)...
415 public override void exit () throws GLib.Error
417 if (!this.isReady()) {
421 this.log(LanguageClientAction.TERM, "SEND exit");
423 this.jsonrpc_client.send_notification (
431 // not used currently..
432 public override async void shutdown () throws GLib.Error
434 if (!this.isReady()) {
437 this.log(LanguageClientAction.TERM, "SEND shutodwn");
438 this.sent_shutdown = true;
439 Variant? return_value;
440 yield this.jsonrpc_client.call_async (
446 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
448 //public async ??/symbol (string symbol) throws GLib.Error {
450 // and now for the important styff..
454 @triggerType 1 = typing or ctl-spac, 2 = tiggercharactres? 3= inside completion?
456 public override async Lsp.CompletionList? completion(JsRender.JsRender file, int line, int offset , int triggerType = 1) throws GLib.Error
458 /* partial_result_token , work_done_token context = null) */
459 GLib.debug("%s get completion %s @ %d:%d", this.get_type().name(), file.relpath, line, offset);
461 var ret = new Lsp.CompletionList();
463 if (!this.isReady()) {
464 GLib.debug("completion - language server not ready");
467 // make sure completion has the latest info..
468 //if (this.change_queue_file != null && this.change_queue_file.path != file.path) {
469 // this.document_change_real(this.change_queue_file, this.change_queue_file_source);
470 // this.change_queue_file != null;
472 this.log(LanguageClientAction.COMPLETE, "SEND complete %s @ %d:%d".printf(file.relpath, line, offset) );
474 Variant? return_value;
476 var args = this.buildDict (
477 context : this.buildDict ( ///CompletionContext;
478 triggerKind: new GLib.Variant.int32 (triggerType)
479 // triggerCharacter : new GLib.Variant.string ("")
481 textDocument : this.buildDict ( ///TextDocumentItem;
482 uri: new GLib.Variant.string (file.to_url()),
483 version : new GLib.Variant.uint64 ( (uint64) file.version)
485 position : this.buildDict (
486 line : new GLib.Variant.uint64 ( (uint) line) ,
487 character : new GLib.Variant.uint64 ( uint.max(0, (offset -1)))
491 GLib.debug ("textDocument/completion send with %s", Json.to_string (Json.gvariant_serialize (args), true));
493 yield this.jsonrpc_client.call_async (
494 "textDocument/completion",
501 //GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
502 var json = Json.gvariant_serialize (return_value);
505 if (json.get_node_type() == Json.NodeType.OBJECT) {
506 ret = Json.gobject_deserialize (typeof (Lsp.CompletionList), json) as Lsp.CompletionList;
507 this.log(LanguageClientAction.COMPLETE_REPLY, "GOT complete %d items".printf(ret.items.size) );
508 GLib.debug ("LS replied with Object");
512 if (json.get_node_type() != Json.NodeType.ARRAY) {
513 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
514 this.log(LanguageClientAction.ERROR_REPLY, "GOT something else??");
518 var ar = json.get_array();
520 for(var i = 0; i < ar.get_length(); i++ ) {
521 var add= Json.gobject_deserialize ( typeof (Lsp.CompletionItem), ar.get_element(i)) as Lsp.CompletionItem;
525 this.log(LanguageClientAction.COMPLETE_REPLY, "GOT array %d items".printf(ret.items.size) );
526 GLib.debug ("LS replied with Array");
531 //CompletionListInfo.itmems.parse_varient or CompletionListInfo.parsevarient
532 public override async Gee.ArrayList<Lsp.DocumentSymbol> syntax (JsRender.JsRender file) throws GLib.Error
534 /* partial_result_token , work_done_token context = null) */
535 GLib.debug("get syntax %s", file.relpath);
536 var ret = new Gee.ArrayList<Lsp.DocumentSymbol>();
538 if (!this.isReady()) {
541 Variant? return_value;
542 yield this.jsonrpc_client.call_async (
543 "textDocument/documentSymbol",
546 textDocument : this.buildDict ( ///TextDocumentItem;
547 uri: new GLib.Variant.string (file.to_url()),
548 version : new GLib.Variant.uint64 ( (uint64) file.version)
557 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
558 var json = Json.gvariant_serialize (return_value);
562 var ar = json.get_array();
563 for(var i = 0; i < ar.get_length(); i++ ) {
564 var add= Json.gobject_deserialize ( typeof (Lsp.DocumentSymbol), ar.get_element(i)) as Lsp.DocumentSymbol;