Fix #8028 - language server performance, fix warnings and critical errors
[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                                 if (this.getting_diagnostics) {
58                                         return true;
59                                 }
60                                 this.countdown--;
61
62                         
63                                 if (this.countdown < 0){
64                                         this.document_change_force.begin(this.change_queue_file,  this.change_queue_file_source, (o, res) => {
65                                                 this.document_change_force.end(res);
66                                         });
67                                         this.change_queue_file = null;
68                                            
69                                 }
70                                 return true;
71                         });
72                         this.startServer();
73
74                 }
75                 
76                  
77                 public bool initProcess(string process_path)
78                 {
79                         this.onClose();
80                         this.log(LanguageClientAction.LAUNCH, process_path);
81                         GLib.debug("Launching %s", process_path);
82                         this.launcher = new GLib.SubprocessLauncher (SubprocessFlags.STDIN_PIPE | SubprocessFlags.STDOUT_PIPE);
83                         this.launcher.set_environ(GLib.Environ.get());
84                         try {
85
86                                 
87                                 this.subprocess = launcher.spawnv ({ process_path });
88                                 
89                                 this.subprocess.wait_async.begin( null, ( obj,res ) => {
90                                         try {
91                                                 this.subprocess.wait_async.end(res);
92                                         } catch (GLib.Error e) {
93                                                 this.log(LanguageClientAction.ERROR_START, e.message);
94                                                 GLib.debug("subprocess startup error %s", e.message);           
95                                         }
96                                         this.log(LanguageClientAction.EXIT, "process ended");
97                                         GLib.debug("Subprocess ended %s", process_path);
98                                         this.onClose();
99
100                                 });
101                                 var input_stream = this.subprocess.get_stdout_pipe ();
102                                 var output_stream = this.subprocess.get_stdin_pipe ();
103  
104                                 if (input_stream is GLib.UnixInputStream && output_stream is GLib.UnixOutputStream) {
105                                         // set nonblocking
106                                         if (!GLib.Unix.set_fd_nonblocking(((GLib.UnixInputStream)input_stream).fd, true)
107                                          || !GLib.Unix.set_fd_nonblocking (((GLib.UnixOutputStream)output_stream).fd, true)) 
108                                          {
109                                                 GLib.debug("could not set pipes to nonblocking");
110                                                 this.onClose();
111                                             return false;
112                                     }
113                             }
114                             this.subprocess_stream = new GLib.SimpleIOStream (input_stream, output_stream);
115                         this.accept_io_stream ( this.subprocess_stream);
116                         } catch (GLib.Error e) {
117                                 this.log(LanguageClientAction.ERROR_START, e.message);
118                                 GLib.debug("subprocess startup error %s", e.message);   
119                                 this.onClose();
120                                 return false;
121                 }
122             return true;
123         }
124         bool in_close = false;
125                 public override void client_accepted (Jsonrpc.Client client) 
126                 {
127                         if (this.jsonrpc_client == null) {
128                                 this.jsonrpc_client = client;
129                                 
130                                 GLib.debug("client accepted connection - calling init server");
131                                 this.log(LanguageClientAction.ACCEPT, "client accepted");
132
133                                 this.jsonrpc_client.notification.connect((method, paramz) => {
134                                         this.onNotification(method, paramz);
135                                 });
136                                  
137                                 this.jsonrpc_client.failed.connect(() => {
138                                         this.log(LanguageClientAction.ERROR_RPC, "client failed");
139                                         this.onClose();
140                                         
141                                         GLib.debug("language server server has failed");
142                                 });
143
144                                 this.initialize_server ();
145                         } 
146                                          
147                          
148                 }
149                 
150                 
151                 public override   void  initialize_server()   {
152                         try {
153                                 Variant? return_value;
154                                     this.jsonrpc_client.call (
155                                     "initialize",
156                                     this.buildDict (
157                                         processId: new Variant.int32 ((int32) Posix.getpid ()),
158                                         rootPath: new Variant.string (this.project.path),
159                                         rootUri: new Variant.string (File.new_for_path (this.project.path).get_uri ())
160                                     ),
161                                     null,
162                                     out return_value
163                                 );
164                                 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));
165                                 this.open_files = new Gee.ArrayList<JsRender.JsRender>((a,b) => {
166                                         return a.path == b.path;
167                                 });
168                                 this.initialized = true;
169                                 this.getting_diagnostics = false;
170                                 return;
171                         } catch (GLib.Error e) {
172                                 GLib.debug ("LS replied with error %s", e.message);
173                                 this.onClose();
174                         }
175                         
176                 }
177                 void onClose()
178                 {
179                         if (this.in_close) {
180                                 return;
181                         }
182                         if (this.launcher == null) {
183                                 return;
184                         }
185                         this.getting_diagnostics = false;
186                         this.in_close = true;
187                         GLib.debug("onClose called");
188                         
189                         if (this.jsonrpc_client != null) {
190                                 try {
191                                         this.jsonrpc_client.close();
192                                 } catch (GLib.Error e) {
193                                         GLib.debug("rpc Error close error %s", e.message);      
194                                 }               
195                         }
196                         if (this.subprocess_stream != null) {
197                                 try {
198                                         this.subprocess_stream.close();
199                                 } catch (GLib.Error e) {
200                                         GLib.debug("stream Error close  %s", e.message);        
201                                 }               
202                         }
203                         if (this.subprocess != null) {
204                                 this.subprocess.force_exit();
205                         }
206                         if (this.launcher != null) {
207                                 this.launcher.close();
208                         }
209                         
210                         this.launcher = null;
211                         this.subprocess = null;
212                         this.jsonrpc_client = null;
213                         this.closed = true;             
214                         this.in_close = false;
215                 }
216         
217                 public async void restartServer()
218                 {
219                         this.startServer();
220                          
221                 }
222         
223                 public bool isReady()
224                 {
225                         if (this.closed) {
226                                 this.log(LanguageClientAction.RESTART,"closed is set - restarting");
227                                 GLib.debug("server stopped = restarting");
228                                 this.initialized = false;
229                                 this.closed = false;
230                                 GLib.MainLoop loop = new GLib.MainLoop ();
231                                 this.restartServer.begin ((obj, async_res) => {
232                                         this.restartServer.end(async_res);
233                                         loop.quit ();
234                                 });
235                                 return false; // can't do an operation yet?
236                                  
237                         }
238                         
239                         if (!this.initialized) {
240                                 GLib.debug("Server has not been initialized");
241                                 return false;
242                         }
243                         if (this.sent_shutdown) {
244                                 GLib.debug("Server has been started its shutting down process");
245                                 return false;
246                         }
247                         // restart server..
248                 
249                         
250                         
251                         return true;
252                 }
253         
254                 public void onNotification(string method, Variant? return_value)
255                 {
256                         switch (method) {
257                                 case "textDocument/publishDiagnostics":
258                                         //GLib.debug("got notification %s : %s",  method , Json.to_string (Json.gvariant_serialize (return_value), true));
259                                         this.onDiagnostic(return_value);
260                                         return;
261                                 default: 
262                                         break;
263                                  
264                         }
265                         GLib.debug("got notification %s : %s",  method , Json.to_string (Json.gvariant_serialize (return_value), true));
266                         
267                 }
268                 
269                 bool getting_diagnostics = false;
270                 /***
271                 
272                 */
273                 public void onDiagnostic(Variant? return_value) 
274                 {
275                         //GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));                                     
276                         var dg = Json.gobject_deserialize (typeof (Lsp.Diagnostics), Json.gvariant_serialize (return_value)) as Lsp.Diagnostics; 
277                         GLib.debug("got diag for %s", dg.filename);
278                         this.log(LanguageClientAction.DIAG, dg.filename);
279                         if (this.project.path == dg.filename) {
280                                 this.getting_diagnostics = false;
281                                 this.log(LanguageClientAction.DIAG_END, "diagnostics done");
282                                 return;
283                         
284                         }
285                         this.getting_diagnostics =true;
286                         var f = this.project.getByPath(dg.filename);
287                         if (f == null) {
288                                 //GLib.debug("no file %s", dg.uri);
289                                 //this.project.updateErrorsforFile(null);
290                                 return;
291                         }
292                         //GLib.debug("got Diagnostics for %s", f.path);
293                         f.updateErrors( dg.diagnostics );
294                          
295                         
296                 }
297                 
298                 public override void document_open (JsRender.JsRender file)  
299                 {
300                         if (!this.isReady()) {
301                                 return;
302                         }
303                         if (this.open_files.contains(file)) {
304                                 return;
305                         }
306                         this.open_files.add(file);
307                         
308                         
309                         GLib.debug ("LS sent open");                     
310                         try {
311                                 this.jsonrpc_client.send_notification (
312                                         "textDocument/didOpen",
313                                         this.buildDict (
314                                                 textDocument : this.buildDict (
315                                                         uri: new Variant.string (file.to_url()),
316                                                         languageId :  new Variant.string (file.language_id()),
317                                                         version :  new GLib.Variant.uint64 ( (uint64) file.version),
318                                                         text : new Variant.string (file.toSource())
319                                                 )
320                                         ),
321                                         null
322                                 );
323                                 this.log(LanguageClientAction.OPEN, file.path);
324                         } catch( GLib.Error  e) {
325                                 this.log(LanguageClientAction.ERROR_RPC, e.message);
326                                 this.onClose();
327                                 GLib.debug ("LS sent open err %s", e.message);
328                         }
329
330                 }
331                 
332                 public override  async void document_save (JsRender.JsRender file)  
333         {
334                         if (!this.isReady()) {
335                                 return;
336                         }
337                         // save only really flags the file on the server - to actually force a change update - we need to 
338                         // flag it as changed.
339                         yield this.document_change_force(file, file.toSource());
340                         
341                         this.change_queue_file = null;
342                         GLib.debug ("LS send save");
343                          try {
344                          
345                                 var args = this.buildDict (  
346                                         textDocument : this.buildDict (    ///TextDocumentItem;
347                                                 uri: new GLib.Variant.string (file.to_url()),
348                                                 version :  new GLib.Variant.uint64 ( (uint64) file.version)
349                                         )
350                                 );
351                          
352                                 //GLib.debug ("textDocument/save send with %s", Json.to_string (Json.gvariant_serialize (args), true));                                 
353                         
354                          
355                          
356                                   yield this.jsonrpc_client.send_notification_async  (
357                                         "textDocument/didSave",
358                                         args,
359                                         null 
360                                 );
361                                 this.log(LanguageClientAction.SAVE, file.path);
362                         } catch( GLib.Error  e) {
363                                 this.log(LanguageClientAction.ERROR_RPC, e.message);
364                                 GLib.debug ("LS   save err %s", e.message);
365                                 this.onClose();
366                         }
367
368          
369         }
370                 public override  void document_close (JsRender.JsRender file) 
371         {
372                         if (!this.isReady()) {
373                                 return;
374                         }
375                         this.change_queue_file = null;
376                         
377                         if (this.open_files.contains(file)) {
378                                 this.open_files.remove(file);
379                         }
380                         this.log(LanguageClientAction.CLOSE, file.path);
381                         GLib.debug ("LS send close");
382                         try {
383                                   this.jsonrpc_client.send_notification  (
384                                         "textDocument/didChange",
385                                         this.buildDict (  
386                                                 textDocument : this.buildDict (    ///TextDocumentItem;
387                                                         uri: new GLib.Variant.string (file.to_url())
388                                                         
389                                                 )
390                                         ),
391                                         null  
392                                 );
393                         } catch( GLib.Error  e) {
394                                 this.log(LanguageClientAction.ERROR_RPC, e.message);
395                                 GLib.debug ("LS close err %s", e.message);
396                                 this.onClose();
397                         }
398
399          
400         }
401         
402          
403                 public override void document_change (JsRender.JsRender file )    
404                 {
405                         if (this.change_queue_file != null && this.change_queue_file.path != file.path) {
406                                 this.document_change_force.begin(this.change_queue_file, this.change_queue_file_source, (o, res) => {
407                                         this.document_change_force.end(res);
408                                 });
409                         }
410                         
411                         this.countdown = 3;
412                         this.change_queue_file = file;
413                          
414                         
415
416                 }
417         
418
419                 public override async void document_change_force (JsRender.JsRender file, string contents)  
420         {
421                         
422                         
423                         if (!this.isReady()) {
424                                 return;
425                         }
426                         this.countdown = 9; // not really relivant..
427                         this.change_queue_file = null; // this is more important..
428                         
429                     if (!this.open_files.contains(file)) {
430                                  this.document_open(file);
431                         }  
432                         
433                         GLib.debug ("LS send change %s rev %d", file.path, file.version);
434                         var ar = new Json.Array();
435                         var obj = new Json.Object();
436                         obj.set_string_member("text", contents);
437                         ar.add_object_element(obj);
438                         var node = new Json.Node(Json.NodeType.ARRAY);
439                         node.set_array(ar);
440                         this.log(LanguageClientAction.CHANGE, file.path);
441                          try {
442                                 yield this.jsonrpc_client.send_notification_async (
443                                         "textDocument/didChange",
444                                         this.buildDict (  
445                                                 textDocument : this.buildDict (    ///TextDocumentItem;
446                                                         uri: new GLib.Variant.string (file.to_url()),
447                                                         version :  new GLib.Variant.uint64 ( (uint64) file.version) 
448                                                 ),
449                                                 contentChanges : Json.gvariant_deserialize (node, null)
450                                                 
451                                         ),
452                                         null 
453                                 );
454                         } catch( GLib.Error  e) {
455                                 this.log(LanguageClientAction.ERROR_RPC, e.message);
456                                 GLib.debug ("LS change err %s", e.message);
457                                 this.onClose();
458                         }
459
460          
461         }
462         // called by close window (on last window)...
463                 public override  void exit () throws GLib.Error 
464                 {
465                         if (!this.isReady()) {
466                         
467                                 return;
468                         }
469                         this.log(LanguageClientAction.TERM, "SEND exit");
470                  
471                           this.jsonrpc_client.send_notification (
472                                 "exit",
473                                 null,
474                                 null 
475                         );
476                         this.onClose();
477
478                 }
479                 // not used currently..
480                 public override async void shutdown () throws GLib.Error 
481                 {
482                         if (!this.isReady()) {
483                                 return;
484                         }
485                         this.log(LanguageClientAction.TERM, "SEND shutodwn");
486                         this.sent_shutdown  = true;
487                         Variant? return_value;
488                         yield this.jsonrpc_client.call_async (
489                                 "shutdown",
490                                 null,
491                                 null,
492                                 out return_value
493                         );
494                         GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));               
495                 }
496                 //public async  ??/symbol (string symbol) throws GLib.Error {
497                 
498                 // and now for the important styff..
499                 
500                 /*
501                 
502                 @triggerType 1 = typing or ctl-spac, 2 = tiggercharactres?  3= inside completion?
503                 */
504                  public override async Lsp.CompletionList?  completion(JsRender.JsRender file, int line, int offset , int triggerType = 1) throws GLib.Error 
505                  {
506                         /* partial_result_token ,  work_done_token   context = null) */
507                         GLib.debug("%s get completion %s @ %d:%d", this.get_type().name(),  file.relpath, line, offset);
508                         
509                         var ret = new Lsp.CompletionList();     
510                         
511                     if (!this.isReady()) {
512                         GLib.debug("completion - language server not ready");
513                                 return ret;
514                         }
515                         // make sure completion has the latest info..
516                         //if (this.change_queue_file != null && this.change_queue_file.path != file.path) {
517                         //      this.document_change_real(this.change_queue_file, this.change_queue_file_source);
518                         //      this.change_queue_file != null;
519                         //}
520                         this.log(LanguageClientAction.COMPLETE, "SEND complete  %s @ %d:%d".printf(file.relpath, line, offset) );
521                         
522                         Variant? return_value;
523                         
524                         var args = this.buildDict (  
525                                         context : this.buildDict (    ///CompletionContext;
526                                                 triggerKind: new GLib.Variant.int32 (triggerType) 
527                                         //      triggerCharacter :  new GLib.Variant.string ("")
528                                         ),
529                                         textDocument : this.buildDict (    ///TextDocumentItem;
530                                                 uri: new GLib.Variant.string (file.to_url()),
531                                                 version :  new GLib.Variant.uint64 ( (uint64) file.version) 
532                                         ), 
533                                         position :  this.buildDict ( 
534                                                 line :  new GLib.Variant.uint64 ( (uint) line) ,
535                                                 character :  new GLib.Variant.uint64 ( uint.max(0,  (offset -1))) 
536                                         )
537                                 );
538                          
539                         GLib.debug ("textDocument/completion send with %s", Json.to_string (Json.gvariant_serialize (args), true));                                     
540                         
541                         yield this.jsonrpc_client.call_async (
542                                 "textDocument/completion",
543                                 args,
544                                 null,
545                                 out return_value
546                         );
547                         
548                         
549                         //GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));                                     
550                         var json = Json.gvariant_serialize (return_value);
551
552
553                         if (json.get_node_type() == Json.NodeType.OBJECT) {
554                                 ret = Json.gobject_deserialize (typeof (Lsp.CompletionList), json) as Lsp.CompletionList; 
555                                 this.log(LanguageClientAction.COMPLETE_REPLY, "GOT complete  %d items".printf(ret.items.size) );
556                                 GLib.debug ("LS replied with Object");
557                                 return ret;
558                         }  
559
560                         if (json.get_node_type() != Json.NodeType.ARRAY) {
561                                 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));                                       
562                                 this.log(LanguageClientAction.ERROR_REPLY, "GOT something else??");
563                                 return ret;
564                         
565                         }
566                         var ar = json.get_array();                      
567                         
568                         for(var i = 0; i < ar.get_length(); i++ ) {
569                                 var add= Json.gobject_deserialize ( typeof (Lsp.CompletionItem),  ar.get_element(i)) as Lsp.CompletionItem;
570                                 ret.items.add( add);
571                                          
572                         }
573                         this.log(LanguageClientAction.COMPLETE_REPLY, "GOT array %d items".printf(ret.items.size) );
574                         GLib.debug ("LS replied with Array");
575                         return ret;
576                 
577
578                 }
579                 //CompletionListInfo.itmems.parse_varient  or CompletionListInfo.parsevarient
580                 public override async Gee.ArrayList<Lsp.DocumentSymbol> syntax (JsRender.JsRender file) throws GLib.Error 
581                  {
582                         /* partial_result_token ,  work_done_token   context = null) */
583                         GLib.debug("get syntax %s", file.relpath);
584                         var ret = new Gee.ArrayList<Lsp.DocumentSymbol>();      
585                         //ret = null;
586                     if (!this.isReady()) {
587                                 return ret;
588                         }
589                         Variant? return_value;
590                         yield this.jsonrpc_client.call_async (
591                                 "textDocument/documentSymbol",
592                                 this.buildDict (  
593                                          
594                                         textDocument : this.buildDict (    ///TextDocumentItem;
595                                                 uri: new GLib.Variant.string (file.to_url()),
596                                                 version :  new GLib.Variant.uint64 ( (uint64) file.version) 
597                                         ) 
598                                          
599                                 ),
600                                 null,
601                                 out return_value
602                         );
603                         
604                         
605                         GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));                                       
606                         var json = Json.gvariant_serialize (return_value);
607                          
608                          
609
610                         var ar = json.get_array();
611                         for(var i = 0; i < ar.get_length(); i++ ) {
612                                 var add= Json.gobject_deserialize ( typeof (Lsp.DocumentSymbol),  ar.get_element(i)) as Lsp.DocumentSymbol;
613                                 ret.add( add);
614                                          
615                         }
616                                 return ret ;
617                         
618                 
619
620                 }
621                 
622         }
623         
624 }