fix #7968 - language server support for syntax check and completion
[roobuilder] / src / Palete / CompletionProvider.vala
index 1f1da27..74cca1f 100644 (file)
@@ -1,19 +1,26 @@
  
-using Gtk;
+//using Gtk;
 
 // not sure why - but extending Gtk.SourceCompletionProvider seems to give an error..
 namespace Palete {
 
-    public class CompletionProvider : Object, SourceCompletionProvider
+    public class CompletionProvider : Object, GtkSource.CompletionProvider
     {
-               Editor editor; 
-               WindowState windowstate;
-               //public List<Gtk.SourceCompletionItem> filtered_proposals;
+
+               public JsRender.JsRender file {
+                       get { return this.editor.file; }
+                       private set {}
+               }
+               public Editor editor; 
+               //public WindowState windowstate;
+               public CompletionModel model;
+               global::Gtk.StringFilter filter;
 
                public CompletionProvider(Editor editor)
                {
                    this.editor  = editor;
-                   this.windowstate = null; // not ready until the UI is built.
+                
+                  // this.windowstate = null; // not ready until the UI is built.
                    
                }
 
@@ -22,85 +29,135 @@ namespace Palete {
                  return  "roojsbuilder";
                }
 
-               public int get_priority ()
+               public int get_priority (GtkSource.CompletionContext context)
                {
                  return 200;
                }
-
-               public bool match (SourceCompletionContext context)
-               {
-                       bool has_matches = false;
-                       this.fetchMatches(context, out has_matches);
-                       return has_matches;
-               }
-
-               public List<SourceCompletionItem>? fetchMatches(SourceCompletionContext context, out bool has_matches)
+               
+               public  void activate (GtkSource.CompletionContext context, GtkSource.CompletionProposal proposal)
                {
-                    has_matches = false;
+                       GLib.debug("compelte activate");
+                       var  p = (CompletionProposal) proposal;
+                       global::Gtk.TextMark end_mark = null;
+                       global::Gtk.TextIter begin, end;
 
-                   if (this.windowstate == null) {
-                           this.windowstate = this.editor.window.windowstate;
-                   }
+                       if (!context.get_bounds(out begin, out end)) {
+                               return;
+                       }  
+                       var buffer = begin.get_buffer();
                
-               
-                   var buffer = context.completion.view.buffer;
-                   var  mark = buffer.get_insert ();
-                   TextIter end;
+                       var  word = p.label;
+                       var len = -1;
+                       
 
-                   buffer.get_iter_at_mark (out end, mark);
-                   var endpos = end;
-               
-                   var searchpos = endpos;
-               
-                   searchpos.backward_find_char(is_space, null);
-                   searchpos.forward_char();
-                   var search = endpos.get_text(searchpos);
-                   print("got search %s\n", search);
-               
-                   if (search.length < 2) {
-                           return null;
-                   }
-                
-                   // now do our magic..
-                   var filtered_proposals = this.windowstate.file.palete().suggestComplete(
-                           this.windowstate.file,
-                           this.editor.node,
-                           this.editor.prop,
-                           search
-                   ); 
-               
-                   print("GOT %d results\n", (int) filtered_proposals.length()); 
-               
-                   if (filtered_proposals.length() < 2) {
-                       return null;
-                   }
+                       /* If the insertion cursor is within a word and the trailing characters
+                        * of the word match the suffix of the proposal, then limit how much
+                        * text we insert so that the word is completed properly.
+                        */
+                       if (!end.ends_line() &&
+                               !end.get_char().isspace() &&
+                               !end.ends_word ())
+                       {
+                               var word_end = end;
+
+                               if (word_end.forward_word_end ()) {
+                                       var text = end.get_slice(word_end);
+                                       if (text.length > word.length) {
+                                               return;
+                                       }
+                                       if (word.has_suffix (text)) {
+                                               //g_assert (strlen (word) >= strlen (text));
+                                               len = word.length - text.length;
+                                               end_mark = buffer.create_mark (null, word_end, false); 
+                                       }
+                               }
+                       }
+
+                       buffer.begin_user_action();
+                       buffer.delete (ref begin, ref end);
+                       buffer.insert ( ref begin, word, len);
+                       buffer.end_user_action ();
+
+                       if (end_mark != null)
+                       {
+                               buffer.get_iter_at_mark(out end, end_mark);
+                               buffer.select_range(end,  end);
+                               buffer.delete_mark(end_mark);
+                       }
                
-                   filtered_proposals.sort((a, b) => {
-                           return ((string)(a.text)).collate((string)(b.text));
-                   });
-                   has_matches = true;
-                   return filtered_proposals;
 
                }
-       
-               public void populate (SourceCompletionContext context)
+
+
+               public  void display (GtkSource.CompletionContext context, GtkSource.CompletionProposal proposal, GtkSource.CompletionCell cell)
+               {
+                       GLib.debug("compelte display");
+                       var col = cell.get_column();
+                       
+                       var p = (CompletionProposal) proposal;
+                       switch(col) {
+                               case GtkSource.CompletionColumn.TYPED_TEXT:
+                                       cell.set_text(p.label);
+                                       break;
+                               case GtkSource.CompletionColumn.ICON:
+                                       cell.set_icon_name("completion-snippet-symbolic");
+                                       break;
+                               case  GtkSource.CompletionColumn.COMMENT:
+                                       cell.set_text(p.info);
+                                       break;
+                               case GtkSource.CompletionColumn.DETAILS:
+                                       cell.set_text(p.text);
+                                       break;
+                               default:
+                                       cell.set_text(null);
+                                       break;
+                       }       
+               }
+
+               
+        
+               internal  async GLib.ListModel populate_async (GtkSource.CompletionContext context, GLib.Cancellable? cancellable)
                {
-                       bool has_matches = false;
-                       var filtered_proposals = this.fetchMatches(context, out has_matches);
-                       if (!has_matches) {
-                           context.add_proposals (this, null, true);
-                           return;
+                       GLib.debug("pupoulate async");
+
+                       global::Gtk.TextIter begin, end;
+                       Lsp.CompletionList res;
+                       if (context.get_bounds (out begin, out end)) {
+                               yield this.file.getLanguageServer().completion(this.file, end.get_line(), end.get_line_offset(), 1, out res);
+                       } else {
+                               res = null;
                        }
-                       // add proposals triggers a critical error in Gtk - try running gtksourceview/tests/test-completion.
-                       // see https://bugzilla.gnome.org/show_bug.cgi?id=758646
-                       var fe = GLib.Log.set_always_fatal(0); 
-                       context.add_proposals (this, filtered_proposals, true);
-                       GLib.Log.set_always_fatal(fe);
+                       this.model = new CompletionModel(this, context, res, cancellable); 
+                       var word = context.get_word();
+                       
+                       
+                       var expression = new global::Gtk.PropertyExpression(typeof(CompletionProposal), null, "label");
+                       this.filter = new global::Gtk.StringFilter(expression);
+                       this.filter.set_search( word);
+                       var  filter_model = new global::Gtk.FilterListModel(this.model, this.filter); 
+                       filter.match_mode = global::Gtk.StringFilterMatchMode.PREFIX;
+                       filter_model.set_incremental(true);
+                       return filter_model; 
+                       
+                        
+                       
                }
 
+               internal  void refilter (GtkSource.CompletionContext context, GLib.ListModel in_model)
+               {
+                       GLib.debug("pupoulate refilter");
+        
+
+                       var word = context.get_word();
+                       this.filter.set_search(word);
+                
+               
+               }
 
 
-               public bool activate_proposal (SourceCompletionProposal proposal, TextIter iter)
+/*
+               public bool activate_proposal (GtkSource.CompletionProposal proposal, TextIter iter)
                {
                        var istart = iter;
                        istart.backward_find_char(is_space, null);
@@ -114,51 +171,110 @@ namespace Palete {
                
                        return true;
                }
+  
+        
 
-               public SourceCompletionActivation get_activation ()
-               {
-                       //if(SettingsManager.Get_Setting("complete_auto") == "true"){
-                               return SourceCompletionActivation.INTERACTIVE | SourceCompletionActivation.USER_REQUESTED;
-                       //} else {
-                       //      return Gtk.SourceCompletionActivation.USER_REQUESTED;
-                       //}
+               private bool is_space(unichar space){
+                       return space.isspace() || space.to_string() == "";
                }
-
-               public int get_interactive_delay ()
+               */
+                
+       }
+       public class CompletionModel : Object, GLib.ListModel 
+       {
+               CompletionProvider provider;
+               Gee.ArrayList<CompletionProposal> items;
+               string search;
+               int minimum_word_size = 2;
+               
+               public Cancellable? cancellable;
+               
+               public CompletionModel(CompletionProvider provider, GtkSource.CompletionContext context, Lsp.CompletionList? res, Cancellable? cancellable)
+               {
+                       this.provider = provider;
+                       this.cancellable = cancellable;
+                       this.items = new Gee.ArrayList<CompletionProposal>();
+                       
+                       var word = context.get_word();
+                       GLib.debug("looking for %s", word);
+                       this.search = word;
+                       if (res != null) {
+                               foreach(var comp in res.items) {
+                                        
+                                       this.items.add(new CompletionProposal(comp));   
+                                       
+                               }
+                       }
+                   print("GOT %d results\n", (int) items.size); 
+                       // WHY TWICE?
+                   if (this.items.size < this.minimum_word_size) {
+                               return;
+                   }
+               
+                   items.sort((a, b) => {
+                           return ((string)(a.text)).collate((string)(b.text));
+                   });
+               
+               }
+               
+                
+               
+               public GLib.Object? get_item (uint pos)
+               {
+                       return (Object) this.items.get((int) pos);
+               }
+               public GLib.Type  get_item_type ()
                {
-                       return -1;
+                       return typeof(GtkSource.CompletionProposal);
                }
-
-               public bool get_start_iter (SourceCompletionContext context, SourceCompletionProposal proposal, out TextIter iter)
+               public   uint get_n_items () 
                {
-                       return false;
+                       return this.items.size;
                }
-
-               public void update_info (SourceCompletionProposal proposal, SourceCompletionInfo info)
+               public bool can_filter (string word) 
                {
+                       if (word == null || word[0] == 0) {
+                               return false;
+                       }
+                       if (word.length < this.minimum_word_size) {
+                               return false;
+                       }
 
+                       /* If the new word starts with our initial word, then we can simply
+                        * refilter outside this model using a GtkFilterListModel.
+                        */
+                        
+                        return word.has_prefix(this.search); 
                }
-
-               private bool is_space(unichar space){
-                       return space.isspace() || space.to_string() == "";
-               }
-               
-               private bool is_forward_space(unichar space){
-                       return !(
-                               space.to_string() == " "
-                               ||
-                               space.to_string() == ""
-                               ||
-                               space.to_string() == "\n"
-                               ||
-                               space.to_string() == ")"
-                               ||
-                               space.to_string() == "("
-                               
-                       );
+               public void  cancel ()
+               {
+                       if (this.cancellable != null) {
+                               this.cancellable.cancel();
+                       }
                }
-       }
 
 
+               
+       }
+       public class CompletionProposal : Object, GtkSource.CompletionProposal 
+       {
+               
+               public string label { get; set; default = ""; }
+               
+               public string text  { get; set; default = ""; }
+               public string info  { get; set; default = ""; }
+               public CompletionProposal(Lsp.CompletionItem ci) //string label, string text, string info)
+               {
+                       
+                       
+                       this.text = ci.detail == null ? "" : ci.detail ;
+                       this.label = ci.label;
+                       this.info = ci.documentation == null ? "": ci.documentation.value;
+                       GLib.debug("SET: text=%s, label = %s; info =%s", ci.detail, ci.label, "to long..");
+               }
+               
+       }
+
 }