4 // not sure why - but extending Gtk.SourceCompletionProvider seems to give an error..
7 public class CompletionProvider : Object, GtkSource.CompletionProvider
10 public JsRender.JsRender file {
11 get { return this.editor.file; }
15 //public WindowState windowstate;
16 public CompletionModel model;
17 global::Gtk.StringFilter filter;
19 public CompletionProvider(Editor editor)
23 // this.windowstate = null; // not ready until the UI is built.
27 public string get_name ()
29 return "roojsbuilder";
32 public int get_priority (GtkSource.CompletionContext context)
37 public void activate (GtkSource.CompletionContext context, GtkSource.CompletionProposal proposal)
39 GLib.debug("compelte activate");
41 var p = (CompletionProposal) proposal;
42 GLib.debug("lsp says use %s", p.ci.insertText);
45 global::Gtk.TextMark end_mark = null;
46 global::Gtk.TextIter begin, end;
48 if (!context.get_bounds(out begin, out end)) {
51 var buffer = begin.get_buffer();
54 if (p.ci.kind == Lsp.CompletionItemKind.Method || p.ci.kind == Lsp.CompletionItemKind.Function) {
55 var bits = p.text.split("(");
56 var abits = bits[1].split(")");
57 var args = abits[0].split(",");
60 for(var i = 0 ; i < args.length; i++) {
61 word += i > 0 ? ", " : " ";
62 var wbit = args[i].strip().split(" ");
63 var ty = wbit[wbit.length - 2];
64 ty = ty.has_suffix("?") ? "?" : "";
65 word += ty + wbit[wbit.length-1]; // property type..?
67 word += args.length > 0 ? " )" : ")";
73 /* If the insertion cursor is within a word and the trailing characters
74 * of the word match the suffix of the proposal, then limit how much
75 * text we insert so that the word is completed properly.
77 if (!end.ends_line() &&
78 !end.get_char().isspace() &&
83 if (word_end.forward_word_end ()) {
84 var text = end.get_slice(word_end);
85 if (text.length > word.length) {
88 if (word.has_suffix (text)) {
89 //g_assert (strlen (word) >= strlen (text));
90 len = word.length - text.length;
91 end_mark = buffer.create_mark (null, word_end, false);
96 buffer.begin_user_action();
97 buffer.delete (ref begin, ref end);
98 buffer.insert ( ref begin, word, len);
99 buffer.end_user_action ();
101 if (end_mark != null)
103 buffer.get_iter_at_mark(out end, end_mark);
104 buffer.select_range(end, end);
105 buffer.delete_mark(end_mark);
112 public void display (GtkSource.CompletionContext context, GtkSource.CompletionProposal proposal, GtkSource.CompletionCell cell)
114 //GLib.debug("compelte display");
115 var col = cell.get_column();
117 var p = (CompletionProposal) proposal;
119 case GtkSource.CompletionColumn.TYPED_TEXT:
120 cell.set_text(p.label);
122 case GtkSource.CompletionColumn.ICON:
123 //cell.set_icon_name("lang-define-symbolic");return;
124 //cell.set_icon_name("lang-include-symbolic");return;
125 //cell.set_icon_name("lang-typedef-symbolic");return;
126 //cell.set_icon_name("lang-union-symbolic");return;
129 case Lsp.CompletionItemKind.Text: cell.set_icon_name("completion-snippet-symbolic");return;
130 case Lsp.CompletionItemKind.Method: cell.set_icon_name("lang-method-symbolic");return;
131 case Lsp.CompletionItemKind.Function: cell.set_icon_name("lang-function-symbolic");return;
132 case Lsp.CompletionItemKind.Constructor: cell.set_icon_name("lang-method-symbolic");return;
133 case Lsp.CompletionItemKind.Field: cell.set_icon_name("lang-struct-field-symbolic");return;
134 case Lsp.CompletionItemKind.Variable: cell.set_icon_name("lang-variable-symbolic");return;
135 case Lsp.CompletionItemKind.Class: cell.set_icon_name("lang-class-symbolic");return;
136 case Lsp.CompletionItemKind.Interface: cell.set_icon_name("lang-class-symbolic");return;
137 case Lsp.CompletionItemKind.Module: cell.set_icon_name("lang-namespace-symbolic");return;
138 case Lsp.CompletionItemKind.Property:cell.set_icon_name("lang-struct-field-symbolic");return;
139 case Lsp.CompletionItemKind.Unit: cell.set_icon_name("lang-variable-symbolic");return;
140 case Lsp.CompletionItemKind.Value: cell.set_icon_name("lang-variable-symbolic");return;
141 case Lsp.CompletionItemKind.Enum: cell.set_icon_name("lang-enum-symbolic");return;
142 case Lsp.CompletionItemKind.Keyword: cell.set_icon_name("completion-word-symbolic");return;
143 case Lsp.CompletionItemKind.Snippet: cell.set_icon_name("completion-snippet-symbolic");return;
145 case Lsp.CompletionItemKind.Color: cell.set_icon_name("lang-typedef-symbolic");return;
146 case Lsp.CompletionItemKind.File:cell.set_icon_name("lang-typedef-symbolic");return;
147 case Lsp.CompletionItemKind.Reference: cell.set_icon_name("lang-typedef-symbolic");return;
148 case Lsp.CompletionItemKind.Folder:cell.set_icon_name("lang-typedef-symbolic");return;
149 case Lsp.CompletionItemKind.EnumMember: cell.set_icon_name("lang-typedef-symbolic");return;
150 case Lsp.CompletionItemKind.Constant:cell.set_icon_name("lang-typedef-symbolic");return;
151 case Lsp.CompletionItemKind.Struct: cell.set_icon_name("lang-struct-symbolic");return;
152 case Lsp.CompletionItemKind.Event:cell.set_icon_name("lang-typedef-symbolic");return;
153 case Lsp.CompletionItemKind.Operator:cell.set_icon_name("lang-typedef-symbolic");return;
154 case Lsp.CompletionItemKind.TypeParameter:cell.set_icon_name("lang-typedef-symbolic");return;
160 cell.set_icon_name("completion-snippet-symbolic");
165 case GtkSource.CompletionColumn.COMMENT:
166 cell.set_text(p.text);
168 case GtkSource.CompletionColumn.DETAILS:
169 if (p.ci.documentation != null) {
170 cell.set_text(p.ci.documentation.value);
174 cell.set_text(p.text);
182 bool in_populate = false;
184 internal async GLib.ListModel populate_async (GtkSource.CompletionContext context, GLib.Cancellable? cancellable)
186 GLib.debug("pupoulate async");
187 /*if (!this.in_populate) {
188 GLib.debug("pupoulate async - skipped waiting for reply");
191 this.in_populate = true;
193 global::Gtk.TextIter begin, end;
194 Lsp.CompletionList res;
195 if (context.get_bounds (out begin, out end)) {
196 var line = end.get_line();
197 var offset = end.get_line_offset();
198 if (this.editor.prop != null) {
199 // tried line -1 (does not work)
201 line += this.editor.prop.start_line ;
202 // this is based on Gtk using tabs (hence 1/2 chars);
203 offset += this.editor.file.file_namespace == "" ? 1 : 2;
206 this.file.getLanguageServer().document_change_real(this.file, this.editor.tempFileContents());
208 yield this.file.getLanguageServer().completion(this.file, line, offset, 1, out res);
209 } catch (GLib.Error e) {
210 GLib.debug("got error %s", e.message);
218 GLib.debug("pupoulate async - got reply");
219 this.model = new CompletionModel(this, context, res, cancellable);
220 var word = context.get_word();
223 var expression = new global::Gtk.PropertyExpression(typeof(CompletionProposal), null, "label");
224 this.filter = new global::Gtk.StringFilter(expression);
225 this.filter.set_search( word);
226 var filter_model = new global::Gtk.FilterListModel(this.model, this.filter);
227 filter.match_mode = global::Gtk.StringFilterMatchMode.PREFIX;
228 filter_model.set_incremental(true);
229 this.in_populate = false;
236 internal void refilter (GtkSource.CompletionContext context, GLib.ListModel in_model)
239 //GLib.debug("pupoulate refilter");
242 var word = context.get_word();
243 this.filter.set_search(word);
250 public bool activate_proposal (GtkSource.CompletionProposal proposal, TextIter iter)
253 istart.backward_find_char(is_space, null);
254 istart.forward_char();
256 // var search = iter.get_text(istart);
258 var buffer = iter.get_buffer();
259 buffer.delete(ref istart, ref iter);
260 buffer.insert(ref istart, proposal.get_text(), -1);
267 private bool is_space(unichar space){
268 return space.isspace() || space.to_string() == "";
273 public class CompletionModel : Object, GLib.ListModel
275 CompletionProvider provider;
276 Gee.ArrayList<CompletionProposal> items;
278 int minimum_word_size = 2;
280 public Cancellable? cancellable;
282 public CompletionModel(CompletionProvider provider, GtkSource.CompletionContext context, Lsp.CompletionList? res, Cancellable? cancellable)
284 this.provider = provider;
285 this.cancellable = cancellable;
286 this.items = new Gee.ArrayList<CompletionProposal>();
288 var word = context.get_word();
289 GLib.debug("looking for %s", word);
292 foreach(var comp in res.items) {
294 this.items.add(new CompletionProposal(comp));
298 print("GOT %d results\n", (int) items.size);
300 if (this.items.size < this.minimum_word_size) {
304 items.sort((a, b) => {
305 return ((string)(a.label)).collate((string)(b.label));
312 public GLib.Object? get_item (uint pos)
314 return (Object) this.items.get((int) pos);
316 public GLib.Type get_item_type ()
318 return typeof(GtkSource.CompletionProposal);
320 public uint get_n_items ()
322 return this.items.size;
324 public bool can_filter (string word)
326 if (word == null || word[0] == 0) {
330 if (word.length < this.minimum_word_size) {
334 /* If the new word starts with our initial word, then we can simply
335 * refilter outside this model using a GtkFilterListModel.
338 return word.has_prefix(this.search);
340 public void cancel ()
342 if (this.cancellable != null) {
343 this.cancellable.cancel();
350 public class CompletionProposal : Object, GtkSource.CompletionProposal
353 public string label { get; set; default = ""; }
355 public string text { get; set; default = ""; }
356 public string info { get; set; default = ""; }
358 public Lsp.CompletionItem ci;
360 public CompletionProposal(Lsp.CompletionItem ci) //string label, string text, string info)
364 this.text = ci.detail == null ? "" : ci.detail ;
365 this.label = ci.label;
366 this.info = ci.documentation == null ? "": ci.documentation.value;
367 //GLib.debug("SET: detail =%s, label = %s; info =%s", ci.detail, ci.label, "to long..");