missing refs, slight change to completion provider
[roobuilder] / src / Palete / CompletionProvider.vala
1  
2 //using Gtk;
3
4 // not sure why - but extending Gtk.SourceCompletionProvider seems to give an error..
5 namespace Palete {
6
7     public class CompletionProvider : Object, GtkSource.CompletionProvider
8     {
9                 public JsRender.JsRender file {
10                         get { return this.editor.file; }
11                         private set {}
12                 }
13                 public Editor editor; 
14                 //public WindowState windowstate;
15                 public CompletionModel model;
16                 global::Gtk.StringFilter? filter = null;
17
18                 public CompletionProvider(Editor editor)
19                 {
20                     this.editor  = editor;
21                  
22                    // this.windowstate = null; // not ready until the UI is built.
23                     
24                 }
25
26                 public string get_name ()
27                 {
28                   return  "roojsbuilder";
29                 }
30
31                 public int get_priority (GtkSource.CompletionContext context)
32                 {
33                   return 200;
34                 }
35                 
36                 
37                 public bool is_trigger(global::Gtk.TextIter  iter, unichar ch)
38                 {
39                         if (this.in_populate || ch == 32) {
40                                 return false;
41                         }
42                         if (this.editor.buffer.el.iter_has_context_class(iter, "comment") ||
43                                 this.editor.buffer.el.iter_has_context_class(iter, "string")
44                         ) { 
45                                 return false;
46                         }
47                         
48                         
49                         //GLib.debug("should trigger? %c", (int) ch);
50                         
51                         
52                         return true;
53                 }
54                 
55                 public  void activate (GtkSource.CompletionContext context, GtkSource.CompletionProposal proposal)
56                 {
57                         GLib.debug("compelte activate");
58                         
59                         var  p = (CompletionProposal) proposal;
60                         GLib.debug("lsp says use %s", p.ci.insertText);
61                         
62                         
63                         global::Gtk.TextMark end_mark = null;
64                         global::Gtk.TextIter begin, end;
65
66                         if (!context.get_bounds(out begin, out end)) {
67                                 return;
68                         }  
69                         var buffer = begin.get_buffer();
70                 
71                         var  word = p.label;
72                         if (p.ci.kind == Lsp.CompletionItemKind.Method || p.ci.kind == Lsp.CompletionItemKind.Function) {
73                                 var bits = p.text.split("(");
74                                 var abits = bits[1].split(")");
75                                 var args = abits[0].split(",");
76                                 
77                                 word += "(";
78                                 for(var i = 0 ; i < args.length; i++) {
79                                         word += i > 0 ? ", " : " ";
80                                         var wbit = args[i].strip().split(" ");
81                                         if (wbit.length < 2) {
82                                                 word += wbit[0];
83                                                 continue;
84                                         }
85                                         var ty = wbit[wbit.length - 2];
86                                         ty = ty.has_suffix("?") ? "?" : "";  
87                                         word += ty + wbit[wbit.length-1]; // property type..?
88                                 }
89                                 word += args.length > 0 ? " )" : ")";
90                         }
91                         
92                         var len = -1;
93                         
94
95                         /* If the insertion cursor is within a word and the trailing characters
96                          * of the word match the suffix of the proposal, then limit how much
97                          * text we insert so that the word is completed properly.
98                          */
99                         if (!end.ends_line() &&
100                                 !end.get_char().isspace() &&
101                                 !end.ends_word ())
102                         {
103                                 var word_end = end;
104
105                                 if (word_end.forward_word_end ()) {
106                                         var text = end.get_slice(word_end);
107                                         if (text.length > word.length) {
108                                                 return;
109                                         }
110                                         if (word.has_suffix (text)) {
111                                                 //g_assert (strlen (word) >= strlen (text));
112                                                 len = word.length - text.length;
113                                                 end_mark = buffer.create_mark (null, word_end, false); 
114                                         }
115                                 }
116                         }
117
118                         buffer.begin_user_action();
119                         buffer.delete (ref begin, ref end);
120                         buffer.insert ( ref begin, word, len);
121                         buffer.end_user_action ();
122
123                         if (end_mark != null)
124                         {
125                                 buffer.get_iter_at_mark(out end, end_mark);
126                                 buffer.select_range(end,  end);
127                                 buffer.delete_mark(end_mark);
128                         }
129                 
130
131                 }
132
133
134                 public  void display (GtkSource.CompletionContext context, GtkSource.CompletionProposal proposal, GtkSource.CompletionCell cell)
135                 {
136                         //GLib.debug("compelte display");
137                         var col = cell.get_column();
138                         
139                         var p = (CompletionProposal) proposal;
140                         switch(col) {
141                                 case GtkSource.CompletionColumn.TYPED_TEXT:
142                                         cell.set_text(p.label);
143                                         break;
144                                 case GtkSource.CompletionColumn.ICON:
145 //cell.set_icon_name("lang-define-symbolic");return;
146 //cell.set_icon_name("lang-include-symbolic");return;
147 //cell.set_icon_name("lang-typedef-symbolic");return;
148 //cell.set_icon_name("lang-union-symbolic");return;                      
149                                         switch (p.ci.kind) {
150                                         
151                                                 case    Lsp.CompletionItemKind.Text: cell.set_icon_name("completion-snippet-symbolic");return;
152                                                 case    Lsp.CompletionItemKind.Method: cell.set_icon_name("lang-method-symbolic");return;
153                                                 case    Lsp.CompletionItemKind.Function: cell.set_icon_name("lang-function-symbolic");return;
154                                                 case    Lsp.CompletionItemKind.Constructor: cell.set_icon_name("lang-method-symbolic");return;
155                                                 case    Lsp.CompletionItemKind.Field: cell.set_icon_name("lang-struct-field-symbolic");return;
156                                                 case    Lsp.CompletionItemKind.Variable: cell.set_icon_name("lang-variable-symbolic");return;
157                                                 case    Lsp.CompletionItemKind.Class: cell.set_icon_name("lang-class-symbolic");return;
158                                                 case    Lsp.CompletionItemKind.Interface: cell.set_icon_name("lang-class-symbolic");return;
159                                                 case    Lsp.CompletionItemKind.Module: cell.set_icon_name("lang-namespace-symbolic");return;
160                                                 case    Lsp.CompletionItemKind.Property:cell.set_icon_name("lang-struct-field-symbolic");return;
161                                                 case    Lsp.CompletionItemKind.Unit: cell.set_icon_name("lang-variable-symbolic");return;
162                                                 case    Lsp.CompletionItemKind.Value: cell.set_icon_name("lang-variable-symbolic");return;
163                                                 case    Lsp.CompletionItemKind.Enum: cell.set_icon_name("lang-enum-symbolic");return;
164                                                 case    Lsp.CompletionItemKind.Keyword: cell.set_icon_name("completion-word-symbolic");return;
165                                                 case    Lsp.CompletionItemKind.Snippet: cell.set_icon_name("completion-snippet-symbolic");return;
166
167                                                 case    Lsp.CompletionItemKind.Color: cell.set_icon_name("lang-typedef-symbolic");return;
168                                                 case    Lsp.CompletionItemKind.File:cell.set_icon_name("lang-typedef-symbolic");return;
169                                                 case    Lsp.CompletionItemKind.Reference: cell.set_icon_name("lang-typedef-symbolic");return;
170                                                 case    Lsp.CompletionItemKind.Folder:cell.set_icon_name("lang-typedef-symbolic");return;
171                                                 case    Lsp.CompletionItemKind.EnumMember: cell.set_icon_name("lang-typedef-symbolic");return;
172                                                 case    Lsp.CompletionItemKind.Constant:cell.set_icon_name("lang-typedef-symbolic");return;
173                                                 case    Lsp.CompletionItemKind.Struct: cell.set_icon_name("lang-struct-symbolic");return;
174                                                 case    Lsp.CompletionItemKind.Event:cell.set_icon_name("lang-typedef-symbolic");return;
175                                                 case    Lsp.CompletionItemKind.Operator:cell.set_icon_name("lang-typedef-symbolic");return;
176                                                 case    Lsp.CompletionItemKind.TypeParameter:cell.set_icon_name("lang-typedef-symbolic");return;
177                                                 default:
178
179
180
181                                         
182                                                         cell.set_icon_name("completion-snippet-symbolic");
183                                                         return;
184                                                 }
185                                                 
186                                         
187                                 case  GtkSource.CompletionColumn.COMMENT:
188                                         cell.set_text(p.text);
189                                         break;
190                                 case GtkSource.CompletionColumn.DETAILS:
191                                         if (p.ci.documentation != null) {
192                                                 cell.set_text(p.ci.documentation.value);
193                                                 return;
194                                         }
195                                 
196                                         cell.set_text(p.text);
197                                         break;
198                                 default:
199                                         cell.set_text(null);
200                                         break;
201                         }       
202                 }
203
204                 bool in_populate = false;
205          
206                 internal  async GLib.ListModel populate_async (GtkSource.CompletionContext context, GLib.Cancellable? cancellable) 
207                 {
208                         GLib.debug("pupoulate async");
209                         var ret = new GLib.ListStore(typeof(CompletionProposal));
210                         
211                         if (this.in_populate) {
212                                 GLib.debug("pupoulate async  - skipped waiting for reply");
213                                 return ret;
214                         }
215                         this.in_populate = true;
216
217                         global::Gtk.TextIter begin, end;
218                         Lsp.CompletionList res;
219                         if (context.get_bounds (out begin, out end)) {
220                                 var line = end.get_line();
221                                 var offset =  end.get_line_offset();
222                                 if (this.editor.prop != null) {
223                                 //      tried line -1 (does not work)
224                                         GLib.debug("node pad = '%s' %d", this.editor.node.node_pad, this.editor.node.node_pad.length);
225                                         
226                                         line += this.editor.prop.start_line ; 
227                                         // this is based on Gtk using tabs (hence 1/2 chars);
228                                         offset += this.editor.node.node_pad.length;
229                                         // javascript listeners are indented 2 more spaces.
230                                         if (this.editor.prop.ptype == JsRender.NodePropType.LISTENER) {
231                                                 offset += 2;
232                                         }
233                                 } 
234                                 //  this should not really be slow, as it's a quick repsonse
235                                 yield this.file.getLanguageServer().document_change_force(this.file, this.editor.tempFileContents());                           
236                                 try {
237                                         GLib.debug("sending request to language server %s", this.file.getLanguageServer().get_type().name());
238                                         
239                                         res = yield this.file.getLanguageServer().completion(this.file, line, offset, 1);
240                                 } catch (GLib.Error e) {
241                                         GLib.debug("got error %s", e.message);
242                                         this.in_populate = false;
243                                         return ret;
244                                 }
245                                 
246                         } else {
247                                 this.in_populate = false;
248                                 return ret;
249                         }
250                         
251                         GLib.debug("pupoulate async  - got reply");
252                         this.model = new CompletionModel(this, context, res, cancellable); 
253                         var word = context.get_word();
254                         GLib.debug("Context word is %s, %d", word, (int)word.length);
255                         
256                         
257                         var expression = new global::Gtk.PropertyExpression(typeof(CompletionProposal), null, "label");
258                         this.filter = new global::Gtk.StringFilter(expression);
259                         this.filter.set_search( word);
260                         var  filter_model = new global::Gtk.FilterListModel(this.model, this.filter); 
261                         filter.match_mode = global::Gtk.StringFilterMatchMode.PREFIX;
262                         filter_model.set_incremental(true);
263                         this.in_populate = false;
264                         return filter_model; 
265                         
266                          
267                         
268                 }
269
270                 internal  void refilter (GtkSource.CompletionContext context, GLib.ListModel in_model)
271                 {
272  
273                         //GLib.debug("pupoulate refilter");
274                         if (this.filter == null) {
275                                 return;
276                         }
277
278                         var word = context.get_word();
279                         this.filter.set_search(word);
280                  
281                 
282                 }
283
284
285 /*
286                 public bool activate_proposal (GtkSource.CompletionProposal proposal, TextIter iter)
287                 {
288                         var istart = iter;
289                         istart.backward_find_char(is_space, null);
290                         istart.forward_char();
291
292                 //    var search = iter.get_text(istart);           
293                 
294                         var buffer = iter.get_buffer();
295                         buffer.delete(ref istart, ref iter);
296                         buffer.insert(ref istart, proposal.get_text(), -1);
297                 
298                         return true;
299                 }
300   
301          
302
303                 private bool is_space(unichar space){
304                         return space.isspace() || space.to_string() == "";
305                 }
306                 */
307                  
308         }
309         public class CompletionModel : Object, GLib.ListModel 
310         {
311                 CompletionProvider provider;
312                 Gee.ArrayList<CompletionProposal> items;
313                 string search;
314                 int minimum_word_size = 2;
315                 
316                 public Cancellable? cancellable;
317                 
318                 public CompletionModel(CompletionProvider provider, GtkSource.CompletionContext context, Lsp.CompletionList? res, Cancellable? cancellable)
319                 {
320                         this.provider = provider;
321                         this.cancellable = cancellable;
322                         this.items = new Gee.ArrayList<CompletionProposal>();
323                         
324                         var word = context.get_word();
325                         GLib.debug("looking for %s", word);
326                         this.search = word;
327                         if (res != null) {
328                                 foreach(var comp in res.items) {
329                                          if (comp.label == "_") { // skip '_'
330                                                 continue;
331                                         }
332                                         this.items.add(new CompletionProposal(comp));   
333                                         
334                                 }
335                         }
336                     print("GOT %d results\n", (int) items.size); 
337                         // WHY TWICE?
338                     if (this.items.size < this.minimum_word_size) {
339                                 return;
340                     }
341                 
342                     items.sort((a, b) => {
343                             return ((string)(a.label)).collate((string)(b.label));
344                     });
345                 
346                 }
347                 
348                  
349                 
350                 public GLib.Object? get_item (uint pos)
351                 {
352                         return (Object) this.items.get((int) pos);
353                 }
354                 public GLib.Type  get_item_type ()
355                 {
356                         return typeof(GtkSource.CompletionProposal);
357                 }
358                 public   uint get_n_items () 
359                 {
360                         return this.items.size;
361                 }
362                 public bool can_filter (string word) 
363                 {
364                         if (word == null || word[0] == 0) {
365                                 return false;
366                         }
367  
368                         if (word.length < this.minimum_word_size) {
369                                 return false;
370                         }
371
372                         /* If the new word starts with our initial word, then we can simply
373                          * refilter outside this model using a GtkFilterListModel.
374                          */
375                          
376                          return word.has_prefix(this.search); 
377                 }
378                 public void  cancel ()
379                 {
380                         if (this.cancellable != null) {
381                                 this.cancellable.cancel();
382                         }
383                 }
384
385
386                 
387         }
388         public class CompletionProposal : Object, GtkSource.CompletionProposal 
389         {
390                 
391                 public string label { get; set; default = ""; }
392                 
393                 public string text  { get; set; default = ""; }
394                 public string info  { get; set; default = ""; }
395                 
396                 public Lsp.CompletionItem ci;
397                 
398                 public CompletionProposal(Lsp.CompletionItem ci) //string label, string text, string info)
399                 {
400                         
401                         this.ci = ci;
402                         this.text = ci.detail == null ? "" : ci.detail ;
403                         this.label = ci.label;
404                         this.info = ci.documentation == null ? "": ci.documentation.value;
405                         //GLib.debug("SET: detail =%s, label = %s; info =%s", ci.detail, ci.label, "to long..");
406                 }
407                 
408         }
409
410
411