1 /* Bluefish HTML Editor
2 * bftextview2_autocomp.c
3 *
4 * Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015 Olivier Sessink
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include <string.h>
21 #include "bluefish.h"
22 #include "bftextview2_private.h"
23 #include "bftextview2_scanner.h"
24 #include "bftextview2_identifier.h"
25 #include "bftextview2_autocomp.h"
26 #include "stringlist.h"
27
28 /*#undef DBG_AUTOCOMP
29 #define DBG_AUTOCOMP g_print*/
30
31 typedef struct {
32 BluefishTextView *btv;
33 gchar *prefix;
34 gchar *newprefix;
35 GtkWidget *win;
36 GtkListStore *store;
37 GtkTreeView *tree;
38 GtkWidget *scroll;
39 GtkWidget *reflabel;
40 GtkCellRenderer *cell;
41 GtkTreeViewColumn *column;
42 gint listwidth;
43 gint w;
44 gint h;
45 gboolean in_fill; /* TRUE while filling the liststore */
46 guint16 contextnum;
47 } Tacwin;
48
49 #define ACWIN(p) ((Tacwin *)(p))
50
51 static void
acwin_cleanup(BluefishTextView * btv)52 acwin_cleanup(BluefishTextView * btv)
53 {
54 if (btv->autocomp) {
55 g_free(ACWIN(btv->autocomp)->prefix);
56 gtk_widget_destroy(ACWIN(btv->autocomp)->win);
57 g_slice_free(Tacwin, btv->autocomp);
58 btv->autocomp = NULL;
59 }
60 }
61
62 static gboolean
acwin_move_selection(BluefishTextView * btv,gint keyval)63 acwin_move_selection(BluefishTextView * btv, gint keyval)
64 {
65 GtkTreeSelection *selection;
66 GtkTreeIter it;
67 GtkTreeModel *model;
68 GtkTreePath *path;
69
70 selection = gtk_tree_view_get_selection(ACWIN(btv->autocomp)->tree);
71 if (gtk_tree_selection_get_selected(selection, &model, &it)) {
72 gboolean retval = TRUE;
73 gint i, rows = 12, *indices = NULL;
74 path = gtk_tree_model_get_path(model, &it);
75 indices = gtk_tree_path_get_indices(path);
76 switch (keyval) {
77 case GDK_Up: /* move the selection one up */
78 retval = gtk_tree_path_prev(path);
79 break;
80 case GDK_Down:
81 gtk_tree_path_next(path);
82 break;
83 case GDK_Page_Down:
84 i = MIN(gtk_tree_model_iter_n_children(model, NULL) - 1, indices[0] + rows);
85 gtk_tree_path_free(path);
86 path = gtk_tree_path_new_from_indices(i, -1);
87 break;
88 case GDK_Page_Up:
89 i = MAX(indices[0] - rows, 0);
90 gtk_tree_path_free(path);
91 path = gtk_tree_path_new_from_indices(i, -1);
92 break;
93 case GDK_Home:
94 gtk_tree_path_free(path);
95 path = gtk_tree_path_new_first();
96 break;
97 case GDK_End:
98 gtk_tree_path_free(path);
99 i = gtk_tree_model_iter_n_children(model, NULL);
100 path = gtk_tree_path_new_from_indices(i - 1, -1);
101 break;
102 default:
103 return FALSE;
104 break;
105 }
106 if (gtk_tree_model_get_iter(model, &it, path)) {
107 gtk_tree_selection_select_iter(selection, &it);
108 gtk_tree_view_scroll_to_cell(ACWIN(btv->autocomp)->tree, path, NULL, FALSE, 0, 0);
109 } else
110 retval = FALSE;
111 gtk_tree_path_free(path);
112 return retval;
113 } else {
114 /* set selection */
115
116 }
117 return FALSE;
118 }
119
120 static gchar *
string_maintain_indenting(BluefishTextView * btv,gchar * string,gint * backward_cursor)121 string_maintain_indenting(BluefishTextView * btv, gchar *string, gint *backward_cursor)
122 {
123 gchar *indentstring;
124 gint i=0, len;
125 GtkTextIter iter;
126 GString *gstr;
127 /*g_print("string_maintain_indenting, check %s for newlines\n",string);*/
128 /* check if there are newlines in the string*/
129 if (!string || string[0]=='\0' || !strchr(string, '\n')) {
130 return string;
131 }
132 gtk_text_buffer_get_iter_at_mark(btv->buffer, &iter, gtk_text_buffer_get_insert(btv->buffer));
133 indentstring = get_line_indenting(btv->buffer, &iter, FALSE);
134 /*g_print("indentstring='%s' with len %d\n",indentstring,strlen(indentstring));*/
135 if (!indentstring || indentstring[0]=='\0') {
136 return string;
137 }
138 /* now do the replace of \n with \n+indentstring */
139 len = strlen(string);
140 gstr = g_string_new("");
141 while(string[i] != '\0') {
142 g_string_append_c(gstr, string[i]);
143 if (string[i] == '\n') {
144 g_string_append(gstr, indentstring);
145 if (i>=(len-*backward_cursor)) {
146 *backward_cursor += strlen(indentstring);
147 }
148 }
149 i++;
150 }
151 g_free(string);
152 string = gstr->str;
153 g_string_free(gstr, FALSE);
154 return string;
155 }
156
157 /* returns the number of bytes
158 that are already present in the text.
159
160 this is used to avoid for example inserting "return;" in a location where ';' is already the
161 character directly beyond the insert position.
162 */
163 static gint
get_existing_end_len(BluefishTextView * btv,const gchar * string,gint prefix_bytelen)164 get_existing_end_len(BluefishTextView * btv, const gchar *string, gint prefix_bytelen)
165 {
166 gchar *tmp;
167 GtkTextIter it1, it2;
168 gint i,len;
169 gint string_len = g_utf8_strlen(string, -1);
170
171 gtk_text_buffer_get_iter_at_mark(btv->buffer, &it1, gtk_text_buffer_get_insert(btv->buffer));
172 it2 = it1;
173 DBG_AUTOCOMP("get_existing_end_len, forward %d chars\n",string_len - prefix_bytelen);
174 gtk_text_iter_forward_chars(&it2,string_len - prefix_bytelen);
175 DBG_AUTOCOMP("get the text %d:%d\n",gtk_text_iter_get_offset(&it1),gtk_text_iter_get_offset(&it2));
176 tmp = gtk_text_buffer_get_text(btv->buffer, &it1, &it2, TRUE);
177 /*g_print("got tmp='%s'\n",tmp);*/
178 len = strlen(tmp);
179 i = len-1;
180 do {
181 DBG_AUTOCOMP("get_existing_end_len, compare %d characters of %s and %s\n",i,string+prefix_bytelen+len-i,tmp);
182 if (strncmp(string+prefix_bytelen+len-i, tmp, i)==0) {
183 DBG_AUTOCOMP("get_existing_end_len, found %d existing characters\n",i);
184 g_free(tmp);
185 return i;
186 }
187 i--;
188 } while(i>0);
189 g_free(tmp);
190 DBG_AUTOCOMP("get_existing_end_len, found no existing characters\n");
191 return 0;
192 }
193
trigger_new_autocomp_idle(gpointer data)194 static gboolean trigger_new_autocomp_idle(gpointer data) {
195 DBG_AUTOCOMP("trigger_new_autocomp_idle\n");
196 autocomp_run(BLUEFISH_TEXT_VIEW(data), FALSE);
197 return FALSE;
198 }
199
200 gboolean
acwin_check_keypress(BluefishTextView * btv,GdkEventKey * event)201 acwin_check_keypress(BluefishTextView * btv, GdkEventKey * event)
202 {
203 DBG_AUTOCOMP("got keyval %c\n", event->keyval);
204 if ((event->state & GDK_CONTROL_MASK) || (event->state & GDK_MOD1_MASK)) {
205 DBG_AUTOCOMP("a modifier is active (state=%d), don't handle the keypress\n", event->state);
206 return FALSE;
207 }
208 switch (event->keyval) {
209 case GDK_Return:{
210 GtkTreeSelection *selection;
211 GtkTreeIter it;
212 GtkTreeModel *model;
213 BluefishTextView *master = BLUEFISH_TEXT_VIEW(btv->master);
214 selection = gtk_tree_view_get_selection(ACWIN(btv->autocomp)->tree);
215 if (selection && gtk_tree_selection_get_selected(selection, &model, &it)) {
216 gchar *string;
217 gint stringlen;
218 gint existing_len, prefix_len;
219 guint pattern_id;
220 gint backup_chars = 0;
221 gboolean trigger_new_autocomp_popup=FALSE;
222 gtk_tree_model_get(model, &it, 1, &string, -1);
223 /*g_print("context %d has patternhash %p, string=%s\n",ACWIN(btv->autocomp)->contextnum, g_array_index(btv->bflang->st->contexts, Tcontext, ACWIN(btv->autocomp)->contextnum).patternhash, string); */
224 if (g_array_index
225 (master->bflang->st->contexts, Tcontext, ACWIN(btv->autocomp)->contextnum).patternhash) {
226 /*g_print("looking in context %d patternhash for '%s'\n",ACWIN(btv->autocomp)->contextnum, string); */
227 pattern_id =
228 GPOINTER_TO_INT(g_hash_table_lookup
229 (g_array_index
230 (master->bflang->st->contexts, Tcontext,
231 ACWIN(btv->autocomp)->contextnum).patternhash, string));
232 DBG_AUTOCOMP("got pattern_id=%d\n", pattern_id);
233 if (pattern_id) {
234 GSList *tmpslist =
235 g_array_index(master->bflang->st->matches, Tpattern, pattern_id).autocomp_items;
236 /* a pattern MAY have multiple autocomplete items. This code is not efficient if in the future some
237 patterns would have many autocomplete items. I don't expect this, so I leave this as it is right now */
238 while (tmpslist) {
239 Tpattern_autocomplete *pac = tmpslist->data;
240 if (g_strcmp0(string, pac->autocomplete_string) == 0) {
241 backup_chars = pac->autocomplete_backup_cursor;
242 trigger_new_autocomp_popup = (pac->trigger_new_autocomp_popup==1);
243 }
244 tmpslist = g_slist_next(tmpslist);
245 }
246 }
247 }
248 stringlen = strlen(string);
249 prefix_len = strlen(ACWIN(btv->autocomp)->prefix);
250 existing_len = get_existing_end_len(btv, string, prefix_len);
251
252 DBG_AUTOCOMP("acwin_check_keypress: ENTER: insert %s\n",
253 string + prefix_len);
254 string[stringlen - existing_len] = '\0';
255 /* see if there are any \n characters in the autocompletion string, and add the current indenting to them */
256 string = string_maintain_indenting(btv, string, &backup_chars);
257 gtk_text_buffer_insert_at_cursor(btv->buffer, string + prefix_len, -1);
258 if (backup_chars != 0) {
259 GtkTextIter iter;
260 gtk_text_buffer_get_iter_at_mark(btv->buffer, &iter, gtk_text_buffer_get_insert(btv->buffer));
261 if (backup_chars-existing_len > 0 && gtk_text_iter_backward_chars(&iter, backup_chars-existing_len)) {
262 DBG_AUTOCOMP("move cursor %d chars back!\n", backup_chars);
263 gtk_text_buffer_place_cursor(btv->buffer, &iter);
264 }
265 }
266 if (trigger_new_autocomp_popup) {
267 g_idle_add(trigger_new_autocomp_idle, btv);
268 }
269 g_free(string);
270 }
271 acwin_cleanup(btv);
272 return TRUE;
273 }
274 break;
275 case GDK_Tab:
276 if (ACWIN(btv->autocomp)->newprefix
277 && strlen(ACWIN(btv->autocomp)->newprefix) > strlen(ACWIN(btv->autocomp)->prefix)) {
278 gtk_text_buffer_insert_at_cursor(gtk_text_view_get_buffer(GTK_TEXT_VIEW(btv)),
279 ACWIN(btv->autocomp)->newprefix +
280 strlen(ACWIN(btv->autocomp)->prefix), -1);
281 }
282 /* now we have to re-position the autocomplete window */
283 autocomp_run(btv, FALSE);
284 return TRUE;
285 break;
286 case GDK_Right:
287 case GDK_KP_Right:
288 case GDK_Left:
289 case GDK_KP_Left:
290 acwin_cleanup(btv);
291 return FALSE;
292 break;
293 case GDK_Escape:
294 acwin_cleanup(btv);
295 return TRUE;
296 break;
297 case GDK_Up:
298 case GDK_Down:
299 case GDK_Page_Down:
300 case GDK_Page_Up:
301 case GDK_Home:
302 case GDK_End:
303 if (acwin_move_selection(btv, event->keyval))
304 return TRUE;
305 break;
306 }
307
308 return FALSE;
309 }
310
311 static void
acw_selection_changed_lcb(GtkTreeSelection * selection,Tacwin * acw)312 acw_selection_changed_lcb(GtkTreeSelection * selection, Tacwin * acw)
313 {
314 GtkTreeModel *model;
315 GtkTreeIter iter;
316 BluefishTextView *master = BLUEFISH_TEXT_VIEW(acw->btv->master);
317 DBG_AUTOCOMP("acw_selection_changed_lcb, in_fill=%d\n",acw->in_fill);
318 if (acw->in_fill)
319 return;
320
321 if (!g_array_index(master->bflang->st->contexts, Tcontext, acw->contextnum).patternhash
322 || !main_v->props.show_autocomp_reference)
323 return;
324
325 if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
326 gchar *key;
327 gtk_tree_model_get(model, &iter, 1, &key, -1);
328 if (key) {
329 DBG_AUTOCOMP("lookup reference for %s\n",key);
330 gint pattern_id =
331 GPOINTER_TO_INT(g_hash_table_lookup
332 (g_array_index
333 (master->bflang->st->contexts, Tcontext, acw->contextnum).patternhash, key));
334 DBG_AUTOCOMP("got pattern_id %d for key %s\n",pattern_id, key);
335 g_free(key);
336 if (pattern_id && g_array_index(master->bflang->st->matches, Tpattern, pattern_id).reference) {
337 GtkRequisition requisition;
338 DBG_AUTOCOMP("acw_selection_changed_lcb, show %s\n",
339 g_array_index(master->bflang->st->matches, Tpattern, pattern_id).reference);
340 gtk_label_set_markup(GTK_LABEL(acw->reflabel),
341 g_array_index(master->bflang->st->matches, Tpattern,
342 pattern_id).reference);
343 gtk_widget_show(acw->reflabel);
344 #if GTK_CHECK_VERSION(3,0,0)
345 gtk_widget_get_preferred_size(acw->reflabel, &requisition, NULL);
346 #else
347 gtk_widget_size_request(acw->reflabel, &requisition);
348 #endif
349 /*gtk_window_get_size(GTK_WINDOW(acw->win),&width,&height); */
350 acw->w = acw->listwidth + requisition.width + 2;
351 gtk_widget_set_size_request(acw->win, acw->w, (acw->h<350)?350:-1);
352 return;
353 }
354 }
355 }
356 gtk_widget_hide(acw->reflabel);
357 acw->w = acw->listwidth;
358 gtk_widget_set_size_request(acw->win, acw->listwidth, -1);
359 }
360
361 static Tacwin *
acwin_create(BluefishTextView * btv)362 acwin_create(BluefishTextView * btv)
363 {
364 /*GtkCellRenderer *cell;*/
365 GtkWidget *hbox;
366 /* GtkWidget *vbar; */
367 Tacwin *acw;
368 GtkTreeSelection *selection;
369
370 acw = g_slice_new0(Tacwin);
371 acw->btv = btv;
372 acw->in_fill = TRUE;
373 acw->win = gtk_window_new(GTK_WINDOW_POPUP);
374 /* We do not do any customized drawing in autocomplete window, as far as I can see, so probably we do not need to switch this on.
375 * On MacOSX this causes the labels of acw->reflabel to pile up on each other, see report by Keith Gunthardt on Facebook:
376 * https://www.facebook.com/photo.php?fbid=10152794950839751&set=p.10152794950839751&type=1
377
378 I (Olivier) think you are right Andrius, we can remove this call for all platforms, uncommenting it:
379 gtk_widget_set_app_paintable(acw->win, TRUE);
380 */
381 gtk_window_set_resizable(GTK_WINDOW(acw->win), FALSE);
382 gtk_container_set_border_width(GTK_CONTAINER(acw->win), 1);
383 gtk_window_set_decorated(GTK_WINDOW(acw->win), FALSE);
384
385 /* setting the type hint makes wayland *not* create a subsurface (I learned that on IRC), and we need a
386 subsurface, otherwise the popup can't be moved. we also need to make the window transient for the parent
387 for the wayland backend to create a subsurface */
388 /*gtk_window_set_type_hint(GTK_WINDOW(acw->win), GDK_WINDOW_TYPE_HINT_POPUP_MENU);*/
389 gtk_window_set_transient_for(GTK_WINDOW(acw->win), GTK_WINDOW(BFWIN(DOCUMENT(acw->btv->doc)->bfwin)->main_window));
390
391 acw->store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
392 acw->tree = GTK_TREE_VIEW(gtk_tree_view_new_with_model(GTK_TREE_MODEL(acw->store)));
393 g_object_unref(acw->store);
394
395 gtk_tree_view_set_headers_visible(acw->tree, FALSE);
396 acw->scroll = gtk_scrolled_window_new(NULL, NULL);
397 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(acw->scroll), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
398 acw->cell = gtk_cell_renderer_text_new();
399 acw->column = gtk_tree_view_column_new_with_attributes("", acw->cell, "markup", 0, NULL);
400 gtk_tree_view_append_column(GTK_TREE_VIEW(acw->tree), acw->column);
401 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(acw->tree), FALSE);
402 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(acw->tree));
403 g_signal_connect(G_OBJECT(selection), "changed", G_CALLBACK(acw_selection_changed_lcb), acw);
404
405 /* gtk_tree_view_set_search_column(GTK_TREE_VIEW(acw->tree),1);
406 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(acw->tree),acwin_tree_search_lcb,prefix,NULL);*/
407
408 /*g_signal_connect_swapped(GTK_WINDOW(acw->win),"expose-event",G_CALLBACK(ac_paint),acw->win); */
409 /*gtk_window_set_position (GTK_WINDOW(acw->win), GTK_WIN_POS_MOUSE); */
410 gtk_container_add(GTK_CONTAINER(acw->scroll), GTK_WIDGET(acw->tree));
411
412 hbox = gtk_hbox_new(FALSE, 0);
413 gtk_box_pack_start(GTK_BOX(hbox), acw->scroll, FALSE, TRUE, 0);
414 acw->reflabel = gtk_label_new(NULL);
415 gtk_label_set_line_wrap(GTK_LABEL(acw->reflabel), TRUE);
416 gtk_misc_set_alignment(GTK_MISC(acw->reflabel), 0.1, 0.1);
417 gtk_box_pack_start(GTK_BOX(hbox), acw->reflabel, TRUE, TRUE, 0);
418 gtk_container_add(GTK_CONTAINER(acw->win), hbox);
419 /*gtk_widget_set_size_request(acw->reflabel,150,-1); */
420 gtk_widget_show_all(acw->scroll);
421 gtk_widget_show(hbox);
422 /*gtk_widget_set_size_request(GTK_WIDGET(acw->tree),100,200); */
423 /*gtk_widget_set_size_request(acw->win, 150, 200); */
424 /*g_signal_connect(G_OBJECT(acw->win),"key-release-event",G_CALLBACK(acwin_key_release_lcb),acw); */
425
426 return acw;
427 }
428
429 /* returns TRUE if window is popped-up lower than the cursor,
430 returns FALSE if window is popped-up higher than the cursor (because cursor is low in the screen) */
431 static gboolean
acwin_position_at_cursor(BluefishTextView * btv)432 acwin_position_at_cursor(BluefishTextView * btv)
433 {
434 GtkTextIter it;
435 GdkRectangle rect;
436 GdkScreen *screen;
437 gint x, y, sh;
438 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(btv));
439 screen = gtk_widget_get_screen(GTK_WIDGET(btv));
440
441 gtk_text_buffer_get_iter_at_mark(buffer, &it, gtk_text_buffer_get_insert(buffer));
442 gtk_text_view_get_iter_location(GTK_TEXT_VIEW(btv), &it, &rect);
443 gtk_text_view_buffer_to_window_coords(GTK_TEXT_VIEW(btv), GTK_TEXT_WINDOW_TEXT, rect.x, rect.y, &rect.x,
444 &rect.y);
445 gdk_window_get_origin(gtk_text_view_get_window(GTK_TEXT_VIEW(btv), GTK_TEXT_WINDOW_TEXT), &x, &y);
446
447 sh = gdk_screen_get_height(screen);
448 if (rect.y + y + ACWIN(btv->autocomp)->h > sh) {
449 DBG_AUTOCOMP("acwin_position_at_cursor, popup above cursuor, rect.y+y=%d + rect.height(%d)-acw->h(%d)=%d, acw->h=%d, sh=%d\n"
450 , rect.y + y,rect.height,ACWIN(btv->autocomp)->h
451 , rect.y + y + rect.height - ACWIN(btv->autocomp)->h
452 , ACWIN(btv->autocomp)->h, sh);
453 gtk_window_move(GTK_WINDOW(ACWIN(btv->autocomp)->win), rect.x + x,
454 rect.y + y + rect.height - ACWIN(btv->autocomp)->h);
455 return FALSE;
456 } else {
457 DBG_AUTOCOMP("acwin_position_at_cursor, popup below cursuor, rect.y+y=%d, acw->h=%d, sh=%d\n", rect.y + y, ACWIN(btv->autocomp)->h, sh);
458 gtk_window_move(GTK_WINDOW(ACWIN(btv->autocomp)->win), rect.x + x, rect.y + y);
459 return TRUE;
460 }
461 }
462
463 static void
acwin_calculate_window_size(Tacwin * acw,GList * items,GList * items2,const gchar * closetag,gint * numitems)464 acwin_calculate_window_size(Tacwin * acw, GList * items, GList * items2, const gchar *closetag, gint *numitems)
465 {
466 GList *tmplist;
467 gboolean runtwo = FALSE;
468 gchar *longest = NULL, *tmp;
469 guint longestlen = 1;
470 *numitems = 0;
471 DBG_AUTOCOMP("acwin_calculate_window_size, items=%p, items2=%p, closetag=%s\n", items, items2, closetag);
472 if (closetag) {
473 longest = g_markup_escape_text(closetag, -1);
474 longestlen = strlen(longest);
475 }
476 tmplist = g_list_first(items);
477 while (!runtwo || tmplist) {
478 guint len;
479 if (!tmplist) {
480 tmplist = g_list_first(items2);
481 runtwo = TRUE;
482 if (!tmplist)
483 break;
484 }
485 g_assert(tmplist != NULL);
486 g_assert(tmplist->data != NULL);
487 /* DBG_AUTOCOMP("acwin_calculate_window_size, tmplist=%p", tmplist);
488 DBG_AUTOCOMP(", tmplist->data=%p",tmplist->data);
489 DBG_AUTOCOMP("=%s\n", (gchar *)tmplist->data);*/
490 tmp = g_markup_escape_text(tmplist->data, -1);
491 len = strlen(tmp);
492 if (len > longestlen) {
493 longest = tmp;
494 longestlen = len;
495 } else {
496 g_free(tmp);
497 }
498 (*numitems)++;
499 tmplist = g_list_next(tmplist);
500 }
501 if (longest) {
502 gint len, rowh,min_h,nat_h;
503 PangoLayout *panlay = gtk_widget_create_pango_layout(GTK_WIDGET(acw->tree), NULL);
504 pango_layout_set_markup(panlay, longest, -1);
505 pango_layout_get_pixel_size(panlay, &len, &rowh);
506 DBG_AUTOCOMP("longest=%s which has len=%d and rowheight=%d\n", longest, len, rowh);
507 /* rowh+9 has been found by trial and error on a particular gtk theme and resolution, so
508 there is quite a chance that this will not be optimal for other themes or for example
509 on a high resolution display. I've tried to request this size but I cannot find it.
510 I've now changed it to nat_h+5, hopefully nat_h is already a more dynamic value */
511 /*gint spacing,height;
512 GtkRequisition min_size, nat_size;
513 gtk_cell_renderer_get_preferred_height(acw->cell,GTK_WIDGET(acw->tree),&min_h,&nat_h);
514 spacing= gtk_tree_view_column_get_spacing(acw->column);
515 gtk_tree_view_column_cell_get_size(acw->column,NULL,NULL,NULL,NULL,&height);
516 gtk_widget_get_preferred_size(GTK_WIDGET(acw->tree),&min_size,&nat_size);
517 g_print("row=%d,rowh+9=%d,min_h=%d,nat_h=%d,spacing=%d,height=%d,nat_size.h=%d\n",rowh,rowh+9,min_h,nat_h,spacing,height,min_size.height);*/
518 #if GTK_CHECK_VERSION(3,0,0)
519 gtk_cell_renderer_get_preferred_height(acw->cell,GTK_WIDGET(acw->tree),&min_h,&nat_h);
520 #else
521 nat_h=rowh+2;
522 #endif
523 acw->h = MIN((*numitems) * (nat_h+5), 350); /*MIN(MAX((*numitems + 1) * rowh + 8, 150), 350);*/
524 acw->w = acw->listwidth = MIN(len + 20, 350);
525 DBG_AUTOCOMP("acwin_calculate_window_size, numitems=%d, rowh=%d, new height=%d, new width=%d\n", *numitems, rowh, acw->h,
526 acw->listwidth);
527 gtk_widget_set_size_request(GTK_WIDGET(acw->scroll), acw->listwidth, acw->h); /* ac_window */
528 g_free(longest);
529 g_object_unref(G_OBJECT(panlay));
530 }
531 }
532
533 static int
ac_sort_func(const gchar * a,const gchar * b)534 ac_sort_func(const gchar *a, const gchar *b)
535 {
536 if (a && b && a[0] == '<' && b[0] == '<' && a[1] != b[1]) {
537 /* make sure that </ is sorted below <[a-z] */
538 if (a[1] == '/') return 1;
539 if (b[1] == '/') return -1;
540 return a[1]-b[1];
541 }
542 return g_strcmp0(a,b);
543 }
544
545 /* fills the tree */
546 static void
acwin_fill_tree(Tacwin * acw,GList * items,GList * items2,gchar * closetag,gboolean reverse,gint numitems)547 acwin_fill_tree(Tacwin * acw, GList * items, GList * items2, gchar * closetag, gboolean reverse, gint numitems)
548 {
549 GList *tmplist, *list = NULL;
550 gint everynth=1, i=0;
551
552 /* to avoid that we show 10000 items in the autocomplete list, we insert every N items where N % everynth == 0 */
553 if (numitems > 512) {
554 everynth = 1.0 * numitems / 512.0;
555 DBG_AUTOCOMP("numitems=%d, everynth=%d\n", numitems, everynth);
556 }
557 if (items)
558 list = g_list_copy(items);
559 if (items2)
560 list = g_list_concat(g_list_copy(items2), list);
561
562 list = g_list_sort(list, (GCompareFunc) ac_sort_func);
563 if (closetag) {
564 GList *tlist2;
565 tlist2 = find_in_stringlist(list, closetag);
566 if (tlist2) {
567 list = g_list_remove_link(list, tlist2);
568 list = g_list_concat(tlist2, list);
569 } else {
570 list = g_list_prepend(list, closetag);
571 }
572 }
573 if (reverse) {
574 list = g_list_reverse(list);
575 DBG_AUTOCOMP("reverse list!\n");
576 }
577 tmplist = g_list_first(list);
578 while (tmplist) {
579 if (i % everynth == 0) {
580 GtkTreeIter it;
581 gchar *tmp;
582 gtk_list_store_append(acw->store, &it);
583 tmp = g_markup_escape_text(tmplist->data, -1);
584 gtk_list_store_set(acw->store, &it, 0, tmp, 1, tmplist->data, -1);
585 /*DBG_AUTOCOMP("acwin_fill_tree, add item %s\n",tmp);*/
586 g_free(tmp);
587 }
588 i++;
589 tmplist = g_list_next(tmplist);
590 }
591 g_list_free(list);
592 }
593
594 /* static void print_ac_items(GCompletion *gc) {
595 GList *tmplist = g_list_first(gc->items);
596 DBG_AUTOCOMP("autocompletion has %d items:",g_list_length(g_list_first(gc->items)));
597 while (tmplist) {
598 DBG_AUTOCOMP(" %s",(char *)tmplist->data);
599 tmplist = g_list_next(tmplist);
600 }
601 DBG_AUTOCOMP("\n");
602 } */
603
604 void
autocomp_stop(BluefishTextView * btv)605 autocomp_stop(BluefishTextView * btv)
606 {
607 acwin_cleanup(btv);
608 }
609
610 static GList *
process_conditional_items(BluefishTextView * btv,Tfound * found,gint contextnum,GList * items)611 process_conditional_items(BluefishTextView * btv, Tfound *found, gint contextnum, GList *items)
612 {
613 BluefishTextView *master = BLUEFISH_TEXT_VIEW(btv->master);
614 GList *retval=NULL, *tmplist=items;
615
616 while (tmplist) {
617 gboolean valid=TRUE;
618 guint pattern_id;
619 const gchar *string = tmplist->data;
620 GHashTable *hasht = g_array_index(master->bflang->st->contexts, Tcontext,contextnum).patternhash;
621 DBG_AUTOCOMP("process_conditional_items, looking up %s in hashtable %p\n",string, hasht);
622 pattern_id = GPOINTER_TO_INT(g_hash_table_lookup(hasht, string));
623 DBG_AUTOCOMP("process_conditional_items, got pattern_id=%d\n", pattern_id);
624 if (pattern_id && found) {
625 /* found may be NULL, for examplein an empty document, in that case anything is valid */
626 GSList *tmpslist = g_array_index(master->bflang->st->matches, Tpattern, pattern_id).autocomp_items;
627 /* a pattern MAY have multiple autocomplete items. This code is not efficient if in the future some
628 patterns would have many autocomplete items. I don't expect this, so I leave this as it is right now */
629 while (tmpslist) {
630 Tpattern_autocomplete *pac = tmpslist->data;
631 if (g_strcmp0(string, pac->autocomplete_string) == 0 && pac->condition!=0) {
632 Tpattern_condition *pcond = &g_array_index(master->bflang->st->conditions, Tpattern_condition, pac->condition);
633
634 valid = test_condition(found->fcontext, found->fblock, pcond);
635 if (!valid) {
636 DBG_AUTOCOMP("process_conditional_items, item %s is NOT VALID for autocompletion\n",string);
637 }
638 }
639 tmpslist = g_slist_next(tmpslist);
640 }
641 }
642 if (valid) {
643 retval = g_list_prepend(retval, string);
644 }
645
646 tmplist = tmplist->next;
647 }
648 return retval;
649 }
650
651 void
autocomp_run(BluefishTextView * btv,gboolean user_requested)652 autocomp_run(BluefishTextView * btv, gboolean user_requested)
653 {
654 GtkTextIter cursorpos, iter;
655 BluefishTextView *master = BLUEFISH_TEXT_VIEW(btv->master);
656 gint contextnum;
657 gunichar uc;
658
659 Tfound *found=NULL;
660 Tfoundblock *fblock = NULL; /* needed for the special case to close generix xml tags
661 based on the top of the blockstack, or to match conditional
662 autocompletion strings */
663
664 if (G_UNLIKELY(!master->bflang || !master->bflang->st))
665 return;
666
667 gtk_text_buffer_get_iter_at_mark(btv->buffer, &cursorpos, gtk_text_buffer_get_insert(btv->buffer));
668
669 iter = cursorpos;
670 gtk_text_iter_set_line_offset(&iter, 0);
671
672
673 scan_for_autocomp_prefix(master, &iter, &cursorpos, &contextnum);
674 DBG_AUTOCOMP("autocomp_run, got possible match start at %d in context %d, cursor is at %d\n",
675 gtk_text_iter_get_offset(&iter), contextnum, gtk_text_iter_get_offset(&cursorpos));
676 /* see if character at cursor is end or symbol */
677 uc = gtk_text_iter_get_char(&cursorpos);
678 if (G_UNLIKELY(uc > NUMSCANCHARS))
679 return;
680
681 /*identstate = g_array_index(master->bflang->st->contexts, Tcontext, contextnum).identstate;*/
682 if (!character_is_symbol(master->bflang->st,contextnum,uc)) {
683 /* current character is not a symbol! */
684 DBG_AUTOCOMP("autocomp_run, character at cursor %d '%c' is not a symbol, return\n", uc, (char) uc);
685 acwin_cleanup(btv);
686 return;
687 }
688
689 /* see if we have enough characters */
690 if (!user_requested && gtk_text_iter_get_offset(&cursorpos) - gtk_text_iter_get_offset(&iter) < main_v->props.autocomp_min_prefix_len) {
691 DBG_AUTOCOMP("autocomp_run, prefix len %d < autocomp_min_prefix_len (%d), abort!\n"
692 , gtk_text_iter_get_offset(&cursorpos) - gtk_text_iter_get_offset(&iter)
693 , main_v->props.autocomp_min_prefix_len);
694 acwin_cleanup(btv);
695 return;
696 }
697
698 if (g_array_index(master->bflang->st->contexts, Tcontext, contextnum).has_tagclose_from_blockstack) {
699 found = get_foundcache_at_offset(btv, gtk_text_iter_get_offset(&cursorpos), NULL);
700 if (found) {
701 fblock =
702 found->numblockchange < 0 ? pop_blocks(found->numblockchange, found->fblock) : found->fblock;
703 if (fblock && fblock->start2_o != BF_OFFSET_UNDEFINED) {
704 DBG_AUTOCOMP("abort offering closing tag: block has an end already\n");
705 fblock = NULL;
706 }
707 /* if (g_array_index(btv->bflang->st->matches, Tpattern, fblock->patternum).tagclose_from_blockstack) {
708 gchar *start;
709 gtk_text_buffer_get_iter_at_mark(buffer, &it1, fblock->start1);
710 gtk_text_buffer_get_iter_at_mark(buffer, &it2, fblock->end1);
711 gtk_text_iter_forward_char(&it1);
712 start = gtk_text_buffer_get_text(buffer,&it1,&it2,TRUE);
713 g_print("close tag %s\n",start);
714 g_free(start);
715 }*/
716 }
717 }
718 if ((user_requested || !gtk_text_iter_equal(&iter, &cursorpos))
719 && (g_array_index(master->bflang->st->contexts, Tcontext, contextnum).ac != NULL
720 || (fblock
721 && g_array_index(master->bflang->st->matches, Tpattern,
722 fblock->patternum).tagclose_from_blockstack)
723 )
724 ) {
725 /* we have a prefix or it is user requested, and we have a context with autocompletion or we have blockstack-tag-auto-closing */
726 gchar *newprefix = NULL, *prefix, *closetag = NULL;
727 GList *items = NULL, *items2 = NULL;
728 gboolean free_items=FALSE;
729 /*print_ac_items(g_array_index(btv->bflang->st->contexts,Tcontext, contextnum).ac); */
730
731 prefix = gtk_text_buffer_get_text(btv->buffer, &iter, &cursorpos, TRUE);
732
733 if (fblock) {
734 GString *tmpstr;
735 gint plen;
736 GtkTextIter it1;
737 gtk_text_buffer_get_iter_at_offset(btv->buffer, &it1, fblock->start1_o);
738 tmpstr = g_string_new("</");
739 while(gtk_text_iter_forward_char(&it1)) {
740 gunichar uc = gtk_text_iter_get_char(&it1);
741 if (!g_unichar_isalnum(uc) && uc != '_') {
742 break;
743 }
744 g_string_append_c(tmpstr, uc);
745 }
746 g_string_append_c(tmpstr, '>');
747 closetag = g_string_free(tmpstr, FALSE);
748 DBG_AUTOCOMP("closetag=%s, prefix=%s\n", closetag, prefix);
749 plen = strlen(prefix);
750 if (plen == strlen(closetag) || strncmp(closetag, prefix, plen) != 0) {
751 g_free(closetag);
752 closetag = NULL;
753 }
754 }
755 if (g_array_index(master->bflang->st->contexts, Tcontext, contextnum).ac) {
756 items =
757 g_completion_complete(g_array_index(master->bflang->st->contexts, Tcontext, contextnum).ac,
758 prefix, &newprefix);
759 DBG_AUTOCOMP("got %d autocompletion items for prefix %s in context %d, newprefix=%s\n",
760 g_list_length(items), prefix, contextnum, newprefix);
761 if (G_UNLIKELY(g_array_index(master->bflang->st->contexts, Tcontext, contextnum).autocomplete_has_conditions)) {
762 if (found==NULL) {
763 found = get_foundcache_at_offset(btv, gtk_text_iter_get_offset(&cursorpos), NULL);
764 }
765 items = process_conditional_items(btv, found, contextnum, items);
766 free_items=TRUE;
767 }
768 {
769 GCompletion *compl = identifier_ac_get_completion(master, contextnum, FALSE);
770 DBG_IDENTIFIER("got completion %p for context %d\n", compl, contextnum);
771 if (compl) {
772 gchar *newprefix2 = NULL;
773 items2 = g_completion_complete(compl, prefix, &newprefix2);
774 DBG_IDENTIFIER("got %d identifier_items for prefix %s, newprefix=%s\n", g_list_length(items2), prefix, newprefix2);
775 if (!newprefix)
776 newprefix = newprefix2;
777 else
778 g_free(newprefix2);
779 }
780 }
781 }
782 if (closetag || items2
783 || (items != NULL && (items->next != NULL || strcmp(items->data, prefix) != 0))) {
784 /* do not popup if there are 0 items, and also not if there is 1 item which equals the prefix */
785 GtkTreeSelection *selection;
786 GtkTreeIter it;
787 gboolean below;
788 gint numitems=0;
789 /* create the GUI */
790 if (!btv->autocomp) {
791 btv->autocomp = acwin_create(btv);
792 } else {
793 ACWIN(btv->autocomp)->in_fill=TRUE;
794 g_free(ACWIN(btv->autocomp)->prefix);
795 g_free(ACWIN(btv->autocomp)->newprefix);
796 ACWIN(btv->autocomp)->prefix = NULL;
797 ACWIN(btv->autocomp)->newprefix = NULL;
798 gtk_list_store_clear(ACWIN(btv->autocomp)->store);
799 }
800 ACWIN(btv->autocomp)->contextnum = contextnum;
801 ACWIN(btv->autocomp)->prefix = g_strdup(prefix);
802 if (newprefix) {
803 ACWIN(btv->autocomp)->newprefix = g_strdup(newprefix);
804 }
805 acwin_calculate_window_size(ACWIN(btv->autocomp), items, items2, closetag, &numitems);
806 below = acwin_position_at_cursor(btv);
807 acwin_fill_tree(ACWIN(btv->autocomp), items, items2, closetag, !below, numitems);
808 gtk_widget_show(ACWIN(btv->autocomp)->win);
809 selection = gtk_tree_view_get_selection(ACWIN(btv->autocomp)->tree);
810 if (below) {
811 DBG_AUTOCOMP("autocomp_run, popup-below, get first iter for selection\n");
812 gtk_tree_model_get_iter_first(GTK_TREE_MODEL(ACWIN(btv->autocomp)->store), &it);
813 } else {
814 GtkTreePath *path;
815 DBG_AUTOCOMP("autocomp_run, popup-above, select last iter and scroll max down\n");
816 gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(ACWIN(btv->autocomp)->store), &it, NULL,
817 gtk_tree_model_iter_n_children(GTK_TREE_MODEL
818 (ACWIN(btv->autocomp)->store),
819 NULL) - 1);
820 path = gtk_tree_model_get_path(GTK_TREE_MODEL(ACWIN(btv->autocomp)->store), &it);
821 gtk_tree_view_scroll_to_cell(ACWIN(btv->autocomp)->tree, path, NULL, FALSE, 1.0, 1.0);
822 gtk_tree_path_free(path);
823 }
824 /* this forces that selection changed will be called two lines below*/
825 gtk_tree_selection_unselect_all(selection);
826 ACWIN(btv->autocomp)->in_fill=FALSE;
827 DBG_AUTOCOMP("call select_iter on autocomp window\n");
828 gtk_tree_selection_select_iter(selection, &it);
829 g_free(closetag);
830 } else {
831 acwin_cleanup(btv);
832 }
833 if (free_items) {
834 g_list_free(items);
835 }
836 g_free(newprefix);
837 g_free(prefix);
838 } else {
839 DBG_AUTOCOMP("no autocompletion data for context %d (ac=%p), or no prefix\n", contextnum,
840 g_array_index(master->bflang->st->contexts, Tcontext, contextnum).ac);
841 acwin_cleanup(btv);
842 }
843 }
844