fix #7968 - language server support for syntax check and 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  
13
14         public abstract class LanguageClient :   Jsonrpc.Server {
15         
16                 public Project.Project project;
17                 private GLib.SubprocessLauncher launcher;
18                 private GLib.Subprocess subprocess;
19                 private IOStream subprocess_stream;
20             public Jsonrpc.Client? jsonrpc_client = null;
21                 
22                 protected LanguageClient(Project.Project project)
23                 {
24                         // extend versions will proably call initialize to start and connect to server.
25                         this.project = project;
26                         
27                 
28                 }
29                  
30                 public bool initProcess(string process_path)
31                 {
32                         this.launcher = new GLib.SubprocessLauncher (SubprocessFlags.STDIN_PIPE | SubprocessFlags.STDOUT_PIPE);
33                         this.launcher.set_environ(GLib.Environ.get());
34                         try {
35                                 this.subprocess = launcher.spawnv ({ process_path });
36                                 var input_stream = this.subprocess.get_stdout_pipe ();
37                                 var output_stream = this.subprocess.get_stdin_pipe ();
38  
39                                 if (input_stream is GLib.UnixInputStream && output_stream is GLib.UnixOutputStream) {
40                                         // set nonblocking
41                                         if (!GLib.Unix.set_fd_nonblocking(((GLib.UnixInputStream)input_stream).fd, true)
42                                          || !GLib.Unix.set_fd_nonblocking (((GLib.UnixOutputStream)output_stream).fd, true)) 
43                                          {
44                                                  GLib.debug("could not set pipes to nonblocking");
45                                             return false;
46                                     }
47                             }
48                             this.subprocess_stream = new SimpleIOStream (input_stream, output_stream);
49                         this.accept_io_stream ( this.subprocess_stream);
50                         } catch (GLib.Error e) {
51                                 GLib.debug("subprocess startup error %s", e.message);           
52                                 return false;
53                 }
54             return true;
55         }
56          
57                 /**
58                 utility method to build variant based queries
59                 */
60                 public Variant buildDict (...) {
61                         var builder = new GLib.VariantBuilder (new GLib.VariantType ("a{sv}"));
62                         var l = va_list ();
63                         while (true) {
64                                 string? key = l.arg ();
65                                 if (key == null) {
66                                         break;
67                                 }
68                                 Variant val = l.arg ();
69                                 builder.add ("{sv}", key, val);
70                         }
71                         return builder.end ();
72                 }
73                  
74                 public override void client_accepted (Jsonrpc.Client client) 
75                 {
76                         if (this.jsonrpc_client == null) {
77                                 this.jsonrpc_client = client;
78                                 
79                                 GLib.debug("client accepted connection - calling init server");
80                                  
81
82                                 this.jsonrpc_client.notification.connect((method, paramz) => {
83                                         this.onNotification(method, paramz);
84                                 });
85                                  
86                                 this.jsonrpc_client.failed.connect(() => {
87                                         GLib.debug("language server server has failed");
88                                 });
89
90                                 this.initialize_server ();
91                         } 
92                                          
93                          
94                 }
95                 public bool isReady()
96                 {
97                         if (!this.initialized) {
98                                 GLib.debug("Server has not been initialized");
99                                 return false;
100                         }
101                         if (this.sent_shutdown) {
102                                 GLib.debug("Server has been started its shutting down process");
103                                 return false;
104                         }
105                         return true;
106                 }
107                 
108                 
109                 public abstract  void initialize_server();
110                 
111                 //public abstract   void  initialize_server()  ;
112                  
113                 protected bool initialized = false;
114                 bool sent_shutdown = false;
115                 
116                 
117                 public void onNotification(string method, Variant? return_value)
118                 {
119                         switch (method) {
120                                 case "textDocument/publishDiagnostics":
121                                         this.onDiagnostic(return_value);
122                                         return;
123                                 default: 
124                                         break;
125                                  
126                         }
127                         GLib.debug("got notification %s : %s",  method , Json.to_string (Json.gvariant_serialize (return_value), true));
128                         
129                 }
130                 
131                 /***
132                 
133                 */
134                 public void onDiagnostic(Variant? return_value) 
135                 {
136                         var dg = Json.gobject_deserialize (typeof (Lsp.Diagnostics), Json.gvariant_serialize (return_value)) as Lsp.Diagnostics; 
137                         var f = this.project.getByPath(dg.filename);
138                         if (f == null) {
139                                 GLib.debug("no file %s", dg.uri);
140                                 return;
141                         }
142                         foreach(var v in f.errorsByType.values) {
143                                 v.remove_all();
144                         }
145                         foreach(var diag in dg.diagnostics) {
146                                 var ce = new CompileError.new_from_diagnostic(f, diag);
147                                 if (!f.errorsByType.has_key(ce.category)) {
148                                         f.errorsByType.set(ce.category, new  GLib.ListStore(typeof(CompileError)));
149                                 }
150                                 f.errorsByType.get(ce.category).append(ce);
151                         }
152                         f.project.updateErrorsforFile(f);
153                         
154                 }
155                 
156                 public void document_open (JsRender.JsRender file)  
157                 {
158                         if (!this.isReady()) {
159                                 return;
160                         }
161                         GLib.debug ("LS sent open");                     
162                         try {
163                                 this.jsonrpc_client.send_notification (
164                                         "textDocument/didOpen",
165                                         this.buildDict (
166                                                 textDocument : this.buildDict (
167                                                         uri: new Variant.string (file.to_url()),
168                                                         languageId :  new Variant.string (file.language_id()),
169                                                         version :  new GLib.Variant.uint64 ( (uint64) file.version),
170                                                         text : new Variant.string (file.toSource())
171                                                 )
172                                         ),
173                                         null
174                                 );
175                         } catch( GLib.Error  e) {
176                                 GLib.debug ("LS sent open err %s", e.message);
177                         }
178
179                 }
180                 
181                 public   void document_save (JsRender.JsRender file)  
182         {
183                         if (!this.isReady()) {
184                                 return;
185                         }
186                                 GLib.debug ("LS send save");
187                          try {
188                                   this.jsonrpc_client.send_notification  (
189                                         "textDocument/didChange",
190                                         this.buildDict (  
191                                                 textDocument : this.buildDict (    ///TextDocumentItem;
192                                                         uri: new GLib.Variant.string (file.to_url())
193                                                         
194                                                 )
195                                         ),
196                                         null 
197                                 );
198                         } catch( GLib.Error  e) {
199                                 GLib.debug ("LS sent save err %s", e.message);
200                         }
201
202          
203         }
204                 public   void document_close (JsRender.JsRender file) 
205         {
206                         if (!this.isReady()) {
207                                 return;
208                         }
209                                                         GLib.debug ("LS send close");
210                         try {
211                                   this.jsonrpc_client.send_notification  (
212                                         "textDocument/didChange",
213                                         this.buildDict (  
214                                                 textDocument : this.buildDict (    ///TextDocumentItem;
215                                                         uri: new GLib.Variant.string (file.to_url())
216                                                         
217                                                 )
218                                         ),
219                                         null  
220                                 );
221                         } catch( GLib.Error  e) {
222                                 GLib.debug ("LS sent close err %s", e.message);
223                         }
224
225          
226         }
227                 public    void document_change (JsRender.JsRender file)  
228         {
229                         if (!this.isReady()) {
230                                 return;
231                         }
232                         GLib.debug ("LS send change");
233                         var ar = new Json.Array();
234                         var obj = new Json.Object();
235                         obj.set_string_member("text", file.toSource());
236                         ar.add_object_element(obj);
237                         var node = new Json.Node(Json.NodeType.ARRAY);
238                         node.set_array(ar);
239                          try {
240                                 this.jsonrpc_client.send_notification (
241                                         "textDocument/didChange",
242                                         this.buildDict (  
243                                                 textDocument : this.buildDict (    ///TextDocumentItem;
244                                                         uri: new GLib.Variant.string (file.to_url()),
245                                                         version :  new GLib.Variant.uint64 ( (uint64) file.version) 
246                                                 ),
247                                                 contentChanges : Json.gvariant_deserialize (node, null)
248                                                 
249                                         ),
250                                         null 
251                                 );
252                         } catch( GLib.Error  e) {
253                                 GLib.debug ("LS sent close err %s", e.message);
254                         }
255
256          
257         }
258                 public   void exit () throws GLib.Error 
259                 {
260                         if (!this.isReady()) {
261                                 return;
262                         }
263                         this.sent_shutdown  = true;
264                  
265                           this.jsonrpc_client.send_notification_async (
266                                 "exit",
267                                 null,
268                                 null 
269                         );
270                         
271                 }
272                 public async void shutdown () throws GLib.Error 
273                 {
274                         if (!this.isReady()) {
275                                 return;
276                         }
277                         this.sent_shutdown  = true;
278                         Variant? return_value;
279                         yield this.jsonrpc_client.call_async (
280                                 "shutdown",
281                                 null,
282                                 null,
283                                 out return_value
284                         );
285                         GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));               
286                 }
287                 //public async  ??/symbol (string symbol) throws GLib.Error {
288                 
289                 // and now for the important styff..
290                 
291                 /*
292                 
293                 @triggerType 1 = typing or ctl-spac, 2 = tiggercharactres?  3= inside completion?
294                 */
295                  public async void completion (JsRender.JsRender file, int line, int offset , int triggerType = 1, out Lsp.CompletionList? ret) throws GLib.Error 
296                  {
297                         /* partial_result_token ,  work_done_token   context = null) */
298                         GLib.debug("get completion %s @ %d:%d", file.relpath, line, offset);
299                         
300                         ret = null;
301                     if (!this.isReady()) {
302                                 return;
303                         }
304                         Variant? return_value;
305                         yield this.jsonrpc_client.call_async (
306                                 "textDocument/completion",
307                                 this.buildDict (  
308                                         context : this.buildDict (    ///CompletionContext;
309                                                 triggerKind: new GLib.Variant.int32 (triggerType) 
310                                         //      triggerCharacter :  new GLib.Variant.string ("")
311                                         ),
312                                         textDocument : this.buildDict (    ///TextDocumentItem;
313                                                 uri: new GLib.Variant.string (file.to_url()),
314                                                 version :  new GLib.Variant.uint64 ( (uint64) file.version) 
315                                         ), 
316                                         position :  this.buildDict ( 
317                                                 line :  new GLib.Variant.uint64 ( (uint64) line) ,
318                                                 character :  new GLib.Variant.uint64 ( (uint64) offset) 
319                                         )
320                                 ),
321                                 null,
322                                 out return_value
323                         );
324                         
325                         
326                         //GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true));                                     
327                         var json = Json.gvariant_serialize (return_value);
328                         var ar = json.get_array();
329
330                         if (ar == null) {
331                                 ret = Json.gobject_deserialize (typeof (Lsp.CompletionList), json) as Lsp.CompletionList; 
332                                 return;
333                         }  
334                         ret = new Lsp.CompletionList(); 
335                         for(var i = 0; i < ar.get_length(); i++ ) {
336                                 var add= Json.gobject_deserialize ( typeof (Lsp.CompletionItem),  ar.get_element(i)) as Lsp.CompletionItem;
337                                 ret.items.add( add);
338                                          
339                         }
340                                   
341                         
342                 
343
344                 }
345                 //CompletionListInfo.itmems.parse_varient  or CompletionListInfo.parsevarient
346
347
348                 
349         }
350 }