Blender V5.0
text_autocomplete.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include <cctype>
10#include <cstring>
11
12#include "MEM_guardedalloc.h"
13
14#include "DNA_text_types.h"
15
16#include "BLI_ghash.h"
17#include "BLI_listbase.h"
18#include "BLI_string.h"
19#include "BLI_string_utf8.h"
20
21#include "BKE_context.hh"
22#include "BKE_screen.hh"
23#include "BKE_text.h"
25
26#include "WM_api.hh"
27#include "WM_types.hh"
28
29#include "ED_screen.hh"
30#include "ED_text.hh"
31#include "ED_undo.hh"
32
33#include "text_format.hh"
34#include "text_intern.hh" /* Own include. */
35
36/* -------------------------------------------------------------------- */
39
40bool space_text_do_suggest_select(SpaceText *st, const ARegion *region, const int mval[2])
41{
42 const int lheight = TXT_LINE_HEIGHT(st);
43 SuggItem *item, *first, *last /* , *sel */ /* UNUSED. */;
44 TextLine *tmp;
45 int l, x, y, w, h, i;
46 int tgti, *top;
47
48 if (!st->text) {
49 return false;
50 }
51 if (!texttool_text_is_active(st->text)) {
52 return false;
53 }
54
55 first = texttool_suggest_first();
56 last = texttool_suggest_last();
57 // sel = texttool_suggest_selected(); /* UNUSED. */
59
60 if (!last || !first) {
61 return false;
62 }
63
64 /* Count the visible lines to the cursor. */
65 for (tmp = st->text->curl, l = -st->top; tmp; tmp = tmp->prev, l++) {
66 /* Pass. */
67 }
68 if (l < 0) {
69 return false;
70 }
71
73
74 x = TXT_BODY_LEFT(st) + (st->runtime->cwidth_px * (st->text->curc - st->left));
75 y = region->winy - lheight * l - 2;
76
77 w = SUGG_LIST_WIDTH * st->runtime->cwidth_px + U.widget_unit;
78 h = SUGG_LIST_SIZE * lheight + 0.4f * U.widget_unit;
79
80 if (mval[0] < x || x + w < mval[0] || mval[1] < y - h || y < mval[1]) {
81 return false;
82 }
83
84 /* Work out which of the items is at the top of the visible list. */
85 for (i = 0, item = first; i < *top && item->next; i++, item = item->next) {
86 /* Pass. */
87 }
88
89 /* Work out the target item index in the visible list. */
90 tgti = (y - mval[1] - 4) / lheight;
91 if (tgti < 0 || tgti > SUGG_LIST_SIZE) {
92 return true;
93 }
94
95 for (i = tgti; i > 0 && item->next; i--, item = item->next) {
96 /* Pass. */
97 }
98 if (item) {
100 }
101 return true;
102}
103
105{
106 SuggItem *item, *sel;
107 int *top, i;
108
109 item = texttool_suggest_first();
112
113 i = 0;
114 while (item && item != sel) {
115 item = item->next;
116 i++;
117 }
118 if (i > *top + SUGG_LIST_SIZE - 1) {
119 *top = i - SUGG_LIST_SIZE + 1;
120 }
121 else if (i < *top) {
122 *top = i;
123 }
124}
125
127
128/* -------------------------------------------------------------------- */
131
133
135{
136 GHash *gh;
137 int seek_len = 0;
138 const char *seek;
140
142
143 /* First get the word we're at. */
144 {
145 const int i = text_find_identifier_start(text->curl->line, text->curc);
146 seek_len = text->curc - i;
147 seek = text->curl->line + i;
148
149 // BLI_strncpy_utf8(seek, seek_ptr, seek_len);
150 }
151
152 /* Now walk over entire doc and suggest words. */
153 {
154 gh = BLI_ghash_str_new(__func__);
155
156 LISTBASE_FOREACH (TextLine *, linep, &text->lines) {
157 size_t i_start = 0;
158 size_t i_end = 0;
159 size_t i_pos = 0;
160
161 while (i_start < linep->len) {
162 /* Seek identifier beginning. */
163 i_pos = i_start;
164 while ((i_start < linep->len) &&
166 BLI_str_utf8_as_unicode_step_safe(linep->line, linep->len, &i_pos)))
167 {
168 i_start = i_pos;
169 }
170 i_pos = i_end = i_start;
171 while ((i_end < linep->len) &&
173 BLI_str_utf8_as_unicode_step_safe(linep->line, linep->len, &i_pos)))
174 {
175 i_end = i_pos;
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
180 * identifier this prevents digits from being added. */
181 ((i_start < 1) || !text_check_identifier_unicode(
182 BLI_str_utf8_as_unicode_or_error(&linep->line[i_start - 1]))))
183 {
184 char *str_sub = &linep->line[i_start];
185 const int choice_len = i_end - i_start;
186
187 if ((choice_len > seek_len) && (seek_len == 0 || STREQLEN(seek, str_sub, seek_len)) &&
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 /* A `set` would make more sense here. */
196 BLI_ghash_insert(gh, str_dup, str_dup);
197 }
198 str_sub[choice_len] = str_sub_last;
199 }
200 }
201 if (i_end != i_start) {
202 i_start = i_end;
203 }
204 else {
205 /* Highly unlikely, but prevent eternal loop. */
206 i_start++;
207 }
208 }
209 }
210
211 {
212 GHashIterator gh_iter;
213
214 /* Get the formatter for highlighting. */
215 TextFormatType *tft;
216 tft = ED_text_format_get(text);
217
218 GHASH_ITER (gh_iter, gh) {
219 const char *s = static_cast<char *>(BLI_ghashIterator_getValue(&gh_iter));
221 }
222 }
223 }
224
225 texttool_suggest_prefix(seek, seek_len);
226
227 return gh;
228}
229
230/* -- */
231
232static void get_suggest_prefix(Text *text, int offset)
233{
234 int i, len;
235 const char *line;
236
237 if (!text) {
238 return;
239 }
240 if (!texttool_text_is_active(text)) {
241 return;
242 }
243
244 line = text->curl->line;
245 i = text_find_identifier_start(line, text->curc + offset);
246 len = text->curc - i + offset;
248}
249
250static void confirm_suggestion(Text *text)
251{
252 SuggItem *sel;
253 int i, over = 0;
254 const char *line;
255
256 if (!text) {
257 return;
258 }
259 if (!texttool_text_is_active(text)) {
260 return;
261 }
262
264 if (!sel) {
265 return;
266 }
267
268 line = text->curl->line;
269 i = text_find_identifier_start(line, text->curc /* - skipleft. */);
270 over = text->curc - i;
271
272 // for (i = 0; i < skipleft; i++)
273 // txt_move_left(text, 0);
274 BLI_assert(memcmp(sel->name, &line[i], over) == 0);
275 const char *buf = sel->name + over;
276 txt_insert_buf(text, buf, strlen(buf));
277
278 // for (i = 0; i < skipleft; i++)
279 // txt_move_right(text, 0);
280
282}
283
285
286/* -------------------------------------------------------------------- */
289
291 wmOperator *op,
292 const wmEvent * /*event*/)
293{
295 Text *text = CTX_data_edit_text(C);
296
297 st->doplugins = true;
299
301
303
309 ED_undo_push(C, op->type->name);
310 return OPERATOR_FINISHED;
311 }
312
315 }
317 return OPERATOR_CANCELLED;
318}
319
321{
322 /* NOTE(@ideasman42): this code could be refactored or rewritten. */
324 ScrArea *area = CTX_wm_area(C);
326
327 int draw = 0, tools = 0, swallow = 0, scroll = 1;
329
330 if (st->doplugins && texttool_text_is_active(st->text)) {
332 tools |= TOOL_SUGG_LIST;
333 }
334 }
335
336 switch (event->type) {
337 case MOUSEMOVE: {
338 if (space_text_do_suggest_select(st, region, event->mval)) {
339 draw = 1;
340 }
341 swallow = 1;
342 break;
343 }
344 case LEFTMOUSE:
345 if (event->val == KM_PRESS) {
346 if (space_text_do_suggest_select(st, region, event->mval)) {
347 if (tools & TOOL_SUGG_LIST) {
351 ED_undo_push(C, op->type->name);
352 swallow = 1;
353 draw = 1;
354 }
355 retval = OPERATOR_FINISHED;
356 }
357 else {
358 if (tools & TOOL_SUGG_LIST) {
360 }
361 retval = OPERATOR_CANCELLED;
362 }
363 draw = 1;
364 }
365 break;
366 case EVT_ESCKEY:
367 if (event->val == KM_PRESS) {
368 draw = swallow = 1;
369 if (tools & TOOL_SUGG_LIST) {
371 }
372 else {
373 draw = swallow = 0;
374 }
375 retval = OPERATOR_CANCELLED;
376 }
377 break;
378 case EVT_RETKEY:
379 case EVT_PADENTER:
380 if (event->val == KM_PRESS) {
381 if (tools & TOOL_SUGG_LIST) {
385 ED_undo_push(C, op->type->name);
386 swallow = 1;
387 draw = 1;
388 }
389 retval = OPERATOR_FINISHED;
390 }
391 break;
392 case EVT_LEFTARROWKEY:
393 case EVT_BACKSPACEKEY:
394 if (event->val == KM_PRESS) {
395 if (tools & TOOL_SUGG_LIST) {
396 if (event->modifier & KM_CTRL) {
398 retval = OPERATOR_CANCELLED;
399 draw = 1;
400 }
401 else {
402 /* Work out which char we are about to delete/pass. */
403 if (st->text->curl && st->text->curc > 0) {
404 char ch = st->text->curl->line[st->text->curc - 1];
405 if ((ch == '_' || !ispunct(ch)) && !text_check_whitespace(ch)) {
406 get_suggest_prefix(st->text, -1);
408 txt_move_left(st->text, false);
409 draw = 1;
410 }
411 else {
413 retval = OPERATOR_CANCELLED;
414 draw = 1;
415 }
416 }
417 else {
419 retval = OPERATOR_CANCELLED;
420 draw = 1;
421 }
422 }
423 }
424 }
425 break;
427 if (event->val == KM_PRESS) {
428 if (tools & TOOL_SUGG_LIST) {
429 if (event->modifier & KM_CTRL) {
431 retval = OPERATOR_CANCELLED;
432 draw = 1;
433 }
434 else {
435 /* Work out which char we are about to pass. */
436 if (st->text->curl && st->text->curc < st->text->curl->len) {
437 char ch = st->text->curl->line[st->text->curc];
438 if ((ch == '_' || !ispunct(ch)) && !text_check_whitespace(ch)) {
439 get_suggest_prefix(st->text, 1);
441 txt_move_right(st->text, false);
442 draw = 1;
443 }
444 else {
446 retval = OPERATOR_CANCELLED;
447 draw = 1;
448 }
449 }
450 else {
452 retval = OPERATOR_CANCELLED;
453 draw = 1;
454 }
455 }
456 }
457 }
458 break;
459 case EVT_PAGEDOWNKEY:
460 scroll = SUGG_LIST_SIZE - 1;
462 case WHEELDOWNMOUSE:
463 case EVT_DOWNARROWKEY:
464 if (event->val == KM_PRESS) {
465 if (tools & TOOL_SUGG_LIST) {
467 if (!sel) {
469 }
470 else {
471 while (sel && scroll--) {
472 if (sel != texttool_suggest_last() && sel->next) {
474 sel = sel->next;
475 }
476 else {
479 }
480 }
481 }
483 swallow = 1;
484 draw = 1;
485 }
486 }
487 break;
488 case EVT_PAGEUPKEY:
489 scroll = SUGG_LIST_SIZE - 1;
491 case WHEELUPMOUSE:
492 case EVT_UPARROWKEY:
493 if (event->val == KM_PRESS) {
494 if (tools & TOOL_SUGG_LIST) {
496 while (sel && scroll--) {
497 if (sel != texttool_suggest_first() && sel->prev) {
499 sel = sel->prev;
500 }
501 else {
503 sel = texttool_suggest_last();
504 }
505 }
507 swallow = 1;
508 draw = 1;
509 }
510 }
511 break;
513 case EVT_LEFTSHIFTKEY:
514 break;
515 default: {
516#if 0
517 if (tools & TOOL_SUGG_LIST) {
519 draw = 1;
520 }
521#endif
522 break;
523 }
524 }
525
526 if (draw) {
527 ED_area_tag_redraw(area);
528 }
529
530 if (swallow) {
531 // retval = OPERATOR_RUNNING_MODAL;
532 }
533
535 if (retval != OPERATOR_RUNNING_MODAL) {
537 }
538 return retval;
539 }
541 return OPERATOR_FINISHED;
542}
543
545{
546 GHash *gh = static_cast<GHash *>(op->customdata);
547 if (gh) {
548 BLI_ghash_free(gh, nullptr, MEM_freeN);
549 op->customdata = nullptr;
550 }
551
552 /* Other stuff. */
553 {
555 st->doplugins = false;
557 }
558}
559
564
566{
567 /* Identifiers. */
568 ot->name = "Text Auto Complete";
569 ot->description = "Show a list of used text in the open document";
570 ot->idname = "TEXT_OT_autocomplete";
571
572 /* API callbacks. */
576 ot->poll = text_space_edit_poll;
577
578 /* Flags. */
579 /* Undo is handled conditionally by this operator. */
580 ot->flag = OPTYPE_BLOCKING;
581}
582
SpaceText * CTX_wm_space_text(const bContext *C)
ScrArea * CTX_wm_area(const bContext *C)
Text * CTX_data_edit_text(const bContext *C)
ARegion * BKE_area_find_region_type(const ScrArea *area, int region_type)
Definition screen.cc:846
int text_check_identifier_unicode(unsigned int ch)
Definition text.cc:2361
void txt_move_left(struct Text *text, bool sel)
Definition text.cc:870
void txt_move_right(struct Text *text, bool sel)
Definition text.cc:913
int text_check_identifier_nodigit_unicode(unsigned int ch)
Definition text.cc:2366
bool text_check_whitespace(char ch)
Definition text.cc:2372
void txt_insert_buf(struct Text *text, const char *in_buffer, int in_buffer_len) ATTR_NONNULL(1
int text_find_identifier_start(const char *str, int i)
Definition text.cc:2380
short texttool_text_is_active(struct Text *text)
void texttool_text_clear(void)
void texttool_suggest_prefix(const char *prefix, int prefix_len)
int * texttool_suggest_top(void)
SuggItem * texttool_suggest_first(void)
void texttool_suggest_select(SuggItem *sel)
void texttool_suggest_add(const char *name, char type)
SuggItem * texttool_suggest_last(void)
void texttool_text_set_active(struct Text *text)
SuggItem * texttool_suggest_selected(void)
void texttool_suggest_clear(void)
#define BLI_assert(a)
Definition BLI_assert.h:46
#define ATTR_FALLTHROUGH
BLI_INLINE void * BLI_ghashIterator_getValue(GHashIterator *ghi) ATTR_WARN_UNUSED_RESULT
Definition BLI_ghash.h:299
#define GHASH_ITER(gh_iter_, ghash_)
Definition BLI_ghash.h:318
GHash * BLI_ghash_str_new(const char *info) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT
void * BLI_ghash_lookup(const GHash *gh, const void *key) ATTR_WARN_UNUSED_RESULT
Definition BLI_ghash.cc:731
void BLI_ghash_insert(GHash *gh, void *key, void *val)
Definition BLI_ghash.cc:707
void BLI_ghash_free(GHash *gh, GHashKeyFreeFP keyfreefp, GHashValFreeFP valfreefp)
Definition BLI_ghash.cc:860
#define LISTBASE_FOREACH(type, var, list)
char * BLI_strdupn(const char *str, size_t len) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition string.cc:30
unsigned int BLI_str_utf8_as_unicode_step_safe(const char *__restrict p, size_t p_len, size_t *__restrict index) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1
unsigned int BLI_str_utf8_as_unicode_or_error(const char *p) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
#define STREQLEN(a, b, n)
@ RGN_TYPE_WINDOW
@ OPERATOR_CANCELLED
@ OPERATOR_FINISHED
@ OPERATOR_RUNNING_MODAL
void ED_area_tag_redraw(ScrArea *area)
Definition area.cc:693
UndoStep * ED_text_undo_push_init(bContext *C)
Definition text_undo.cc:249
void ED_undo_push(bContext *C, const char *str)
Definition ed_undo.cc:98
Read Guarded memory(de)allocation.
#define C
Definition RandGen.cpp:29
@ KM_CTRL
Definition WM_types.hh:279
@ KM_PRESS
Definition WM_types.hh:311
@ OPTYPE_BLOCKING
Definition WM_types.hh:184
#define U
ATTR_WARN_UNUSED_RESULT const BMLoop * l
SIMD_FORCE_INLINE const btScalar & w() const
Return the w value.
Definition btQuadWord.h:119
uint top
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
SpaceText_Runtime * runtime
struct Text * text
struct SuggItem * prev
struct SuggItem * next
char(* format_identifier)(const char *string)
char * line
struct TextLine * prev
ListBase lines
TextLine * curl
wmEventModifierFlag modifier
Definition WM_types.hh:774
wmEventType type
Definition WM_types.hh:757
short val
Definition WM_types.hh:759
int mval[2]
Definition WM_types.hh:763
const char * name
Definition WM_types.hh:1033
struct wmOperatorType * type
void TEXT_OT_autocomplete(wmOperatorType *ot)
static wmOperatorStatus text_autocomplete_modal(bContext *C, wmOperator *op, const wmEvent *event)
static GHash * text_autocomplete_build(Text *text)
static void text_autocomplete_free(bContext *C, wmOperator *op)
static void confirm_suggestion(Text *text)
static wmOperatorStatus text_autocomplete_invoke(bContext *C, wmOperator *op, const wmEvent *)
void text_pop_suggest_list()
bool space_text_do_suggest_select(SpaceText *st, const ARegion *region, const int mval[2])
static void get_suggest_prefix(Text *text, int offset)
static void text_autocomplete_cancel(bContext *C, wmOperator *op)
linep
Definition text_draw.cc:229
void space_text_update_character_width(SpaceText *st)
i
Definition text_draw.cc:230
TextFormatType * ED_text_format_get(Text *text)
#define SUGG_LIST_WIDTH
#define TXT_LINE_HEIGHT(st)
bool text_space_edit_poll(bContext *C)
Definition text_ops.cc:279
#define SUGG_LIST_SIZE
#define TXT_BODY_LEFT(st)
#define TOOL_SUGG_LIST
void text_update_line_edited(TextLine *line)
Definition text_ops.cc:324
uint len
wmEventHandler_Op * WM_event_add_modal_handler(bContext *C, wmOperator *op)
@ EVT_DOWNARROWKEY
@ WHEELUPMOUSE
@ EVT_PAGEUPKEY
@ EVT_PAGEDOWNKEY
@ EVT_RIGHTARROWKEY
@ EVT_PADENTER
@ WHEELDOWNMOUSE
@ MOUSEMOVE
@ EVT_UPARROWKEY
@ LEFTMOUSE
@ EVT_LEFTARROWKEY
@ EVT_ESCKEY
@ EVT_BACKSPACEKEY
@ EVT_RIGHTSHIFTKEY
@ EVT_LEFTSHIFTKEY
@ EVT_RETKEY
wmOperatorType * ot
Definition wm_files.cc:4237