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