:Revert "fix line numbering issues with vala generator - hopefully fixes completion...
[roobuilder] / src / Palete / LanguageClient.vala
1 /**
2   generic interface to language server
3   ?? first of will be for vala... but later?
4   based on gvls-client-jsonrpc (loosly)
5   and vala-language-server
6  
7
8 */
9
10 namespace Palete {
11
12         public enum  LanguageClientAction {
13                 INIT,
14                 LAUNCH,
15                 ACCEPT,
16                 
17                 DIAG,
18                 OPEN,
19                 SAVE,
20                 CLOSE,
21                 CHANGE,
22                 TERM,
23                 COMPLETE,
24                 COMPLETE_REPLY,
25                 
26                 RESTART,
27                 ERROR,
28                 ERROR_START,
29                 ERROR_RPC,
30                 ERROR_REPLY,
31
32                 EXIT,
33         }
34
35         public abstract class LanguageClient :   Jsonrpc.Server {
36         
37                 public Project.Project project;
38                 private GLib.SubprocessLauncher launcher = null;
39                 private GLib.Subprocess? subprocess = null;
40                 private IOStream? subprocess_stream = null;
41             public Jsonrpc.Client? jsonrpc_client = null;
42                 
43                 Gee.ArrayList<JsRender.JsRender> open_files;
44                 private JsRender.JsRender? _change_queue_file = null;
45                 private string change_queue_file_source = "";
46                 
47                 JsRender.JsRender? change_queue_file {
48                         set {
49                                 this.change_queue_file_source = value == null ? "" : value.toSource();
50                                 this._change_queue_file = value;
51                         } 
52                         get {
53                                 return this._change_queue_file;
54                         } 
55                 }
56  
57                 uint change_queue_id = 0;
58                 int countdown = 0;
59                 protected bool initialized = false;
60                 bool sent_shutdown = false;
61                 private bool _closed = false;
62                 private bool closed {
63                         get { return this._closed ; } 
64                         set {
65                                 GLib.debug("closed has been set? to %s" , value ? "TRUE" : "FALSE" );
66                                 this._closed = value;
67                         }
68                 }
69         
70                 public signal void log(LanguageClientAction action, string message);
71                 
72                 
73                 protected LanguageClient(Project.Project project)
74                 {
75                         // extend versions will proably call initialize to start and connect to server.
76                         this.project = project;
77                         this.open_files = new   Gee.ArrayList<JsRender.JsRender>();
78                         this.change_queue_id = GLib.Timeout.add_seconds(1, () => {
79                                 if (this.change_queue_file == null) {
80                                         return true;
81                                 }
82                                 this.countdown--;
83                                 if (this.countdown < 0){
84                                         this.document_change_real(this.change_queue_file,  this.change_queue_file_source);
85                                         this.change_queue_file = null;
86                                            
87                                 }
88                                 return true;
89                         });
90                 
91                 }
92                  
93                 public bool initProcess(string process_path)
94                 {
95                         this.onClose();
96                         this.log(LanguageClientAction.LAUNCH, process_path);
97                         GLib.debug("Launching %s", process_path);
98                         this.launcher = new GLib.SubprocessLauncher (SubprocessFlags.STDIN_PIPE | SubprocessFlags.STDOUT_PIPE);
99                         this.launcher.set_environ(GLib.Environ.get());
100                         try {
101
102                                 
103                                 this.subprocess = launcher.spawnv ({ process_path });
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                 protected void onClose()
142                 {
143                         if (this.in_close) {
144                                 return;
145                         }
146                         if (this.launcher == null) {
147                                 return;
148                         }
149                         this.in_close = true;
150                         GLib.debug("onClose called");
151                         
152                         if (this.jsonrpc_client != null) {
153                                 try {
154                                         this.jsonrpc_client.close();
155                                 } catch (GLib.Error e) {
156                                         GLib.debug("rpc Error close error %s", e.message);      
157                                 }               
158                         }
159                         if (this.subprocess_stream != null) {
160                                 try {
161                                         this.subprocess_stream.close();
162                                 } catch (GLib.Error e) {
163                                         GLib.debug("stream Error close  %s", e.message);        
164                                 }               
165                         }
166                         if (this.subprocess != null) {
167                                 this.subprocess.force_exit();
168                         }
169                         if (this.launcher != null) {
170                                 this.launcher.close();
171                         }
172                         
173                         this.launcher = null;
174                         this.subprocess = null;
175                         this.jsonrpc_client = null;
176                         this.closed = true;             
177                         this.in_close = false;
178                 }
179                 /**
180                 utility method to build variant based queries
181                 */
182                 public Variant buildDict (...) {
183                         var builder = new GLib.VariantBuilder (new GLib.VariantType ("a{sv}"));
184                         var l = va_list ();
185                         while (true) {
186                                 string? key = l.arg ();
187                                 if (key == null) {
188                                         break;
189                                 }
190                                 Variant val = l.arg ();
191                                 builder.add ("{sv}", key, val);
192                         }
193                         return builder.end ();
194                 }
195                  
196                 public override void client_accepted (Jsonrpc.Client client) 
197                 {
198                         if (this.jsonrpc_client == null) {
199                                 this.jsonrpc_client = client;
200                                 
201                                 GLib.debug("client accepted connection - calling init server");
202                                 this.log(LanguageClientAction.ACCEPT, "client accepted");
203
204                                 this.jsonrpc_client.notification.connect((method, paramz) => {
205                                         this.onNotification(method, paramz);
206                                 });
207                                  
208                                 this.jsonrpc_client.failed.connect(() => {
209                                         this.log(LanguageClientAction.ERROR_RPC, "client failed");
210                                         this.onClose();
211                                         
212                                         GLib.debug("language server server has failed");
213                                 });
214
215                                 this.initialize_server ();
216                         } 
217                                          
218                          
219                 }
220                 public bool isReady()
221                 {
222                         if (this.closed) {
223                                 this.log(LanguageClientAction.RESTART,"closed is set - restarting");
224                                 GLib.debug("server stopped = restarting");
225                                 this.initialized = false;
226                                 this.closed = false;
227                                 this.startServer();
228                                 foreach(var f in this.open_files) {
229                                         this.document_open(f);
230                                 }
231                                 return false; // can't do an operation yet?
232                                  
233                         }
234                         
235                         if (!this.initialized) {
236                                 GLib.debug("Server has not been initialized");
237                                 return false;
238                         }
239                         if (this.sent_shutdown) {
240                                 GLib.debug("Server has been started its shutting down process");
241                                 return false;
242                         }
243                         // restart server..
244
245                         
246                         
247                         return true;
248                 }
249                 
250                 
251                 public abstract  void initialize_server();
252                 public abstract  void startServer();
253                 //public abstract   void  initialize_server()  ;
254                  
255                 
256                 
257                 
258                 public void onNotification(string method, Variant? return_value)
259                 {
260                         switch (method) {
261                                 case "textDocument/publishDiagnostics":
262                                         this.onDiagnostic(return_value);
263                                         return;
264                                 default: 
265                                         break;
266                                  
267                         }
268                         GLib.debug("got notification %s : %s",  method , Json.to_string (Json.gvariant_serialize (return_value), true));
269                         
270                 }
271                 
272                 /***
273                 
274                 */
275                 public void onDiagnostic(Variant? return_value) 
276                 {
277
278                         var dg = Json.gobject_deserialize (typeof (Lsp.Diagnostics), Json.gvariant_serialize (return_value)) as Lsp.Diagnostics; 
279                         this.log(LanguageClientAction.DIAG, dg.filename);
280                         var f = this.project.getByPath(dg.filename);
281                         if (f == null) {
282                                 //GLib.debug("no file %s", dg.uri);
283                                 this.project.updateErrorsforFile(null);
284                                 return;
285                         }
286                         foreach(var v in f.errorsByType.values) {
287                                 v.remove_all();
288                         }
289                         foreach(var diag in dg.diagnostics) {
290                                 var ce = new CompileError.new_from_diagnostic(f, diag);
291                                 if (!f.errorsByType.has_key(ce.category)) {
292                                         f.errorsByType.set(ce.category, new  GLib.ListStore(typeof(CompileError)));
293                                 }
294                                 f.errorsByType.get(ce.category).append(ce);
295                         }
296                         f.project.updateErrorsforFile(f);
297                         
298                 }
299                 
300                 public void document_open (JsRender.JsRender file)  
301                 {
302                         if (!this.isReady()) {
303                                 return;
304                         }
305                         if (!this.open_files.contains(file)) {
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   void document_save (JsRender.JsRender file)  
333         {
334                         if (!this.isReady()) {
335                                 return;
336                         }
337                         this.change_queue_file = null;
338                         GLib.debug ("LS send save");
339                          try {
340                                   this.jsonrpc_client.send_notification  (
341                                         "textDocument/didChange",
342                                         this.buildDict (  
343                                                 textDocument : this.buildDict (    ///TextDocumentItem;
344                                                         uri: new GLib.Variant.string (file.to_url()),
345                                                         version :  new GLib.Variant.uint64 ( (uint64) file.version)
346                                                 )
347                                         ),
348                                         null 
349                                 );
350                                 this.log(LanguageClientAction.SAVE, file.path);
351                         } catch( GLib.Error  e) {
352                                 this.log(LanguageClientAction.ERROR_RPC, e.message);
353                                 GLib.debug ("LS   save err %s", e.message);
354                                 this.onClose();
355                         }
356
357          
358         }
359                 public   void document_close (JsRender.JsRender file) 
360         {
361                         if (!this.isReady()) {
362                                 return;
363                         }
364                         this.change_queue_file = null;
365                         
366                         if (this.open_files.contains(file)) {
367                                 this.open_files.remove(file);
368                         }
369                         this.log(LanguageClientAction.CLOSE, file.path);
370                         GLib.debug ("LS send close");
371                         try {
372                                   this.jsonrpc_client.send_notification  (
373                                         "textDocument/didChange",
374                                         this.buildDict (  
375                                                 textDocument : this.buildDict (    ///TextDocumentItem;
376                                                         uri: new GLib.Variant.string (file.to_url())
377                                                         
378                                                 )
379                                         ),
380                                         null  
381                                 );
382                         } catch( GLib.Error  e) {
383                                 this.log(LanguageClientAction.ERROR_RPC, e.message);
384                                 GLib.debug ("LS close err %s", e.message);
385                                 this.onClose();
386                         }
387
388          
389         }
390         
391          
392                 public void document_change (JsRender.JsRender file   )    
393                 {
394                         if (this.change_queue_file != null && this.change_queue_file.path != file.path) {
395                                 this.document_change_real(this.change_queue_file, this.change_queue_file_source);
396                         }
397                         
398                         this.countdown = 3;
399                         this.change_queue_file = file;
400                          
401                         
402
403                 }
404         
405
406                 public void document_change_real (JsRender.JsRender file, string contents)  
407         {
408                         if (!this.isReady()) {
409                                 return;
410                         }
411                              
412                         
413                         GLib.debug ("LS send change");
414                         var ar = new Json.Array();
415                         var obj = new Json.Object();
416                         obj.set_string_member("text", contents);
417                         ar.add_object_element(obj);
418                         var node = new Json.Node(Json.NodeType.ARRAY);
419                         node.set_array(ar);
420                         this.log(LanguageClientAction.CHANGE, file.path);
421                          try {
422                                 this.jsonrpc_client.send_notification (
423                                         "textDocument/didChange",
424                                         this.buildDict (  
425                                                 textDocument : this.buildDict (    ///TextDocumentItem;
426                                                         uri: new GLib.Variant.string (file.to_url()),
427                                                         version :  new GLib.Variant.uint64 ( (uint64) file.version) 
428                                                 ),
429                                                 contentChanges : Json.gvariant_deserialize (node, null)
430                                                 
431                                         ),
432                                         null 
433                                 );
434                         } catch( GLib.Error  e) {
435                                 this.log(LanguageClientAction.ERROR_RPC, e.message);
436                                 GLib.debug ("LS change err %s", e.message);
437                                 this.onClose();
438                         }
439
440          
441         }
442         // called by close window (on last window)...
443                 public   void exit () throws GLib.Error 
444                 {
445                         if (!this.isReady()) {
446                         
447                                 return;
448                         }
449                         this.log(LanguageClientAction.TERM, "SEND exit");
450                  
451                           this.jsonrpc_client.send_notification (
452                                 "exit",
453                                 null,
454                                 null 
455                         );
456                         this.onClose();
457
458                 }
459                 // not used currently..
460                 public async void shutdown () throws GLib.Error 
461                 {
462                         if (!this.isReady()) {
463                                 return;
464                         }
465                         this.log(LanguageClientAction.TERM, "SEND shutodwn");
466                         this.sent_shutdown  = true;
467                         Variant? return_value;
468                         yield this.jsonrpc_client.call_async (
469                                 "shutdown",
470                                 null,
471                                 null,
472                                 out return_value
473                         );
474                         GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));               
475                 }
476                 //public async  ??/symbol (string symbol) throws GLib.Error {
477                 
478                 // and now for the important styff..
479                 
480                 /*
481                 
482                 @triggerType 1 = typing or ctl-spac, 2 = tiggercharactres?  3= inside completion?
483                 */
484                  public async Lsp.CompletionList?  completion(JsRender.JsRender file, int line, int offset , int triggerType = 1) throws GLib.Error 
485                  {
486                         /* partial_result_token ,  work_done_token   context = null) */
487                         GLib.debug("get completion %s @ %d:%d", file.relpath, line, offset);
488                         
489                         var ret = new Lsp.CompletionList();     
490                         
491                     if (!this.isReady()) {
492                         GLib.debug("completion - language server not ready");
493                                 return ret;
494                         }
495                         // make sure completion has the latest info..
496                         //if (this.change_queue_file != null && this.change_queue_file.path != file.path) {
497                         //      this.document_change_real(this.change_queue_file, this.change_queue_file_source);
498                         //      this.change_queue_file != null;
499                         //}
500                         this.log(LanguageClientAction.COMPLETE, "SEND complete  %s @ %d:%d".printf(file.relpath, line, offset) );
501                         
502                         Variant? return_value;
503                         
504                         var args = this.buildDict (  
505                                         context : this.buildDict (    ///CompletionContext;
506                                                 triggerKind: new GLib.Variant.int32 (triggerType) 
507                                         //      triggerCharacter :  new GLib.Variant.string ("")
508                                         ),
509                                         textDocument : this.buildDict (    ///TextDocumentItem;
510                                                 uri: new GLib.Variant.string (file.to_url()),
511                                                 version :  new GLib.Variant.uint64 ( (uint64) file.version) 
512                                         ), 
513                                         position :  this.buildDict ( 
514                                                 line :  new GLib.Variant.uint64 ( (uint) line) ,
515                                                 character :  new GLib.Variant.uint64 ( uint.max(0,  (offset -1))) 
516                                         )
517                                 );
518                          
519                         GLib.debug ("textDocument/completion send with %s", Json.to_string (Json.gvariant_serialize (args), true));                                     
520                         
521                         yield this.jsonrpc_client.call_async (
522                                 "textDocument/completion",
523                                 args,
524                                 null,
525                                 out return_value
526                         );
527                         
528                         
529                         //GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));                                     
530                         var json = Json.gvariant_serialize (return_value);
531
532
533                         if (json.get_node_type() == Json.NodeType.OBJECT) {
534                                 ret = Json.gobject_deserialize (typeof (Lsp.CompletionList), json) as Lsp.CompletionList; 
535                                 this.log(LanguageClientAction.COMPLETE_REPLY, "GOT complete  %d items".printf(ret.items.size) );
536                                 GLib.debug ("LS replied with Object");
537                                 return ret;
538                         }  
539
540                         if (json.get_node_type() != Json.NodeType.ARRAY) {
541                                 GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));                                       
542                                 this.log(LanguageClientAction.ERROR_REPLY, "GOT something else??");
543                                 return ret;
544                         
545                         }
546                         var ar = json.get_array();                      
547                         
548                         for(var i = 0; i < ar.get_length(); i++ ) {
549                                 var add= Json.gobject_deserialize ( typeof (Lsp.CompletionItem),  ar.get_element(i)) as Lsp.CompletionItem;
550                                 ret.items.add( add);
551                                          
552                         }
553                         this.log(LanguageClientAction.COMPLETE_REPLY, "GOT array %d items".printf(ret.items.size) );
554                         GLib.debug ("LS replied with Array");
555                         return ret;
556                 
557
558                 }
559                 //CompletionListInfo.itmems.parse_varient  or CompletionListInfo.parsevarient
560                 public async Gee.ArrayList<Lsp.DocumentSymbol> syntax (JsRender.JsRender file) throws GLib.Error 
561                  {
562                         /* partial_result_token ,  work_done_token   context = null) */
563                         GLib.debug("get syntax %s", file.relpath);
564                         var ret = new Gee.ArrayList<Lsp.DocumentSymbol>();      
565                         //ret = null;
566                     if (!this.isReady()) {
567                                 return ret;
568                         }
569                         Variant? return_value;
570                         yield this.jsonrpc_client.call_async (
571                                 "textDocument/documentSymbol",
572                                 this.buildDict (  
573                                          
574                                         textDocument : this.buildDict (    ///TextDocumentItem;
575                                                 uri: new GLib.Variant.string (file.to_url()),
576                                                 version :  new GLib.Variant.uint64 ( (uint64) file.version) 
577                                         ) 
578                                          
579                                 ),
580                                 null,
581                                 out return_value
582                         );
583                         
584                         
585                         GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));                                       
586                         var json = Json.gvariant_serialize (return_value);
587                          
588                          
589
590                         var ar = json.get_array();
591                         for(var i = 0; i < ar.get_length(); i++ ) {
592                                 var add= Json.gobject_deserialize ( typeof (Lsp.DocumentSymbol),  ar.get_element(i)) as Lsp.DocumentSymbol;
593                                 ret.add( add);
594                                          
595                         }
596                                 return ret ;
597                         
598                 
599
600                 }
601                 
602         }
603 }