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