Fix #8011 - add resources
[roobuilder] / src / Palete / LanguageClientVala.vala
1
2 namespace Palete {
3         public class LanguageClientVala : LanguageClient {
4                 int countdown = 0;
5                 protected bool initialized = false;
6                 bool sent_shutdown = false;
7                 uint change_queue_id = 0;
8                 
9                         
10                 private bool _closed = false;
11                 private bool closed {
12                         get { return this._closed ; } 
13                         set {
14                                 GLib.debug("closed has been set? to %s" , value ? "TRUE" : "FALSE" );
15                                 this._closed = value;
16                         }
17                 }
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;
22                 
23                 Gee.ArrayList<JsRender.JsRender> open_files;
24                 private JsRender.JsRender? _change_queue_file = null;
25                 private string change_queue_file_source = "";
26                 
27                 JsRender.JsRender? change_queue_file {
28                         set {
29                                 this.change_queue_file_source = value == null ? "" : value.toSource();
30                                 this._change_queue_file = value;
31                         } 
32                         get {
33                                 return this._change_queue_file;
34                         } 
35                 }
36                 void startServer()
37                 {
38                         var exe = GLib.Environment.find_program_in_path( "vala-language-server");
39                         if (exe == null) {
40                                 GLib.warning("could not find vala-language-server");
41                                  
42                                 return;
43                         }
44                         this.initProcess(exe);
45                 }
46                 
47                 
48                 public LanguageClientVala(Project.Project project)
49                 {
50                         // extend versions will proably call initialize to start and connect to server.
51                         base(project);
52
53                         this.change_queue_id = GLib.Timeout.add_seconds(1, () => {
54                                 if (this.change_queue_file == null) {
55                                         return true;
56                                 }
57                                 this.countdown--;
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);
61                                         });
62                                         this.change_queue_file = null;
63                                            
64                                 }
65                                 return true;
66                         });
67                         this.startServer();
68
69                 }
70                 
71                  
72                 public bool initProcess(string process_path)
73                 {
74                         this.onClose();
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());
79                         try {
80
81                                 
82                                 this.subprocess = launcher.spawnv ({ process_path });
83                                 
84                                 this.subprocess.wait_async.begin( null, ( obj,res ) => {
85                                         try {
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);           
90                                         }
91                                         this.log(LanguageClientAction.EXIT, "process ended");
92                                         GLib.debug("Subprocess ended %s", process_path);
93                                         this.onClose();
94
95                                 });
96                                 var input_stream = this.subprocess.get_stdout_pipe ();
97                                 var output_stream = this.subprocess.get_stdin_pipe ();
98  
99                                 if (input_stream is GLib.UnixInputStream && output_stream is GLib.UnixOutputStream) {
100                                         // set nonblocking
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)) 
103                                          {
104                                                 GLib.debug("could not set pipes to nonblocking");
105                                                 this.onClose();
106                                             return false;
107                                     }
108                             }
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);   
114                                 this.onClose();
115                                 return false;
116                 }
117             return true;
118         }
119         bool in_close = false;
120                 public override void client_accepted (Jsonrpc.Client client) 
121                 {
122                         if (this.jsonrpc_client == null) {
123                                 this.jsonrpc_client = client;
124                                 
125                                 GLib.debug("client accepted connection - calling init server");
126                                 this.log(LanguageClientAction.ACCEPT, "client accepted");
127
128                                 this.jsonrpc_client.notification.connect((method, paramz) => {
129                                         this.onNotification(method, paramz);
130                                 });
131                                  
132                                 this.jsonrpc_client.failed.connect(() => {
133                                         this.log(LanguageClientAction.ERROR_RPC, "client failed");
134                                         this.onClose();
135                                         
136                                         GLib.debug("language server server has failed");
137                                 });
138
139                                 this.initialize_server ();
140                         } 
141                                          
142                          
143                 }
144                 
145                 
146                 public override   void  initialize_server()   {
147                         try {
148                                 Variant? return_value;
149                                     this.jsonrpc_client.call (
150                                     "initialize",
151                                     this.buildDict (
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 ())
155                                     ),
156                                     null,
157                                     out return_value
158                                 );
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;
162                                 });
163                                 this.initialized = true;
164                                 return;
165                         } catch (GLib.Error e) {
166                                 GLib.debug ("LS replied with error %s", e.message);
167                                 this.onClose();
168                         }
169                         
170                 }
171                 void onClose()
172                 {
173                         if (this.in_close) {
174                                 return;
175                         }
176                         if (this.launcher == null) {
177                                 return;
178                         }
179                         this.in_close = true;
180                         GLib.debug("onClose called");
181                         
182                         if (this.jsonrpc_client != null) {
183                                 try {
184                                         this.jsonrpc_client.close();
185                                 } catch (GLib.Error e) {
186                                         GLib.debug("rpc Error close error %s", e.message);      
187                                 }               
188                         }
189                         if (this.subprocess_stream != null) {
190                                 try {
191                                         this.subprocess_stream.close();
192                                 } catch (GLib.Error e) {
193                                         GLib.debug("stream Error close  %s", e.message);        
194                                 }               
195                         }
196                         if (this.subprocess != null) {
197                                 this.subprocess.force_exit();
198                         }
199                         if (this.launcher != null) {
200                                 this.launcher.close();
201                         }
202                         
203                         this.launcher = null;
204                         this.subprocess = null;
205                         this.jsonrpc_client = null;
206                         this.closed = true;             
207                         this.in_close = false;
208                 }
209         
210                 public async void restartServer()
211                 {
212                         this.startServer();
213                          
214                 }
215         
216                 public bool isReady()
217                 {
218                         if (this.closed) {
219                                 this.log(LanguageClientAction.RESTART,"closed is set - restarting");
220                                 GLib.debug("server stopped = restarting");
221                                 this.initialized = false;
222                                 this.closed = false;
223                                 GLib.MainLoop loop = new GLib.MainLoop ();
224                                 this.restartServer.begin ((obj, async_res) => {
225                                         this.restartServer.end(async_res);
226                                         loop.quit ();
227                                 });
228                                 return false; // can't do an operation yet?
229                                  
230                         }
231                         
232                         if (!this.initialized) {
233                                 GLib.debug("Server has not been initialized");
234                                 return false;
235                         }
236                         if (this.sent_shutdown) {
237                                 GLib.debug("Server has been started its shutting down process");
238                                 return false;
239                         }
240                         // restart server..
241                 
242                         
243                         
244                         return true;
245                 }
246         
247                 public void onNotification(string method, Variant? return_value)
248                 {
249                         switch (method) {
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);
253                                         return;
254                                 default: 
255                                         break;
256                                  
257                         }
258                         GLib.debug("got notification %s : %s",  method , Json.to_string (Json.gvariant_serialize (return_value), true));
259                         
260                 }
261                 
262                 /***
263                 
264                 */
265                 public void onDiagnostic(Variant? return_value) 
266                 {
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);
271                         if (f == null) {
272                                 //GLib.debug("no file %s", dg.uri);
273                                 //this.project.updateErrorsforFile(null);
274                                 return;
275                         }
276                         //GLib.debug("got Diagnostics for %s", f.path);
277                         f.updateErrors( dg.diagnostics );
278                          
279                         
280                 }
281                 
282                 public override void document_open (JsRender.JsRender file)  
283                 {
284                         if (!this.isReady()) {
285                                 return;
286                         }
287                         if (this.open_files.contains(file)) {
288                                 return;
289                         }
290                         this.open_files.add(file);
291                         
292                         
293                         GLib.debug ("LS sent open");                     
294                         try {
295                                 this.jsonrpc_client.send_notification (
296                                         "textDocument/didOpen",
297                                         this.buildDict (
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())
303                                                 )
304                                         ),
305                                         null
306                                 );
307                                 this.log(LanguageClientAction.OPEN, file.path);
308                         } catch( GLib.Error  e) {
309                                 this.log(LanguageClientAction.ERROR_RPC, e.message);
310                                 this.onClose();
311                                 GLib.debug ("LS sent open err %s", e.message);
312                         }
313
314                 }
315                 
316                 public override  async void document_save (JsRender.JsRender file)  
317         {
318                         if (!this.isReady()) {
319                                 return;
320                         }
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());
324                         
325                         this.change_queue_file = null;
326                         GLib.debug ("LS send save");
327                          try {
328                          
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)
333                                         )
334                                 );
335                          
336                                 //GLib.debug ("textDocument/save send with %s", Json.to_string (Json.gvariant_serialize (args), true));                                 
337                         
338                          
339                          
340                                   yield this.jsonrpc_client.send_notification_async  (
341                                         "textDocument/didSave",
342                                         args,
343                                         null 
344                                 );
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);
349                                 this.onClose();
350                         }
351
352          
353         }
354                 public override  void document_close (JsRender.JsRender file) 
355         {
356                         if (!this.isReady()) {
357                                 return;
358                         }
359                         this.change_queue_file = null;
360                         
361                         if (this.open_files.contains(file)) {
362                                 this.open_files.remove(file);
363                         }
364                         this.log(LanguageClientAction.CLOSE, file.path);
365                         GLib.debug ("LS send close");
366                         try {
367                                   this.jsonrpc_client.send_notification  (
368                                         "textDocument/didChange",
369                                         this.buildDict (  
370                                                 textDocument : this.buildDict (    ///TextDocumentItem;
371                                                         uri: new GLib.Variant.string (file.to_url())
372                                                         
373                                                 )
374                                         ),
375                                         null  
376                                 );
377                         } catch( GLib.Error  e) {
378                                 this.log(LanguageClientAction.ERROR_RPC, e.message);
379                                 GLib.debug ("LS close err %s", e.message);
380                                 this.onClose();
381                         }
382
383          
384         }
385         
386          
387                 public override void document_change (JsRender.JsRender file )    
388                 {
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);
392                                 });
393                         }
394                         
395                         this.countdown = 3;
396                         this.change_queue_file = file;
397                          
398                         
399
400                 }
401         
402
403                 public override async void document_change_force (JsRender.JsRender file, string contents)  
404         {
405                         
406                         
407                         if (!this.isReady()) {
408                                 return;
409                         }
410                         this.countdown = 9; // not really relivant..
411                         this.change_queue_file = null; // this is more important..
412                         
413                     if (!this.open_files.contains(file)) {
414                                  this.document_open(file);
415                         }  
416                         
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);
423                         node.set_array(ar);
424                         this.log(LanguageClientAction.CHANGE, file.path);
425                          try {
426                                 yield this.jsonrpc_client.send_notification_async (
427                                         "textDocument/didChange",
428                                         this.buildDict (  
429                                                 textDocument : this.buildDict (    ///TextDocumentItem;
430                                                         uri: new GLib.Variant.string (file.to_url()),
431                                                         version :  new GLib.Variant.uint64 ( (uint64) file.version) 
432                                                 ),
433                                                 contentChanges : Json.gvariant_deserialize (node, null)
434                                                 
435                                         ),
436                                         null 
437                                 );
438                         } catch( GLib.Error  e) {
439                                 this.log(LanguageClientAction.ERROR_RPC, e.message);
440                                 GLib.debug ("LS change err %s", e.message);
441                                 this.onClose();
442                         }
443
444          
445         }
446         // called by close window (on last window)...
447                 public override  void exit () throws GLib.Error 
448                 {
449                         if (!this.isReady()) {
450                         
451                                 return;
452                         }
453                         this.log(LanguageClientAction.TERM, "SEND exit");
454                  
455                           this.jsonrpc_client.send_notification (
456                                 "exit",
457                                 null,
458                                 null 
459                         );
460                         this.onClose();
461
462                 }
463                 // not used currently..
464                 public override async void shutdown () throws GLib.Error 
465                 {
466                         if (!this.isReady()) {
467                                 return;
468                         }
469                         this.log(LanguageClientAction.TERM, "SEND shutodwn");
470                         this.sent_shutdown  = true;
471                         Variant? return_value;
472                         yield this.jsonrpc_client.call_async (
473                                 "shutdown",
474                                 null,
475                                 null,
476                                 out return_value
477                         );
478                         GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));               
479                 }
480                 //public async  ??/symbol (string symbol) throws GLib.Error {
481                 
482                 // and now for the important styff..
483                 
484                 /*
485                 
486                 @triggerType 1 = typing or ctl-spac, 2 = tiggercharactres?  3= inside completion?
487                 */
488                  public override async Lsp.CompletionList?  completion(JsRender.JsRender file, int line, int offset , int triggerType = 1) throws GLib.Error 
489                  {
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);
492                         
493                         var ret = new Lsp.CompletionList();     
494                         
495                     if (!this.isReady()) {
496                         GLib.debug("completion - language server not ready");
497                                 return ret;
498                         }
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;
503                         //}
504                         this.log(LanguageClientAction.COMPLETE, "SEND complete  %s @ %d:%d".printf(file.relpath, line, offset) );
505                         
506                         Variant? return_value;
507                         
508                         var args = this.buildDict (  
509                                         context : this.buildDict (    ///CompletionContext;
510                                                 triggerKind: new GLib.Variant.int32 (triggerType) 
511                                         //      triggerCharacter :  new GLib.Variant.string ("")
512                                         ),
513                                         textDocument : this.buildDict (    ///TextDocumentItem;
514                                                 uri: new GLib.Variant.string (file.to_url()),
515                                                 version :  new GLib.Variant.uint64 ( (uint64) file.version) 
516                                         ), 
517                                         position :  this.buildDict ( 
518                                                 line :  new GLib.Variant.uint64 ( (uint) line) ,
519                                                 character :  new GLib.Variant.uint64 ( uint.max(0,  (offset -1))) 
520                                         )
521                                 );
522                          
523                         GLib.debug ("textDocument/completion send with %s", Json.to_string (Json.gvariant_serialize (args), true));                                     
524                         
525                         yield this.jsonrpc_client.call_async (
526                                 "textDocument/completion",
527                                 args,
528                                 null,
529                                 out return_value
530                         );
531                         
532                         
533                         //GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));                                     
534                         var json = Json.gvariant_serialize (return_value);
535
536
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");
541                                 return ret;
542                         }  
543
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??");
547                                 return ret;
548                         
549                         }
550                         var ar = json.get_array();                      
551                         
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;
554                                 ret.items.add( add);
555                                          
556                         }
557                         this.log(LanguageClientAction.COMPLETE_REPLY, "GOT array %d items".printf(ret.items.size) );
558                         GLib.debug ("LS replied with Array");
559                         return ret;
560                 
561
562                 }
563                 //CompletionListInfo.itmems.parse_varient  or CompletionListInfo.parsevarient
564                 public override async Gee.ArrayList<Lsp.DocumentSymbol> syntax (JsRender.JsRender file) throws GLib.Error 
565                  {
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>();      
569                         //ret = null;
570                     if (!this.isReady()) {
571                                 return ret;
572                         }
573                         Variant? return_value;
574                         yield this.jsonrpc_client.call_async (
575                                 "textDocument/documentSymbol",
576                                 this.buildDict (  
577                                          
578                                         textDocument : this.buildDict (    ///TextDocumentItem;
579                                                 uri: new GLib.Variant.string (file.to_url()),
580                                                 version :  new GLib.Variant.uint64 ( (uint64) file.version) 
581                                         ) 
582                                          
583                                 ),
584                                 null,
585                                 out return_value
586                         );
587                         
588                         
589                         GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));                                       
590                         var json = Json.gvariant_serialize (return_value);
591                          
592                          
593
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;
597                                 ret.add( add);
598                                          
599                         }
600                                 return ret ;
601                         
602                 
603
604                 }
605                 
606         }
607         
608 }