revert testing -1 code
[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                         if (word.length < 1) {
256                                 word = " "; // this should filter out everything, and prevent it displaying 
257                         }
258                         
259                         var expression = new global::Gtk.PropertyExpression(typeof(CompletionProposal), null, "label");
260                         this.filter = new global::Gtk.StringFilter(expression);
261                         this.filter.set_search( word);
262                         var  filter_model = new global::Gtk.FilterListModel(this.model, this.filter); 
263                         filter.match_mode = global::Gtk.StringFilterMatchMode.PREFIX;
264                         filter_model.set_incremental(true);
265                         this.in_populate = false;
266                         return filter_model; 
267                         
268                          
269                         
270                 }
271
272                 internal  void refilter (GtkSource.CompletionContext context, GLib.ListModel in_model)
273                 {
274  
275                         //GLib.debug("pupoulate refilter");
276                         if (this.filter == null) {
277                                 return;
278                         }
279
280                         var word = context.get_word();
281                         this.filter.set_search(word);
282                  
283                 
284                 }
285
286
287 /*
288                 public bool activate_proposal (GtkSource.CompletionProposal proposal, TextIter iter)
289                 {
290                         var istart = iter;
291                         istart.backward_find_char(is_space, null);
292                         istart.forward_char();
293
294                 //    var search = iter.get_text(istart);           
295                 
296                         var buffer = iter.get_buffer();
297                         buffer.delete(ref istart, ref iter);
298                         buffer.insert(ref istart, proposal.get_text(), -1);
299                 
300                         return true;
301                 }
302   
303          
304
305                 private bool is_space(unichar space){
306                         return space.isspace() || space.to_string() == "";
307                 }
308                 */
309                  
310         }
311         public class CompletionModel : Object, GLib.ListModel 
312         {
313                 CompletionProvider provider;
314                 Gee.ArrayList<CompletionProposal> items;
315                 string search;
316                 int minimum_word_size = 2;
317                 
318                 public Cancellable? cancellable;
319                 
320                 public CompletionModel(CompletionProvider provider, GtkSource.CompletionContext context, Lsp.CompletionList? res, Cancellable? cancellable)
321                 {
322                         this.provider = provider;
323                         this.cancellable = cancellable;
324                         this.items = new Gee.ArrayList<CompletionProposal>();
325                         
326                         var word = context.get_word();
327                         GLib.debug("looking for %s", word);
328                         this.search = word;
329                         if (res != null) {
330                                 foreach(var comp in res.items) {
331                                          if (comp.label == "_") { // skip '_'
332                                                 continue;
333                                         }
334                                         this.items.add(new CompletionProposal(comp));   
335                                         
336                                 }
337                         }
338                     print("GOT %d results\n", (int) items.size); 
339                         // WHY TWICE?
340                     if (this.items.size < this.minimum_word_size) {
341                                 return;
342                     }
343                 
344                     items.sort((a, b) => {
345                             return ((string)(a.label)).collate((string)(b.label));
346                     });
347                 
348                 }
349                 
350                  
351                 
352                 public GLib.Object? get_item (uint pos)
353                 {
354                         return (Object) this.items.get((int) pos);
355                 }
356                 public GLib.Type  get_item_type ()
357                 {
358                         return typeof(GtkSource.CompletionProposal);
359                 }
360                 public   uint get_n_items () 
361                 {
362                         return this.items.size;
363                 }
364                 public bool can_filter (string word) 
365                 {
366                         if (word == null || word[0] == 0) {
367                                 return false;
368                         }
369  
370                         if (word.length < this.minimum_word_size) {
371                                 return false;
372                         }
373
374                         /* If the new word starts with our initial word, then we can simply
375                          * refilter outside this model using a GtkFilterListModel.
376                          */
377                          
378                          return word.has_prefix(this.search); 
379                 }
380                 public void  cancel ()
381                 {
382                         if (this.cancellable != null) {
383                                 this.cancellable.cancel();
384                         }
385                 }
386
387
388                 
389         }
390         public class CompletionProposal : Object, GtkSource.CompletionProposal 
391         {
392                 
393                 public string label { get; set; default = ""; }
394                 
395                 public string text  { get; set; default = ""; }
396                 public string info  { get; set; default = ""; }
397                 
398                 public Lsp.CompletionItem ci;
399                 
400                 public CompletionProposal(Lsp.CompletionItem ci) //string label, string text, string info)
401                 {
402                         
403                         this.ci = ci;
404                         this.text = ci.detail == null ? "" : ci.detail ;
405                         this.label = ci.label;
406                         this.info = ci.documentation == null ? "": ci.documentation.value;
407                         //GLib.debug("SET: detail =%s, label = %s; info =%s", ci.detail, ci.label, "to long..");
408                 }
409                 
410         }
411
412
413