4 // not sure why - but extending Gtk.SourceCompletionProvider seems to give an error..
7 public class CompletionProvider : Object, GtkSource.CompletionProvider
9 public JsRender.JsRender file {
10 get { return this.editor.file; }
14 //public WindowState windowstate;
15 public CompletionModel model;
16 global::Gtk.StringFilter? filter = null;
18 public CompletionProvider(Editor editor)
22 // this.windowstate = null; // not ready until the UI is built.
26 public string get_name ()
28 return "roojsbuilder";
31 public int get_priority (GtkSource.CompletionContext context)
37 public bool is_trigger(global::Gtk.TextIter iter, unichar ch)
39 if (this.in_populate || ch == 32 || ch == 10) {
42 if (this.editor.buffer.el.iter_has_context_class(iter, "comment") ||
43 this.editor.buffer.el.iter_has_context_class(iter, "string")
47 var back = iter.copy();
50 // what's the character at the iter?
51 var str = back.get_text(iter);
53 GLib.debug("Previos char to trigger is '%s;", str);
59 public void activate (GtkSource.CompletionContext context, GtkSource.CompletionProposal proposal)
61 GLib.debug("compelte activate");
63 var p = (CompletionProposal) proposal;
64 GLib.debug("lsp says use %s", p.ci.insertText);
67 global::Gtk.TextMark end_mark = null;
68 global::Gtk.TextIter begin, end;
70 if (!context.get_bounds(out begin, out end)) {
73 var buffer = begin.get_buffer();
76 if (p.ci.kind == Lsp.CompletionItemKind.Method || p.ci.kind == Lsp.CompletionItemKind.Function) {
77 var bits = p.text.split("(");
78 var abits = bits[1].split(")");
79 var args = abits[0].split(",");
82 for(var i = 0 ; i < args.length; i++) {
83 word += i > 0 ? ", " : " ";
84 var wbit = args[i].strip().split(" ");
85 if (wbit.length < 2) {
89 var ty = wbit[wbit.length - 2];
90 ty = ty.has_suffix("?") ? "?" : "";
91 word += ty + wbit[wbit.length-1]; // property type..?
93 word += args.length > 0 ? " )" : ")";
99 /* If the insertion cursor is within a word and the trailing characters
100 * of the word match the suffix of the proposal, then limit how much
101 * text we insert so that the word is completed properly.
103 if (!end.ends_line() &&
104 !end.get_char().isspace() &&
109 if (word_end.forward_word_end ()) {
110 var text = end.get_slice(word_end);
111 if (text.length > word.length) {
114 if (word.has_suffix (text)) {
115 //g_assert (strlen (word) >= strlen (text));
116 len = word.length - text.length;
117 end_mark = buffer.create_mark (null, word_end, false);
122 buffer.begin_user_action();
123 buffer.delete (ref begin, ref end);
124 buffer.insert ( ref begin, word, len);
125 buffer.end_user_action ();
127 if (end_mark != null)
129 buffer.get_iter_at_mark(out end, end_mark);
130 buffer.select_range(end, end);
131 buffer.delete_mark(end_mark);
138 public void display (GtkSource.CompletionContext context, GtkSource.CompletionProposal proposal, GtkSource.CompletionCell cell)
140 //GLib.debug("compelte display");
141 var col = cell.get_column();
143 var p = (CompletionProposal) proposal;
145 case GtkSource.CompletionColumn.TYPED_TEXT:
146 cell.set_text(p.label);
148 case GtkSource.CompletionColumn.ICON:
149 //cell.set_icon_name("lang-define-symbolic");return;
150 //cell.set_icon_name("lang-include-symbolic");return;
151 //cell.set_icon_name("lang-typedef-symbolic");return;
152 //cell.set_icon_name("lang-union-symbolic");return;
155 case Lsp.CompletionItemKind.Text: cell.set_icon_name("completion-snippet-symbolic");return;
156 case Lsp.CompletionItemKind.Method: cell.set_icon_name("lang-method-symbolic");return;
157 case Lsp.CompletionItemKind.Function: cell.set_icon_name("lang-function-symbolic");return;
158 case Lsp.CompletionItemKind.Constructor: cell.set_icon_name("lang-method-symbolic");return;
159 case Lsp.CompletionItemKind.Field: cell.set_icon_name("lang-struct-field-symbolic");return;
160 case Lsp.CompletionItemKind.Variable: cell.set_icon_name("lang-variable-symbolic");return;
161 case Lsp.CompletionItemKind.Class: cell.set_icon_name("lang-class-symbolic");return;
162 case Lsp.CompletionItemKind.Interface: cell.set_icon_name("lang-class-symbolic");return;
163 case Lsp.CompletionItemKind.Module: cell.set_icon_name("lang-namespace-symbolic");return;
164 case Lsp.CompletionItemKind.Property:cell.set_icon_name("lang-struct-field-symbolic");return;
165 case Lsp.CompletionItemKind.Unit: cell.set_icon_name("lang-variable-symbolic");return;
166 case Lsp.CompletionItemKind.Value: cell.set_icon_name("lang-variable-symbolic");return;
167 case Lsp.CompletionItemKind.Enum: cell.set_icon_name("lang-enum-symbolic");return;
168 case Lsp.CompletionItemKind.Keyword: cell.set_icon_name("completion-word-symbolic");return;
169 case Lsp.CompletionItemKind.Snippet: cell.set_icon_name("completion-snippet-symbolic");return;
171 case Lsp.CompletionItemKind.Color: cell.set_icon_name("lang-typedef-symbolic");return;
172 case Lsp.CompletionItemKind.File:cell.set_icon_name("lang-typedef-symbolic");return;
173 case Lsp.CompletionItemKind.Reference: cell.set_icon_name("lang-typedef-symbolic");return;
174 case Lsp.CompletionItemKind.Folder:cell.set_icon_name("lang-typedef-symbolic");return;
175 case Lsp.CompletionItemKind.EnumMember: cell.set_icon_name("lang-typedef-symbolic");return;
176 case Lsp.CompletionItemKind.Constant:cell.set_icon_name("lang-typedef-symbolic");return;
177 case Lsp.CompletionItemKind.Struct: cell.set_icon_name("lang-struct-symbolic");return;
178 case Lsp.CompletionItemKind.Event:cell.set_icon_name("lang-typedef-symbolic");return;
179 case Lsp.CompletionItemKind.Operator:cell.set_icon_name("lang-typedef-symbolic");return;
180 case Lsp.CompletionItemKind.TypeParameter:cell.set_icon_name("lang-typedef-symbolic");return;
186 cell.set_icon_name("completion-snippet-symbolic");
191 case GtkSource.CompletionColumn.COMMENT:
192 cell.set_text(p.text);
194 case GtkSource.CompletionColumn.DETAILS:
195 if (p.ci.documentation != null) {
196 cell.set_text(p.ci.documentation.value);
200 cell.set_text(p.text);
208 bool in_populate = false;
210 internal async GLib.ListModel populate_async (GtkSource.CompletionContext context, GLib.Cancellable? cancellable)
212 GLib.debug("pupoulate async");
213 var ret = new GLib.ListStore(typeof(CompletionProposal));
215 if (this.in_populate) {
216 GLib.debug("pupoulate async - skipped waiting for reply");
219 this.in_populate = true;
221 global::Gtk.TextIter begin, end;
222 Lsp.CompletionList res;
223 if (context.get_bounds (out begin, out end)) {
224 var line = end.get_line();
225 var offset = end.get_line_offset();
226 if (this.editor.prop != null) {
227 // tried line -1 (does not work)
228 GLib.debug("node pad = '%s' %d", this.editor.node.node_pad, this.editor.node.node_pad.length);
230 line += this.editor.prop.start_line ;
231 // this is based on Gtk using tabs (hence 1/2 chars);
232 offset += this.editor.node.node_pad.length;
233 // javascript listeners are indented 2 more spaces.
234 if (this.editor.prop.ptype == JsRender.NodePropType.LISTENER) {
238 // this should not really be slow, as it's a quick repsonse
239 yield this.file.getLanguageServer().document_change_force(this.file, this.editor.tempFileContents());
241 GLib.debug("sending request to language server %s", this.file.getLanguageServer().get_type().name());
243 res = yield this.file.getLanguageServer().completion(this.file, line, offset, 1);
244 } catch (GLib.Error e) {
245 GLib.debug("got error %s", e.message);
246 this.in_populate = false;
251 this.in_populate = false;
255 GLib.debug("pupoulate async - got reply");
256 this.model = new CompletionModel(this, context, res, cancellable);
257 var word = context.get_word();
261 var lchar = lc.get_text(end);
264 GLib.debug("Context word is %s / '%s' , %d", word, lchar, (int)word.length);
265 if (word.length < 1 && lchar != ".") {
266 word = " "; // this should filter out everything, and prevent it displaying
269 var expression = new global::Gtk.PropertyExpression(typeof(CompletionProposal), null, "label");
270 this.filter = new global::Gtk.StringFilter(expression);
271 this.filter.set_search( word);
272 var filter_model = new global::Gtk.FilterListModel(this.model, this.filter);
273 filter.match_mode = global::Gtk.StringFilterMatchMode.PREFIX;
274 filter_model.set_incremental(true);
275 this.in_populate = false;
282 internal void refilter (GtkSource.CompletionContext context, GLib.ListModel in_model)
285 //GLib.debug("pupoulate refilter");
286 if (this.filter == null) {
290 var word = context.get_word();
291 this.filter.set_search(word);
298 public bool activate_proposal (GtkSource.CompletionProposal proposal, TextIter iter)
301 istart.backward_find_char(is_space, null);
302 istart.forward_char();
304 // var search = iter.get_text(istart);
306 var buffer = iter.get_buffer();
307 buffer.delete(ref istart, ref iter);
308 buffer.insert(ref istart, proposal.get_text(), -1);
315 private bool is_space(unichar space){
316 return space.isspace() || space.to_string() == "";
321 public class CompletionModel : Object, GLib.ListModel
323 CompletionProvider provider;
324 Gee.ArrayList<CompletionProposal> items;
326 int minimum_word_size = 2;
328 public Cancellable? cancellable;
330 public CompletionModel(CompletionProvider provider, GtkSource.CompletionContext context, Lsp.CompletionList? res, Cancellable? cancellable)
332 this.provider = provider;
333 this.cancellable = cancellable;
334 this.items = new Gee.ArrayList<CompletionProposal>();
336 var word = context.get_word();
337 GLib.debug("looking for %s", word);
340 foreach(var comp in res.items) {
341 if (comp.label == "_") { // skip '_'
344 GLib.debug("got suggestion %s", comp.label);
345 this.items.add(new CompletionProposal(comp));
349 GLib.debug("GOT %d results\n", (int) items.size);
351 if (this.items.size < this.minimum_word_size) {
355 items.sort((a, b) => {
356 return ((string)(a.label)).collate((string)(b.label));
363 public GLib.Object? get_item (uint pos)
365 return (Object) this.items.get((int) pos);
367 public GLib.Type get_item_type ()
369 return typeof(GtkSource.CompletionProposal);
371 public uint get_n_items ()
373 return this.items.size;
375 public bool can_filter (string word)
377 if (word == null || word[0] == 0) {
381 if (word.length < this.minimum_word_size) {
385 /* If the new word starts with our initial word, then we can simply
386 * refilter outside this model using a GtkFilterListModel.
389 return word.has_prefix(this.search);
391 public void cancel ()
393 if (this.cancellable != null) {
394 this.cancellable.cancel();
401 public class CompletionProposal : Object, GtkSource.CompletionProposal
404 public string label { get; set; default = ""; }
406 public string text { get; set; default = ""; }
407 public string info { get; set; default = ""; }
409 public Lsp.CompletionItem ci;
411 public CompletionProposal(Lsp.CompletionItem ci) //string label, string text, string info)
415 this.text = ci.detail == null ? "" : ci.detail ;
416 this.label = ci.label;
417 this.info = ci.documentation == null ? "": ci.documentation.value;
418 //GLib.debug("SET: detail =%s, label = %s; info =%s", ci.detail, ci.label, "to long..");