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