Fix #8047 - debugging language server and fix completion handling
[roobuilder] / src / Palete / CompletionProvider.vala
index f71cb43..5f5e527 100644 (file)
@@ -1,18 +1,24 @@
  
-using Gtk;
+//using Gtk;
 
 // not sure why - but extending Gtk.SourceCompletionProvider seems to give an error..
 namespace Palete {
 
     public class CompletionProvider : Object, GtkSource.CompletionProvider
     {
+               public JsRender.JsRender file {
+                       get { return this.editor.file; }
+                       private set {}
+               }
                public Editor editor; 
                //public WindowState windowstate;
                public CompletionModel model;
+               global::Gtk.StringFilter? filter = null;
 
                public CompletionProvider(Editor editor)
                {
                    this.editor  = editor;
+                
                   // this.windowstate = null; // not ready until the UI is built.
                    
                }
@@ -27,19 +33,68 @@ namespace Palete {
                  return 200;
                }
                
+               
+               public bool is_trigger(global::Gtk.TextIter  iter, unichar ch)
+               {
+                       if (this.in_populate || ch == 32 || ch == 10) {
+                               return false;
+                       }
+                       if (this.editor.buffer.el.iter_has_context_class(iter, "comment") ||
+                               this.editor.buffer.el.iter_has_context_class(iter, "string")
+                       ) { 
+                               return false;
+                       }
+                       var back = iter.copy();
+                       back.backward_char();
+                       
+                       // what's the character at the iter?
+                       var str = back.get_text(iter);
+                       
+                       GLib.debug("Previos char to trigger is '%s;", str);
+                       
+                       
+                       return true;
+               }
+               
                public  void activate (GtkSource.CompletionContext context, GtkSource.CompletionProposal proposal)
                {
+                       GLib.debug("compelte activate");
+                       
                        var  p = (CompletionProposal) proposal;
-                       TextMark end_mark = null;
-                       TextIter begin, end;
+                       GLib.debug("lsp says use %s", p.ci.insertText);
+                       
+                       
+                       global::Gtk.TextMark end_mark = null;
+                       global::Gtk.TextIter begin, end;
 
                        if (!context.get_bounds(out begin, out end)) {
                                return;
                        }  
                        var buffer = begin.get_buffer();
                
-                       var  word = p.get_typed_text();
+                       var  word = p.label;
+                       if (p.ci.kind == Lsp.CompletionItemKind.Method || p.ci.kind == Lsp.CompletionItemKind.Function) {
+                               var bits = p.text.split("(");
+                               var abits = bits[1].split(")");
+                               var args = abits[0].split(",");
+                               
+                               word += "(";
+                               for(var i = 0 ; i < args.length; i++) {
+                                       word += i > 0 ? ", " : " ";
+                                       var wbit = args[i].strip().split(" ");
+                                       if (wbit.length < 2) {
+                                               word += wbit[0];
+                                               continue;
+                                       }
+                                       var ty = wbit[wbit.length - 2];
+                                       ty = ty.has_suffix("?") ? "?" : "";  
+                                       word += ty + wbit[wbit.length-1]; // property type..?
+                               }
+                               word += args.length > 0 ? " )" : ")";
+                       }
+                       
                        var len = -1;
+                       
 
                        /* 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
@@ -53,7 +108,9 @@ namespace Palete {
 
                                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;
@@ -80,70 +137,159 @@ namespace Palete {
 
                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;
+                       
+                       var p = (CompletionProposal) proposal;
                        switch(col) {
                                case GtkSource.CompletionColumn.TYPED_TEXT:
-                                       cell.set_icon_name("completion-snippet-symbolic");
+                                       cell.set_text(p.label);
                                        break;
                                case GtkSource.CompletionColumn.ICON:
-                                       cell.set_text(cell.text);
-                                       break;
+//cell.set_icon_name("lang-define-symbolic");return;
+//cell.set_icon_name("lang-include-symbolic");return;
+//cell.set_icon_name("lang-typedef-symbolic");return;
+//cell.set_icon_name("lang-union-symbolic");return;                     
+                                       switch (p.ci.kind) {
+                                       
+                                               case    Lsp.CompletionItemKind.Text: cell.set_icon_name("completion-snippet-symbolic");return;
+                                               case    Lsp.CompletionItemKind.Method: cell.set_icon_name("lang-method-symbolic");return;
+                                               case    Lsp.CompletionItemKind.Function: cell.set_icon_name("lang-function-symbolic");return;
+                                               case    Lsp.CompletionItemKind.Constructor: cell.set_icon_name("lang-method-symbolic");return;
+                                               case    Lsp.CompletionItemKind.Field: cell.set_icon_name("lang-struct-field-symbolic");return;
+                                               case    Lsp.CompletionItemKind.Variable: cell.set_icon_name("lang-variable-symbolic");return;
+                                               case    Lsp.CompletionItemKind.Class: cell.set_icon_name("lang-class-symbolic");return;
+                                               case    Lsp.CompletionItemKind.Interface: cell.set_icon_name("lang-class-symbolic");return;
+                                               case    Lsp.CompletionItemKind.Module: cell.set_icon_name("lang-namespace-symbolic");return;
+                                               case    Lsp.CompletionItemKind.Property:cell.set_icon_name("lang-struct-field-symbolic");return;
+                                               case    Lsp.CompletionItemKind.Unit: cell.set_icon_name("lang-variable-symbolic");return;
+                                               case    Lsp.CompletionItemKind.Value: cell.set_icon_name("lang-variable-symbolic");return;
+                                               case    Lsp.CompletionItemKind.Enum: cell.set_icon_name("lang-enum-symbolic");return;
+                                               case    Lsp.CompletionItemKind.Keyword: cell.set_icon_name("completion-word-symbolic");return;
+                                               case    Lsp.CompletionItemKind.Snippet: cell.set_icon_name("completion-snippet-symbolic");return;
+
+                                               case    Lsp.CompletionItemKind.Color: cell.set_icon_name("lang-typedef-symbolic");return;
+                                               case    Lsp.CompletionItemKind.File:cell.set_icon_name("lang-typedef-symbolic");return;
+                                               case    Lsp.CompletionItemKind.Reference: cell.set_icon_name("lang-typedef-symbolic");return;
+                                               case    Lsp.CompletionItemKind.Folder:cell.set_icon_name("lang-typedef-symbolic");return;
+                                               case    Lsp.CompletionItemKind.EnumMember: cell.set_icon_name("lang-typedef-symbolic");return;
+                                               case    Lsp.CompletionItemKind.Constant:cell.set_icon_name("lang-typedef-symbolic");return;
+                                               case    Lsp.CompletionItemKind.Struct: cell.set_icon_name("lang-struct-symbolic");return;
+                                               case    Lsp.CompletionItemKind.Event:cell.set_icon_name("lang-typedef-symbolic");return;
+                                               case    Lsp.CompletionItemKind.Operator:cell.set_icon_name("lang-typedef-symbolic");return;
+                                               case    Lsp.CompletionItemKind.TypeParameter:cell.set_icon_name("lang-typedef-symbolic");return;
+                                               default:
+
+
+
+                                       
+                                                       cell.set_icon_name("completion-snippet-symbolic");
+                                                       return;
+                                               }
+                                               
+                                       
                                case  GtkSource.CompletionColumn.COMMENT:
-                                       cell.set_text(cell.text);
+                                       cell.set_text(p.text);
                                        break;
                                case GtkSource.CompletionColumn.DETAILS:
-                                       cell.set_text(cell.text);
+                                       if (p.ci.documentation != null) {
+                                               cell.set_text(p.ci.documentation.value);
+                                               return;
+                                       }
+                               
+                                       cell.set_text(p.text);
                                        break;
                                default:
-                                       cell.set_text(cell.text);
+                                       cell.set_text(null);
                                        break;
                        }       
                }
 
-               public  async GLib.ListModel populate_async (GtkSource.CompletionContext context, GLib.Cancellable? cancelleble)
+               bool in_populate = false;
+        
+               internal  async GLib.ListModel populate_async (GtkSource.CompletionContext context, GLib.Cancellable? cancellable) 
                {
+                       GLib.debug("pupoulate async");
+                       var ret = new GLib.ListStore(typeof(CompletionProposal));
+                       
+                       if (this.in_populate) {
+                               GLib.debug("pupoulate async  - skipped waiting for reply");
+                               return ret;
+                       }
+                       this.in_populate = true;
+
+                       global::Gtk.TextIter begin, end;
+                       Lsp.CompletionList res;
+                       if (context.get_bounds (out begin, out end)) {
+                               var line = end.get_line();
+                               var offset =  end.get_line_offset();
+                               if (this.editor.prop != null) {
+                               //      tried line -1 (does not work)
+                                       GLib.debug("node pad = '%s' %d", this.editor.node.node_pad, this.editor.node.node_pad.length);
+                                       
+                                       line += this.editor.prop.start_line ; 
+                                       // this is based on Gtk using tabs (hence 1/2 chars);
+                                       offset += this.editor.node.node_pad.length;
+                                       // javascript listeners are indented 2 more spaces.
+                                       if (this.editor.prop.ptype == JsRender.NodePropType.LISTENER) {
+                                               offset += 2;
+                                       }
+                               } 
+                               //  this should not really be slow, as it's a quick repsonse
+                               yield this.file.getLanguageServer().document_change_force(this.file, this.editor.tempFileContents());                           
+                               try {
+                                       GLib.debug("sending request to language server %s", this.file.getLanguageServer().get_type().name());
+                                       
+                                       res = yield this.file.getLanguageServer().completion(this.file, line, offset, 1);
+                               } catch (GLib.Error e) {
+                                       GLib.debug("got error %s", e.message);
+                                       this.in_populate = false;
+                                       return ret;
+                               }
+                               
+                       } else {
+                               this.in_populate = false;
+                               return ret;
+                       }
+                       
+                       GLib.debug("pupoulate async  - got reply");
+                       this.model = new CompletionModel(this, context, res, cancellable); 
+                       var word = context.get_word();
+                       
+                       var lc = end.copy();
+                       lc.backward_char();
+                       var lchar = lc.get_text(end);
                        
-                       this.model = new CompletionModel(this, context, cancelleble); 
-                       return this.model;
+                       
+                       GLib.debug("Context word is %s / '%s' , %d", word, lchar, (int)word.length);
+                       if (word.length < 1 && lchar != ".") {
+                               word = " "; // this should filter out everything, and prevent it displaying 
+                       }
+                       
+                       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);
+                       this.in_populate = false;
+                       return filter_model; 
+                       
+                        
                        
                }
 
-               public  void refilter (GtkSource.CompletionContext context, GLib.ListModel in_model)
+               internal  void refilter (GtkSource.CompletionContext context, GLib.ListModel in_model)
                {
  
-                       //GtkFilterListModel *filter_model = NULL;
-                       //G//tkExpression *expression = NULL;
-                       //GtkStringFilter *filter = NULL;
-                       //GListModel *replaced_model = NULL;
-                       //char *word;
-
-                       var model = in_model;
+                       //GLib.debug("pupoulate refilter");
+                       if (this.filter == null) {
+                               return;
+                       }
 
                        var word = context.get_word();
-                       if (model is FilterListModel) { 
-                               model = model.get_model ();
-                       }
-
-                       if (!this.model.can_filter(word)) {
-                               this.model.cancel(); 
-                               var replaced_model = new CompletionModel(this, context, this.model.cancellable);
-                               context.set_proposals_for_provider(this, replaced_model);
-                               
-                               context.set_proposals_for_provider(this, replaced_model);
-                               return;
-                       }
-                        
-                       var expression = new PropertyExpression(typeof(CompletionProposal), null, "word");
-                       var filter = new StringFilter(expression);
-                       filter.set_search( word);
-                       var  filter_model = new FilterListModel(in_model, filter); 
-                       filter_model.set_incremental(true);
-                       context.set_proposals_for_provider(this, filter_model); 
+                       this.filter.set_search(word);
                 
-
                
                }
 
@@ -178,39 +324,36 @@ namespace Palete {
                Gee.ArrayList<CompletionProposal> items;
                string search;
                int minimum_word_size = 2;
-               public Cancellable cancellable;
                
-               public CompletionModel(CompletionProvider provider, GtkSource.CompletionContext context, Cancellable cancellable)
+               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>();
-                       this.search = context.get_word();
-                   if (this.search.length < this.minimum_word_size) {
-                           return;
-                   }
-                   var prov  =  this.provider;
-                       
-                       if (prov.editor.window.windowstate == null) {
-                               GLib.debug("Warning - provider windowstate not set?");
-                               return;
-                       }
-                   // now do our magic..
-                   this.items = prov.editor.window.windowstate.file.palete().suggestComplete(
-                           prov.editor.window.windowstate.file,
-                           prov.editor.node,
-                           prov.editor.prop,
-                           this.search
-                   ); 
-               
-                   print("GOT %d results\n", (int) items.size); 
+                       
+                       var word = context.get_word();
+                       GLib.debug("looking for %s", word);
+                       this.search = word;
+                       if (res != null) {
+                               foreach(var comp in res.items) {
+                                        if (comp.label == "_") { // skip '_'
+                                               continue;
+                                       }
+                                       GLib.debug("got suggestion %s", comp.label);
+                                       this.items.add(new CompletionProposal(comp));   
+                                       
+                               }
+                       }
+                   GLib.debug("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));
+                           return ((string)(a.label)).collate((string)(b.label));
                    });
                
                }
@@ -242,11 +385,14 @@ namespace Palete {
                        /* 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); 
                }
                public void  cancel ()
                {
-                       this.cancellable.cancel();
+                       if (this.cancellable != null) {
+                               this.cancellable.cancel();
+                       }
                }
 
 
@@ -255,15 +401,21 @@ namespace Palete {
        public class CompletionProposal : Object, GtkSource.CompletionProposal 
        {
                
-               string label;
+               public string label { get; set; default = ""; }
+               
+               public string text  { get; set; default = ""; }
+               public string info  { get; set; default = ""; }
+               
+               public Lsp.CompletionItem ci;
                
-               public string text;
-               string info;
-               public CompletionProposal(string label, string text, string info)
+               public CompletionProposal(Lsp.CompletionItem ci) //string label, string text, string info)
                {
-                       this.text = text;
-                       this.label = label;
-                       this.info = info;
+                       
+                       this.ci = ci;
+                       this.text = ci.detail == null ? "" : ci.detail ;
+                       this.label = ci.label;
+                       this.info = ci.documentation == null ? "": ci.documentation.value;
+                       //GLib.debug("SET: detail =%s, label = %s; info =%s", ci.detail, ci.label, "to long..");
                }
                
        }