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