-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.
}
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
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;
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);
-
}
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));
});
}
/* 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();
+ }
}
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..");
}
}