1 /*
2 * gretl -- Gnu Regression, Econometrics and Time-series Library
3 * Copyright (C) 2001 Allin Cottrell and Riccardo "Jack" Lucchetti
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20 #include "gretl.h"
21 #include "genparse.h"
22 #include "completions.h"
23
24 #if GTKSOURCEVIEW_VERSION > 2
25 # define GTK_TYPE_SOURCE_COMPLETION_PROVIDER GTK_SOURCE_TYPE_COMPLETION_PROVIDER
26 #endif
27
28 #define AC_DEBUG 0
29
30 #include <gtksourceview/gtksourcecompletionprovider.h>
31 #include <gtksourceview/gtksourcecompletionitem.h>
32 #include <gtksourceview/completion-providers/words/gtksourcecompletionwords.h>
33
34 /* global, referenced in settings.c and elsewhere */
35 int hansl_completion;
36 int console_completion;
37
38 enum {
39 PROV_WORDS,
40 PROV_FUNCS,
41 PROV_CMDS,
42 PROV_SNIPPETS,
43 PROV_SERIES,
44 N_PROV
45 };
46
47 static const char *prov_names[] = {
48 "words", "functions", "commands", "snippets", "series"
49 };
50
51 typedef struct prov_info_ prov_info;
52
53 struct prov_info_ {
54 void *ptr; /* pointer to the completer struct */
55 GtkTextBuffer *buf; /* text buffer used for PROV_FUNCS, PROV_CMDS */
56 };
57
prov_info_new(void)58 static prov_info *prov_info_new (void)
59 {
60 prov_info *pi = malloc(N_PROV * sizeof *pi);
61 int i;
62
63 for (i=0; i<N_PROV; i++) {
64 pi[i].ptr = NULL;
65 pi[i].buf = NULL;
66 }
67
68 return pi;
69 }
70
prov_info_destroy(prov_info * pi)71 static void prov_info_destroy (prov_info *pi)
72 {
73 int i;
74
75 for (i=0; i<N_PROV; i++) {
76 if (pi[i].buf != NULL) {
77 g_object_unref(pi[i].buf);
78 }
79 }
80 free(pi);
81 }
82
destroy_words_providers(GtkWidget * w,gpointer p)83 static void destroy_words_providers (GtkWidget *w, gpointer p)
84 {
85 prov_info *pi = g_object_get_data(G_OBJECT(w), "prov_info");
86
87 if (pi != NULL) {
88 prov_info_destroy(pi);
89 g_object_set_data(G_OBJECT(w), "prov_info", NULL);
90 }
91 }
92
93 /* Create a GtkTextBuffer holding the names of built-in
94 gretl functions to serve as a completion provider.
95 */
96
function_names_buffer(void)97 static GtkTextBuffer *function_names_buffer (void)
98 {
99 GtkTextBuffer *tbuf;
100 GString *str;
101 gchar *fnames;
102 const char *s;
103 int i, nf;
104
105 nf = gen_func_count();
106 tbuf = gtk_text_buffer_new(NULL);
107 str = g_string_sized_new(nf * 8);
108
109 for (i=0; i<nf; i++) {
110 s = gen_func_name(i);
111 if (*s != '_') {
112 g_string_append(str, s);
113 g_string_append_c(str, ' ');
114 }
115 }
116
117 fnames = g_string_free(str, FALSE);
118 gtk_text_buffer_set_text(tbuf, fnames, -1);
119 g_free(fnames);
120
121 return tbuf;
122 }
123
124 /* Create a GtkTextBuffer holding the names of gretl
125 commands to serve as a completion provider.
126 */
127
command_names_buffer(void)128 static GtkTextBuffer *command_names_buffer (void)
129 {
130 GtkTextBuffer *tbuf;
131 GString *str;
132 gchar *cnames;
133 int i;
134
135 tbuf = gtk_text_buffer_new(NULL);
136 str = g_string_sized_new(NC * 8);
137
138 for (i=1; i<=NC; i++) {
139 g_string_append(str, gretl_command_word(i));
140 g_string_append_c(str, ' ');
141 }
142
143 cnames = g_string_free(str, FALSE);
144 gtk_text_buffer_set_text(tbuf, cnames, -1);
145 g_free(cnames);
146
147 return tbuf;
148 }
149
150 /* Apparatus for providing "snippets" which may consist of
151 several "words", or other gretl-specific material (at
152 present, just names of dataset series).
153 */
154
155 typedef struct _GretlProvider GretlProvider;
156 typedef struct _GretlProviderClass GretlProviderClass;
157
158 struct _GretlProvider {
159 GObject parent;
160 GList *proposals;
161 gint priority;
162 const gchar *name;
163 GdkPixbuf *icon;
164 GtkSourceCompletionActivation activation;
165 gint id;
166 };
167
168 struct _GretlProviderClass {
169 GObjectClass parent_class;
170 };
171
172 static void gretl_provider_iface_init (GtkSourceCompletionProviderIface *iface);
173 GType gretl_provider_get_type (void);
174
G_DEFINE_TYPE_WITH_CODE(GretlProvider,gretl_provider,G_TYPE_OBJECT,G_IMPLEMENT_INTERFACE (GTK_TYPE_SOURCE_COMPLETION_PROVIDER,gretl_provider_iface_init))175 G_DEFINE_TYPE_WITH_CODE (GretlProvider,
176 gretl_provider,
177 G_TYPE_OBJECT,
178 G_IMPLEMENT_INTERFACE(GTK_TYPE_SOURCE_COMPLETION_PROVIDER,
179 gretl_provider_iface_init))
180
181 static gchar *
182 gretl_provider_get_name (GtkSourceCompletionProvider *provider)
183 {
184 return g_strdup(((GretlProvider *) provider)->name);
185 }
186
187 static gint
gretl_provider_get_priority(GtkSourceCompletionProvider * provider)188 gretl_provider_get_priority (GtkSourceCompletionProvider *provider)
189 {
190 return ((GretlProvider *) provider)->priority;
191 }
192
193 static GtkSourceCompletionActivation
gretl_provider_get_activation(GtkSourceCompletionProvider * provider)194 gretl_provider_get_activation (GtkSourceCompletionProvider *provider)
195 {
196 return ((GretlProvider *) provider)->activation;
197 }
198
199 #define valid(c) (c == '_' || isalnum(c))
200
backward_word_start(GtkTextIter * iter)201 static gboolean backward_word_start (GtkTextIter *iter)
202 {
203 GtkTextIter prev = *iter;
204
205 while (TRUE) {
206 /* starting a line is OK */
207 if (gtk_text_iter_starts_line(&prev)) {
208 break;
209 }
210 gtk_text_iter_backward_char(&prev);
211 /* check if the previous character is a valid word character */
212 if (!valid(gtk_text_iter_get_char(&prev))) {
213 break;
214 }
215 *iter = prev;
216 }
217
218 if (!valid(gtk_text_iter_get_char(iter))) {
219 return FALSE;
220 }
221
222 return isalpha(gtk_text_iter_get_char(iter));
223 }
224
forward_word_end(GtkTextIter * iter)225 static gboolean forward_word_end (GtkTextIter *iter)
226 {
227 while (TRUE) {
228 /* ending a line is OK */
229 if (gtk_text_iter_ends_line(iter)) {
230 break;
231 }
232 /* check if the next character is a valid word character */
233 if (!valid(gtk_text_iter_get_char(iter))) {
234 break;
235 }
236 gtk_text_iter_forward_char(iter);
237 }
238
239 return TRUE;
240 }
241
get_word_at_iter(GtkTextIter * iter)242 static gchar *get_word_at_iter (GtkTextIter *iter)
243 {
244 GtkTextIter end = *iter;
245
246 if (!forward_word_end(iter) || !gtk_text_iter_equal(iter, &end)) {
247 return NULL;
248 } else if (!backward_word_start(iter)) {
249 return NULL;
250 } else if (gtk_text_iter_equal(iter, &end)) {
251 return NULL;
252 } else {
253 return gtk_text_iter_get_text(iter, &end);
254 }
255 }
256
proposal_get_cursor_offset(const gchar * s)257 static int proposal_get_cursor_offset (const gchar *s)
258 {
259 if (!strncmp(s, "if", 2)) {
260 return 3;
261 } else if (!strncmp(s, "loop", 4)) {
262 return 5;
263 } else if (!strncmp(s, "function", 8)) {
264 return 9;
265 } else if (!strncmp(s, "outfile", 7)) {
266 return 8;
267 } else if (!strncmp(s, "plot", 4)) {
268 return 5;
269 } else if (!strncmp(s, "mpi", 3)) {
270 return 5;
271 } else {
272 return 0;
273 }
274 }
275
276 /* Back up over the trigger for completion; insert the replacement
277 text; then back the cursor up to the point where the user will
278 first have to add something to the boilerplate.
279 */
280
281 static gboolean
snippet_activate_proposal(GtkSourceCompletionProvider * provider,GtkSourceCompletionProposal * proposal,GtkTextIter * iter)282 snippet_activate_proposal (GtkSourceCompletionProvider *provider,
283 GtkSourceCompletionProposal *proposal,
284 GtkTextIter *iter)
285 {
286 gchar *s = gtk_source_completion_proposal_get_text(proposal);
287 int n = proposal_get_cursor_offset(s);
288
289 if (n > 0) {
290 GtkTextBuffer *buf = gtk_text_iter_get_buffer(iter);
291 GtkTextIter start = *iter;
292
293 backward_word_start(&start);
294 gtk_text_buffer_delete(buf, &start, iter);
295 gtk_text_buffer_insert(buf, iter, s, -1);
296 gtk_text_iter_backward_chars(iter, strlen(s) - n);
297 gtk_text_buffer_place_cursor(buf, iter);
298 return TRUE;
299 } else {
300 return FALSE;
301 }
302 }
303
304 static gboolean
series_activate_proposal(GtkSourceCompletionProvider * provider,GtkSourceCompletionProposal * proposal,GtkTextIter * iter)305 series_activate_proposal (GtkSourceCompletionProvider *provider,
306 GtkSourceCompletionProposal *proposal,
307 GtkTextIter *iter)
308 {
309 /* just accept the gtksourceview default */
310 return FALSE;
311 }
312
313 static gboolean
gretl_activate_proposal(GtkSourceCompletionProvider * provider,GtkSourceCompletionProposal * proposal,GtkTextIter * iter)314 gretl_activate_proposal (GtkSourceCompletionProvider *provider,
315 GtkSourceCompletionProposal *proposal,
316 GtkTextIter *iter)
317 {
318 gint id = ((GretlProvider *) provider)->id;
319 gboolean ret = FALSE;
320
321 #if AC_DEBUG
322 fprintf(stderr, "HERE gretl_activate_proposal (%s)\n",
323 prov_names[id]);
324 #endif
325
326 if (id == PROV_SNIPPETS) {
327 ret = snippet_activate_proposal(provider, proposal, iter);
328 } else if (id == PROV_SERIES) {
329 ret = series_activate_proposal(provider, proposal, iter);
330 }
331
332 return ret;
333 }
334
335 static gboolean
gretl_provider_match(GtkSourceCompletionProvider * provider,GtkSourceCompletionContext * context)336 gretl_provider_match (GtkSourceCompletionProvider *provider,
337 GtkSourceCompletionContext *context)
338 {
339 return TRUE;
340 }
341
342 static void
snippet_provider_populate(GtkSourceCompletionProvider * provider,GtkSourceCompletionContext * context)343 snippet_provider_populate (GtkSourceCompletionProvider *provider,
344 GtkSourceCompletionContext *context)
345 {
346 GList *L = ((GretlProvider *) provider)->proposals;
347 GList *ret = NULL;
348 GtkTextIter iter;
349 gchar *word;
350 int n;
351
352 gtk_source_completion_context_get_iter(context, &iter);
353 word = get_word_at_iter(&iter);
354
355 if (word != NULL && (n = strlen(word)) >= 2) {
356 GtkSourceCompletionItem *item;
357 gchar *label;
358
359 while (L != NULL) {
360 item = L->data;
361 g_object_get(item, "label", &label, NULL);
362 if (!strncmp(label, word, n)) {
363 ret = g_list_prepend(ret, item);
364 }
365 g_free(label);
366 L = L->next;
367 }
368 g_free(word);
369 }
370
371 if (ret != NULL) {
372 ret = g_list_reverse(ret);
373 }
374
375 gtk_source_completion_context_add_proposals(context, provider, ret, TRUE);
376 g_list_free(ret);
377 }
378
comp_item_new(const gchar * label,const gchar * text)379 static GtkSourceCompletionItem *comp_item_new (const gchar *label,
380 const gchar *text)
381 {
382 GtkSourceCompletionItem *item;
383
384 #if GTKSOURCEVIEW_VERSION == 4
385 item = gtk_source_completion_item_new();
386 gtk_source_completion_item_set_label(item, label);
387 gtk_source_completion_item_set_text(item, text);
388 #else
389 item = gtk_source_completion_item_new(label, text, NULL, NULL);
390 #endif
391
392 return item;
393 }
394
395 static void
series_provider_populate(GtkSourceCompletionProvider * provider,GtkSourceCompletionContext * context)396 series_provider_populate (GtkSourceCompletionProvider *provider,
397 GtkSourceCompletionContext *context)
398 {
399 GList *ret = NULL;
400 GtkTextIter iter;
401 gchar *word;
402 int n;
403
404 gtk_source_completion_context_get_iter(context, &iter);
405 word = get_word_at_iter(&iter);
406
407 if (word != NULL && (n = strlen(word)) > 1) {
408 GtkSourceCompletionItem *item;
409 const char *vname;
410 int i;
411
412 for (i=0; i<dataset->v; i++) {
413 vname = dataset->varname[i];
414 if (!strncmp(vname, word, n)) {
415 item = comp_item_new(vname, vname);
416 ret = g_list_prepend(ret, item);
417 }
418 }
419 }
420
421 if (ret != NULL) {
422 ret = g_list_reverse(ret);
423 }
424
425 gtk_source_completion_context_add_proposals(context, provider, ret, TRUE);
426 g_list_free(ret);
427 }
428
429 static void
gretl_provider_populate(GtkSourceCompletionProvider * provider,GtkSourceCompletionContext * context)430 gretl_provider_populate (GtkSourceCompletionProvider *provider,
431 GtkSourceCompletionContext *context)
432 {
433 gint id = ((GretlProvider *) provider)->id;
434
435 if (id == PROV_SNIPPETS) {
436 snippet_provider_populate(provider, context);
437 } else if (id == PROV_SERIES) {
438 series_provider_populate(provider, context);
439 }
440 }
441
442 static void
gretl_provider_iface_init(GtkSourceCompletionProviderIface * iface)443 gretl_provider_iface_init (GtkSourceCompletionProviderIface *iface)
444 {
445 iface->get_name = gretl_provider_get_name;
446 iface->populate = gretl_provider_populate;
447 iface->match = gretl_provider_match;
448 iface->get_priority = gretl_provider_get_priority;
449 iface->get_activation = gretl_provider_get_activation;
450 iface->activate_proposal = gretl_activate_proposal;
451 }
452
453 static void
gretl_provider_set_activation(GretlProvider * gp,GtkSourceCompletionActivation A)454 gretl_provider_set_activation (GretlProvider *gp,
455 GtkSourceCompletionActivation A)
456 {
457 gp->activation = A;
458 }
459
460 static void
words_provider_set_activation(GObject * obj,GtkSourceCompletionActivation A)461 words_provider_set_activation (GObject *obj,
462 GtkSourceCompletionActivation A)
463 {
464 g_object_set(obj, "activation", A, NULL);
465 }
466
providers_set_activation(prov_info * pi,int userval)467 static void providers_set_activation (prov_info *pi,
468 int userval)
469 {
470 GtkSourceCompletionActivation A =
471 GTK_SOURCE_COMPLETION_ACTIVATION_NONE;
472 int i;
473
474 if (userval == COMPLETE_USER) {
475 A = GTK_SOURCE_COMPLETION_ACTIVATION_USER_REQUESTED;
476 } else if (userval == COMPLETE_AUTO) {
477 A = GTK_SOURCE_COMPLETION_ACTIVATION_INTERACTIVE;
478 }
479
480 for (i=0; i<N_PROV; i++) {
481 if (pi[i].ptr != NULL) {
482 #if AC_DEBUG
483 fprintf(stderr, "set activation %d on %s\n", A, prov_names[i]);
484 #endif
485 if (i >= PROV_SNIPPETS) {
486 gretl_provider_set_activation(pi[i].ptr, A);
487 } else {
488 words_provider_set_activation(pi[i].ptr, A);
489 }
490 }
491 }
492 }
493
gretl_provider_class_init(GretlProviderClass * klass)494 static void gretl_provider_class_init (GretlProviderClass *klass)
495 {
496 return;
497 }
498
499 struct snippet {
500 const char *label;
501 const char *text;
502 };
503
504 /* a few simple example snippets for now */
505
506 struct snippet snippets[] = {
507 { "loop..endloop", "loop \n\t\nendloop\n" },
508 { "if..endif", "if \n\t\nendif\n" },
509 { "function...", "function type name ()\n\t\nend function\n\n" },
510 { "outfile...", "outfile \n\t\nend outfile\n" },
511 { "plot...", "plot \n\t\nend plot\n" },
512 { "mpi...", "mpi\n\t\nend mpi\n" }
513 };
514
snippet_provider_init(GretlProvider * self)515 static void snippet_provider_init (GretlProvider *self)
516 {
517 GtkSourceCompletionItem *item;
518 GList *proposals = NULL;
519 int i, n = G_N_ELEMENTS(snippets);
520
521 for (i=0; i<n; i++) {
522 item = comp_item_new(snippets[i].label, snippets[i].text);
523 proposals = g_list_prepend(proposals, item);
524 }
525 self->proposals = proposals;
526 }
527
528 static int gretl_prov_id;
529
gretl_provider_init(GretlProvider * self)530 static void gretl_provider_init (GretlProvider *self)
531 {
532 if (gretl_prov_id == PROV_SNIPPETS) {
533 snippet_provider_init(self);
534 } else {
535 self->proposals = NULL;
536 }
537 }
538
539 #if AC_DEBUG
540
notify_activated(GtkSourceCompletion * comp,gpointer p)541 static void notify_activated (GtkSourceCompletion *comp,
542 gpointer p)
543 {
544 fprintf(stderr, "+++ got activate-proposal signal +++\n");
545 }
546
notify_hidden(GtkSourceCompletion * comp,gpointer p)547 static void notify_hidden (GtkSourceCompletion *comp,
548 gpointer p)
549 {
550 fprintf(stderr, "+++ got hide signal +++\n");
551 }
552
notify_populate(GtkSourceCompletion * comp,GtkSourceCompletionContext * context,gpointer p)553 static void notify_populate (GtkSourceCompletion *comp,
554 GtkSourceCompletionContext *context,
555 gpointer p)
556 {
557 fprintf(stderr, "+++ got populate-context signal +++\n");
558 }
559
560 #endif
561
add_gretl_provider(GtkSourceCompletion * comp,gint id,gint priority,windata_t * vwin,prov_info * pi)562 static void add_gretl_provider (GtkSourceCompletion *comp,
563 gint id, gint priority,
564 windata_t *vwin,
565 prov_info *pi)
566 {
567 GretlProvider *gp;
568
569 gretl_prov_id = id; /* hack! */
570 gp = g_object_new(gretl_provider_get_type(), NULL);
571 pi[id].ptr = gp;
572 gp->id = id;
573 gp->priority = priority;
574 gp->name = prov_names[id];
575 gtk_source_completion_add_provider(comp,
576 GTK_SOURCE_COMPLETION_PROVIDER(gp),
577 NULL);
578 g_object_unref(gp);
579 }
580
581 /* end snippets apparatus */
582
add_words_provider(GtkSourceCompletion * comp,gint8 id,gint priority,windata_t * vwin,prov_info * pi)583 static void add_words_provider (GtkSourceCompletion *comp,
584 gint8 id, gint priority,
585 windata_t *vwin,
586 prov_info *pi)
587 {
588 const char *name = prov_names[id];
589 GtkSourceCompletionWords *cw;
590 GtkTextBuffer *buf;
591
592 cw = gtk_source_completion_words_new(name, NULL);
593 pi[id].ptr = cw;
594 g_object_set(cw, "priority", priority, NULL);
595
596 if (id == PROV_CMDS) {
597 buf = pi[id].buf = command_names_buffer();
598 } else if (id == PROV_FUNCS) {
599 buf = pi[id].buf = function_names_buffer();
600 } else {
601 /* plain PROV_WORDS */
602 buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vwin->text));
603 }
604 gtk_source_completion_words_register(cw, buf);
605 gtk_source_completion_add_provider(comp,
606 GTK_SOURCE_COMPLETION_PROVIDER(cw),
607 NULL);
608 g_object_set_data(G_OBJECT(vwin->text), name, cw);
609 g_object_unref(cw);
610 }
611
set_sv_completion(windata_t * vwin)612 void set_sv_completion (windata_t *vwin)
613 {
614 GtkSourceCompletion *comp;
615 prov_info *pi = NULL;
616 int compval;
617
618 comp = gtk_source_view_get_completion(GTK_SOURCE_VIEW(vwin->text));
619 pi = g_object_get_data(G_OBJECT(vwin->text), "prov_info");
620 compval = (vwin->role == CONSOLE)? console_completion :
621 hansl_completion;
622
623 #if AC_DEBUG
624 fprintf(stderr, "set_sv_completion: comp %s, prov_info %s, completion %d\n",
625 comp==NULL ? "null" : "present", pi==NULL? "null" : "present",
626 compval);
627 #endif
628
629 if (compval && pi == NULL) {
630 /* set up and activate */
631 g_object_set(G_OBJECT(comp), "accelerators", 10,
632 "remember-info-visibility", TRUE, NULL);
633 pi = prov_info_new();
634 g_object_set_data(G_OBJECT(vwin->text), "prov_info", pi);
635 if (vwin->role == CONSOLE) {
636 add_words_provider(comp, PROV_CMDS, 4, vwin, pi);
637 add_gretl_provider(comp, PROV_SERIES, 3, vwin, pi);
638 add_words_provider(comp, PROV_FUNCS, 2, vwin, pi);
639 add_words_provider(comp, PROV_WORDS, 1, vwin, pi);
640 } else {
641 /* context is script editor */
642 add_gretl_provider(comp, PROV_SNIPPETS, 5, vwin, pi);
643 add_words_provider(comp, PROV_CMDS, 4, vwin, pi);
644 add_words_provider(comp, PROV_FUNCS, 3, vwin, pi);
645 add_gretl_provider(comp, PROV_SERIES, 2, vwin, pi);
646 add_words_provider(comp, PROV_WORDS, 1, vwin, pi);
647 }
648 g_signal_connect(G_OBJECT(vwin->text), "destroy",
649 G_CALLBACK(destroy_words_providers), NULL);
650 #if AC_DEBUG
651 g_signal_connect(G_OBJECT(comp), "activate-proposal",
652 G_CALLBACK(notify_activated), NULL);
653 g_signal_connect(G_OBJECT(comp), "hide",
654 G_CALLBACK(notify_hidden), NULL);
655 g_signal_connect(G_OBJECT(comp), "populate-context",
656 G_CALLBACK(notify_populate), NULL);
657 fprintf(stderr, "providers set up\n");
658 #endif
659 }
660
661 if (pi != NULL) {
662 providers_set_activation(pi, compval);
663 }
664 }
665
call_user_completion(GtkWidget * w)666 void call_user_completion (GtkWidget *w)
667 {
668 #if AC_DEBUG
669 fprintf(stderr, "call_user_completion...\n");
670 #endif
671 g_signal_emit_by_name(GTK_SOURCE_VIEW(w), "show-completion", NULL);
672 }
673