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