revert own commit r54625, broke autocomplete.
[blender.git] / source / blender / editors / space_text / text_autocomplete.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
19  * All rights reserved.
20  *
21  * The Original Code is: all of this file.
22  *
23  * Contributor(s): none yet.
24  *
25  * ***** END GPL LICENSE BLOCK *****
26  */
27
28 /** \file blender/editors/space_text/text_autocomplete.c
29  *  \ingroup sptext
30  */
31
32 #include <ctype.h>
33 #include <string.h>
34
35 #include "MEM_guardedalloc.h"
36
37 #include "DNA_text_types.h"
38
39 #include "BLI_blenlib.h"
40 #include "BLI_ghash.h"
41
42 #include "BKE_context.h"
43 #include "BKE_text.h"
44 #include "BKE_screen.h"
45 #include "BKE_suggestions.h"
46
47 #include "WM_api.h"
48 #include "WM_types.h"
49
50 #include "ED_screen.h"
51 #include "UI_interface.h"
52
53 #include "text_format.h"
54 #include "text_intern.h"  /* own include */
55
56
57 /* -------------------------------------------------------------------- */
58 /* Public API */
59
60 int text_do_suggest_select(SpaceText *st, ARegion *ar)
61 {
62         SuggItem *item, *first, *last /* , *sel */ /* UNUSED */;
63         TextLine *tmp;
64         int l, x, y, w, h, i;
65         int tgti, *top;
66         int mval[2] = {0, 0};
67
68         if (!st || !st->text) return 0;
69         if (!texttool_text_is_active(st->text)) return 0;
70
71         first = texttool_suggest_first();
72         last = texttool_suggest_last();
73         /* sel = texttool_suggest_selected(); */ /* UNUSED */
74         top = texttool_suggest_top();
75
76         if (!last || !first)
77                 return 0;
78
79         /* Count the visible lines to the cursor */
80         for (tmp = st->text->curl, l = -st->top; tmp; tmp = tmp->prev, l++) ;
81         if (l < 0) return 0;
82
83         text_update_character_width(st);
84
85         if (st->showlinenrs) {
86                 x = st->cwidth * (st->text->curc - st->left) + TXT_OFFSET + TEXTXLOC - 4;
87         }
88         else {
89                 x = st->cwidth * (st->text->curc - st->left) + TXT_OFFSET - 4;
90         }
91         y = ar->winy - st->lheight_dpi * l - 2;
92
93         w = SUGG_LIST_WIDTH * st->cwidth + U.widget_unit;
94         h = SUGG_LIST_SIZE * st->lheight_dpi + 0.4f * U.widget_unit;
95
96         // XXX getmouseco_areawin(mval);
97
98         if (mval[0] < x || x + w < mval[0] || mval[1] < y - h || y < mval[1])
99                 return 0;
100
101         /* Work out which of the items is at the top of the visible list */
102         for (i = 0, item = first; i < *top && item->next; i++, item = item->next) ;
103
104         /* Work out the target item index in the visible list */
105         tgti = (y - mval[1] - 4) / st->lheight_dpi;
106         if (tgti < 0 || tgti > SUGG_LIST_SIZE)
107                 return 1;
108
109         for (i = tgti; i > 0 && item->next; i--, item = item->next) ;
110         if (item)
111                 texttool_suggest_select(item);
112         return 1;
113 }
114
115 void text_pop_suggest_list(void)
116 {
117         SuggItem *item, *sel;
118         int *top, i;
119
120         item = texttool_suggest_first();
121         sel = texttool_suggest_selected();
122         top = texttool_suggest_top();
123
124         i = 0;
125         while (item && item != sel) {
126                 item = item->next;
127                 i++;
128         }
129         if (i > *top + SUGG_LIST_SIZE - 1)
130                 *top = i - SUGG_LIST_SIZE + 1;
131         else if (i < *top)
132                 *top = i;
133 }
134
135 /* -------------------------------------------------------------------- */
136 /* Private API */
137
138 static void text_autocomplete_free(bContext *C, wmOperator *op);
139
140 static GHash *text_autocomplete_build(Text *text)
141 {
142         GHash *gh;
143         int seek_len = 0;
144         const char *seek;
145         texttool_text_clear();
146
147         texttool_text_set_active(text);
148
149         /* first get the word we're at */
150         {
151                 const int i = text_find_identifier_start(text->curl->line, text->curc);
152                 seek_len = text->curc - i;
153                 seek = text->curl->line + i;
154
155                 // BLI_strncpy(seek, seek_ptr, seek_len);
156         }
157
158         /* now walk over entire doc and suggest words */
159         {
160                 TextLine *linep;
161
162                 gh = BLI_ghash_str_new(__func__);
163
164                 for (linep = text->lines.first; linep; linep = linep->next) {
165                         int i_start = 0;
166                         int i_end = 0;
167
168                         while (i_start < linep->len) {
169                                 /* seek identifier beginning */
170                                 while (i_start < linep->len && !text_check_identifier_nodigit(linep->line[i_start])) {
171                                         i_start++;
172                                 }
173                                 i_end = i_start;
174                                 while (i_end < linep->len && text_check_identifier(linep->line[i_end])) {
175                                         i_end++;
176                                 }
177
178                                 if ((i_start != i_end) &&
179                                     /* check we're at the beginning of a line or that the previous char is not an identifier
180                                          * this prevents digits from being added */
181                                     ((i_start < 1) || !text_check_identifier(linep->line[i_start - 1])))
182                                 {
183                                         char *str_sub = &linep->line[i_start];
184                                         const int choice_len = i_end - i_start;
185
186                                         if ((choice_len > seek_len) &&
187                                             (seek_len == 0 || strncmp(seek, str_sub, seek_len) == 0) &&
188                                             (seek != str_sub))
189                                         {
190                                                 // printf("Adding: %s\n", s);
191                                                 char str_sub_last = str_sub[choice_len];
192                                                 str_sub[choice_len] = '\0';
193                                                 if (!BLI_ghash_lookup(gh, str_sub)) {
194                                                         char *str_dup = BLI_strdupn(str_sub, choice_len);
195                                                         BLI_ghash_insert(gh, str_dup, str_dup);  /* A 'set' would make more sense here */
196                                                 }
197                                                 str_sub[choice_len] = str_sub_last;
198                                         }
199                                 }
200                                 i_start = i_end;
201                         }
202                 }
203
204                 {
205                         GHashIterator *iter = BLI_ghashIterator_new(gh);
206
207                         /* get the formatter for highlighting */
208                         TextFormatType *tft;
209                         tft = ED_text_format_get(text);
210
211                         for (; !BLI_ghashIterator_isDone(iter); BLI_ghashIterator_step(iter)) {
212                                 const char *s = BLI_ghashIterator_getValue(iter);
213                                 texttool_suggest_add(s, tft->format_identifier(s));
214                         }
215                         BLI_ghashIterator_free(iter);
216
217                 }
218         }
219
220         texttool_suggest_prefix(seek, seek_len);
221
222         return gh;
223 }
224
225 /* -- */
226
227 static void get_suggest_prefix(Text *text, int offset)
228 {
229         int i, len;
230         char *line;
231
232         if (!text) return;
233         if (!texttool_text_is_active(text)) return;
234
235         line = text->curl->line;
236         i = text_find_identifier_start(line, text->curc + offset);
237         len = text->curc - i + offset;
238         texttool_suggest_prefix(line + i, len);
239 }
240
241 static void confirm_suggestion(Text *text)
242 {
243         SuggItem *sel;
244         int i, over = 0;
245         const char *line;
246
247         if (!text) return;
248         if (!texttool_text_is_active(text)) return;
249
250         sel = texttool_suggest_selected();
251         if (!sel) return;
252
253         line = text->curl->line;
254         i = text_find_identifier_start(line, text->curc /* - skipleft */);
255         over = text->curc - i;
256
257 //      for (i = 0; i < skipleft; i++)
258 //              txt_move_left(text, 0);
259         for (i = 0; i < over; i++)
260                 txt_move_left(text, 1);
261
262         txt_insert_buf(text, sel->name);
263
264 //      for (i = 0; i < skipleft; i++)
265 //              txt_move_right(text, 0);
266
267         texttool_text_clear();
268 }
269
270 /* -- */
271
272
273 static int text_autocomplete_invoke(bContext *C, wmOperator *op, wmEvent *UNUSED(event))
274 {
275         SpaceText *st = CTX_wm_space_text(C);
276         Text *text = CTX_data_edit_text(C);
277
278         st->doplugins = TRUE;
279         op->customdata = text_autocomplete_build(text);
280
281         if (texttool_suggest_first()) {
282
283                 ED_area_tag_redraw(CTX_wm_area(C));
284
285                 if (texttool_suggest_first() == texttool_suggest_last()) {
286                         confirm_suggestion(st->text);
287                         text_update_line_edited(st->text->curl);
288                         text_autocomplete_free(C, op);
289                         return OPERATOR_FINISHED;
290                 }
291                 else {
292                         WM_event_add_modal_handler(C, op);
293                         return OPERATOR_RUNNING_MODAL;
294                 }
295         }
296         else {
297                 text_autocomplete_free(C, op);
298                 return OPERATOR_CANCELLED;
299         }
300 }
301
302 static int doc_scroll = 0;
303
304 static int text_autocomplete_modal(bContext *C, wmOperator *op, wmEvent *event)
305 {
306         SpaceText *st = CTX_wm_space_text(C);
307         ScrArea *sa = CTX_wm_area(C);
308         ARegion *ar = BKE_area_find_region_type(sa, RGN_TYPE_WINDOW);
309
310         int draw = 0, tools = 0, swallow = 0, scroll = 1;
311         Text *text = CTX_data_edit_text(C);
312         int retval = OPERATOR_RUNNING_MODAL;
313
314         (void)text;
315
316         if (st->doplugins && texttool_text_is_active(st->text)) {
317                 if (texttool_suggest_first()) tools |= TOOL_SUGG_LIST;
318                 if (texttool_docs_get()) tools |= TOOL_DOCUMENT;
319         }
320
321         switch (event->type) {
322                 case LEFTMOUSE:
323                         if (event->val == KM_PRESS) {
324                                 if (text_do_suggest_select(st, ar))
325                                         swallow = 1;
326                                 else {
327                                         if (tools & TOOL_SUGG_LIST) texttool_suggest_clear();
328                                         if (tools & TOOL_DOCUMENT) texttool_docs_clear(), doc_scroll = 0;
329                                         retval = OPERATOR_FINISHED;
330                                 }
331                                 draw = 1;
332                         }
333                         break;
334                 case MIDDLEMOUSE:
335                         if (event->val == KM_PRESS) {
336                                 if (text_do_suggest_select(st, ar)) {
337                                         confirm_suggestion(st->text);
338                                         text_update_line_edited(st->text->curl);
339                                         swallow = 1;
340                                 }
341                                 else {
342                                         if (tools & TOOL_SUGG_LIST) texttool_suggest_clear();
343                                         if (tools & TOOL_DOCUMENT) texttool_docs_clear(), doc_scroll = 0;
344                                         retval = OPERATOR_FINISHED;
345                                 }
346                                 draw = 1;
347                         }
348                         break;
349                 case ESCKEY:
350                         if (event->val == KM_PRESS) {
351                                 draw = swallow = 1;
352                                 if (tools & TOOL_SUGG_LIST) texttool_suggest_clear();
353                                 else if (tools & TOOL_DOCUMENT) texttool_docs_clear(), doc_scroll = 0;
354                                 else draw = swallow = 0;
355                                 retval = OPERATOR_CANCELLED;
356                         }
357                         break;
358                 case RETKEY:
359                         if (event->val == KM_PRESS) {
360                                 if (tools & TOOL_SUGG_LIST) {
361                                         confirm_suggestion(st->text);
362                                         text_update_line_edited(st->text->curl);
363                                         swallow = 1;
364                                         draw = 1;
365                                 }
366                                 if (tools & TOOL_DOCUMENT) texttool_docs_clear(), doc_scroll = 0, draw = 1;
367                                 retval = OPERATOR_FINISHED;
368                         }
369                         break;
370                 case LEFTARROWKEY:
371                 case BACKSPACEKEY:
372                         if (event->val == KM_PRESS) {
373                                 if (tools & TOOL_SUGG_LIST) {
374                                         if (event->ctrl) {
375                                                 texttool_suggest_clear();
376                                                 retval = OPERATOR_CANCELLED;
377                                         }
378                                         else {
379                                                 /* Work out which char we are about to delete/pass */
380                                                 if (st->text->curl && st->text->curc > 0) {
381                                                         char ch = st->text->curl->line[st->text->curc - 1];
382                                                         if ((ch == '_' || !ispunct(ch)) && !text_check_whitespace(ch)) {
383                                                                 get_suggest_prefix(st->text, -1);
384                                                                 text_pop_suggest_list();
385                                                         }
386                                                         else {
387                                                                 texttool_suggest_clear();
388                                                                 retval = OPERATOR_CANCELLED;
389                                                         }
390                                                 }
391                                                 else {
392                                                         texttool_suggest_clear();
393                                                         retval = OPERATOR_CANCELLED;
394                                                 }
395                                         }
396                                 }
397                                 if (tools & TOOL_DOCUMENT) texttool_docs_clear(), doc_scroll = 0;
398                         }
399                         break;
400                 case RIGHTARROWKEY:
401                         if (event->val == KM_PRESS) {
402                                 if (tools & TOOL_SUGG_LIST) {
403                                         if (event->ctrl) {
404                                                 texttool_suggest_clear();
405                                                 retval = OPERATOR_CANCELLED;
406                                         }
407                                         else {
408                                                 /* Work out which char we are about to pass */
409                                                 if (st->text->curl && st->text->curc < st->text->curl->len) {
410                                                         char ch = st->text->curl->line[st->text->curc + 1];
411                                                         if ((ch == '_' || !ispunct(ch)) && !text_check_whitespace(ch)) {
412                                                                 get_suggest_prefix(st->text, 1);
413                                                                 text_pop_suggest_list();
414                                                         }
415                                                         else {
416                                                                 texttool_suggest_clear();
417                                                                 retval = OPERATOR_CANCELLED;
418                                                         }
419                                                 }
420                                                 else {
421                                                         texttool_suggest_clear();
422                                                         retval = OPERATOR_CANCELLED;
423                                                 }
424                                         }
425                                 }
426                                 if (tools & TOOL_DOCUMENT) texttool_docs_clear(), doc_scroll = 0;
427                         }
428                         break;
429                 case PAGEDOWNKEY:
430                         scroll = SUGG_LIST_SIZE - 1;
431                 case WHEELDOWNMOUSE:
432                 case DOWNARROWKEY:
433                         if (event->val == KM_PRESS) {
434                                 if (tools & TOOL_DOCUMENT) {
435                                         doc_scroll++;
436                                         swallow = 1;
437                                         draw = 1;
438                                 }
439                                 else if (tools & TOOL_SUGG_LIST) {
440                                         SuggItem *sel = texttool_suggest_selected();
441                                         if (!sel) {
442                                                 texttool_suggest_select(texttool_suggest_first());
443                                         }
444                                         else {
445                                                 while (sel && sel != texttool_suggest_last() && sel->next && scroll--) {
446                                                         texttool_suggest_select(sel->next);
447                                                         sel = sel->next;
448                                                 }
449                                         }
450                                         text_pop_suggest_list();
451                                         swallow = 1;
452                                         draw = 1;
453                                 }
454                         }
455                         break;
456                 case PAGEUPKEY:
457                         scroll = SUGG_LIST_SIZE - 1;
458                 case WHEELUPMOUSE:
459                 case UPARROWKEY:
460                         if (event->val == KM_PRESS) {
461                                 if (tools & TOOL_DOCUMENT) {
462                                         if (doc_scroll > 0) doc_scroll--;
463                                         swallow = 1;
464                                         draw = 1;
465                                 }
466                                 else if (tools & TOOL_SUGG_LIST) {
467                                         SuggItem *sel = texttool_suggest_selected();
468                                         while (sel && sel != texttool_suggest_first() && sel->prev && scroll--) {
469                                                 texttool_suggest_select(sel->prev);
470                                                 sel = sel->prev;
471                                         }
472                                         text_pop_suggest_list();
473                                         swallow = 1;
474                                         draw = 1;
475                                 }
476                         }
477                         break;
478                 case RIGHTSHIFTKEY:
479                 case LEFTSHIFTKEY:
480                         break;
481 #if 0
482                 default:
483                         if (tools & TOOL_SUGG_LIST) texttool_suggest_clear(), draw = 1;
484                         if (tools & TOOL_DOCUMENT) texttool_docs_clear(), doc_scroll = 0, draw = 1;
485 #endif
486         }
487
488         if (draw) {
489                 ED_area_tag_redraw(CTX_wm_area(C));
490         }
491
492 //      if (swallow) {
493 //              retval = OPERATOR_RUNNING_MODAL;
494 //      }
495
496         if (texttool_suggest_first()) {
497                 if (retval != OPERATOR_RUNNING_MODAL) {
498                         text_autocomplete_free(C, op);
499                 }
500                 return retval;
501         }
502         else {
503                 text_autocomplete_free(C, op);
504                 return OPERATOR_FINISHED;
505         }
506 }
507
508 static void text_autocomplete_free(bContext *C, wmOperator *op)
509 {
510         GHash *gh = op->customdata;
511         if (gh) {
512                 BLI_ghash_free(gh, NULL, (GHashValFreeFP)MEM_freeN);
513                 op->customdata = NULL;
514         }
515
516         /* other stuff */
517         {
518                 SpaceText *st = CTX_wm_space_text(C);
519                 st->doplugins = FALSE;
520                 texttool_text_clear();
521         }
522 }
523
524 static int text_autocomplete_cancel(bContext *C, wmOperator *op)
525 {
526         text_autocomplete_free(C, op);
527         return OPERATOR_CANCELLED;
528 }
529
530 void TEXT_OT_autocomplete(wmOperatorType *ot)
531 {
532         /* identifiers */
533         ot->name = "Text Auto Complete";
534         ot->description = "Show a list of used text in the open document";
535         ot->idname = "TEXT_OT_autocomplete";
536
537         /* api callbacks */
538         ot->invoke = text_autocomplete_invoke;
539         ot->cancel = text_autocomplete_cancel;
540         ot->modal = text_autocomplete_modal;
541         ot->poll = text_space_edit_poll;
542
543         /* flags */
544         ot->flag = OPTYPE_BLOCKING;
545 }