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