Blender V4.3
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
9#include <cctype>
10#include <cstring>
11
12#include "MEM_guardedalloc.h"
13
14#include "DNA_text_types.h"
15
16#include "BLI_blenlib.h"
17#include "BLI_ghash.h"
18
19#include "BKE_context.hh"
20#include "BKE_screen.hh"
21#include "BKE_text.h"
23
24#include "WM_api.hh"
25#include "WM_types.hh"
26
27#include "ED_screen.hh"
28#include "ED_text.hh"
29#include "ED_undo.hh"
30
31#include "text_format.hh"
32#include "text_intern.hh" /* own include */
33
34/* -------------------------------------------------------------------- */
38bool space_text_do_suggest_select(SpaceText *st, const ARegion *region, const int mval[2])
39{
40 const int lheight = TXT_LINE_HEIGHT(st);
41 SuggItem *item, *first, *last /* , *sel */ /* UNUSED */;
42 TextLine *tmp;
43 int l, x, y, w, h, i;
44 int tgti, *top;
45
46 if (!st->text) {
47 return false;
48 }
49 if (!texttool_text_is_active(st->text)) {
50 return false;
51 }
52
53 first = texttool_suggest_first();
54 last = texttool_suggest_last();
55 // sel = texttool_suggest_selected(); /* UNUSED. */
57
58 if (!last || !first) {
59 return false;
60 }
61
62 /* Count the visible lines to the cursor */
63 for (tmp = st->text->curl, l = -st->top; tmp; tmp = tmp->prev, l++) {
64 /* pass */
65 }
66 if (l < 0) {
67 return false;
68 }
69
71
72 x = TXT_BODY_LEFT(st) + (st->runtime->cwidth_px * (st->text->curc - st->left));
73 y = region->winy - lheight * l - 2;
74
75 w = SUGG_LIST_WIDTH * st->runtime->cwidth_px + U.widget_unit;
76 h = SUGG_LIST_SIZE * lheight + 0.4f * U.widget_unit;
77
78 if (mval[0] < x || x + w < mval[0] || mval[1] < y - h || y < mval[1]) {
79 return false;
80 }
81
82 /* Work out which of the items is at the top of the visible list */
83 for (i = 0, item = first; i < *top && item->next; i++, item = item->next) {
84 /* pass */
85 }
86
87 /* Work out the target item index in the visible list */
88 tgti = (y - mval[1] - 4) / lheight;
89 if (tgti < 0 || tgti > SUGG_LIST_SIZE) {
90 return true;
91 }
92
93 for (i = tgti; i > 0 && item->next; i--, item = item->next) {
94 /* pass */
95 }
96 if (item) {
98 }
99 return true;
100}
101
103{
104 SuggItem *item, *sel;
105 int *top, i;
106
107 item = texttool_suggest_first();
109 top = texttool_suggest_top();
110
111 i = 0;
112 while (item && item != sel) {
113 item = item->next;
114 i++;
115 }
116 if (i > *top + SUGG_LIST_SIZE - 1) {
117 *top = i - SUGG_LIST_SIZE + 1;
118 }
119 else if (i < *top) {
120 *top = i;
121 }
122}
123
126/* -------------------------------------------------------------------- */
130static void text_autocomplete_free(bContext *C, wmOperator *op);
131
133{
134 GHash *gh;
135 int seek_len = 0;
136 const char *seek;
138
140
141 /* first get the word we're at */
142 {
143 const int i = text_find_identifier_start(text->curl->line, text->curc);
144 seek_len = text->curc - i;
145 seek = text->curl->line + i;
146
147 // BLI_strncpy(seek, seek_ptr, seek_len);
148 }
149
150 /* now walk over entire doc and suggest words */
151 {
152 gh = BLI_ghash_str_new(__func__);
153
154 LISTBASE_FOREACH (TextLine *, linep, &text->lines) {
155 size_t i_start = 0;
156 size_t i_end = 0;
157 size_t i_pos = 0;
158
159 while (i_start < linep->len) {
160 /* seek identifier beginning */
161 i_pos = i_start;
162 while ((i_start < linep->len) &&
164 BLI_str_utf8_as_unicode_step_safe(linep->line, linep->len, &i_pos)))
165 {
166 i_start = i_pos;
167 }
168 i_pos = i_end = i_start;
169 while ((i_end < linep->len) &&
171 BLI_str_utf8_as_unicode_step_safe(linep->line, linep->len, &i_pos)))
172 {
173 i_end = i_pos;
174 }
175
176 if ((i_start != i_end) &&
177 /* Check we're at the beginning of a line or that the previous char is not an
178 * identifier this prevents digits from being added. */
179 ((i_start < 1) || !text_check_identifier_unicode(
180 BLI_str_utf8_as_unicode_or_error(&linep->line[i_start - 1]))))
181 {
182 char *str_sub = &linep->line[i_start];
183 const int choice_len = i_end - i_start;
184
185 if ((choice_len > seek_len) && (seek_len == 0 || STREQLEN(seek, str_sub, seek_len)) &&
186 (seek != str_sub))
187 {
188 // printf("Adding: %s\n", s);
189 char str_sub_last = str_sub[choice_len];
190 str_sub[choice_len] = '\0';
191 if (!BLI_ghash_lookup(gh, str_sub)) {
192 char *str_dup = BLI_strdupn(str_sub, choice_len);
193 /* A 'set' would make more sense here */
194 BLI_ghash_insert(gh, str_dup, str_dup);
195 }
196 str_sub[choice_len] = str_sub_last;
197 }
198 }
199 if (i_end != i_start) {
200 i_start = i_end;
201 }
202 else {
203 /* highly unlikely, but prevent eternal loop */
204 i_start++;
205 }
206 }
207 }
208
209 {
210 GHashIterator gh_iter;
211
212 /* get the formatter for highlighting */
213 TextFormatType *tft;
214 tft = ED_text_format_get(text);
215
216 GHASH_ITER (gh_iter, gh) {
217 const char *s = static_cast<char *>(BLI_ghashIterator_getValue(&gh_iter));
219 }
220 }
221 }
222
223 texttool_suggest_prefix(seek, seek_len);
224
225 return gh;
226}
227
228/* -- */
229
230static void get_suggest_prefix(Text *text, int offset)
231{
232 int i, len;
233 const char *line;
234
235 if (!text) {
236 return;
237 }
238 if (!texttool_text_is_active(text)) {
239 return;
240 }
241
242 line = text->curl->line;
243 i = text_find_identifier_start(line, text->curc + offset);
244 len = text->curc - i + offset;
245 texttool_suggest_prefix(line + i, len);
246}
247
248static void confirm_suggestion(Text *text)
249{
250 SuggItem *sel;
251 int i, over = 0;
252 const char *line;
253
254 if (!text) {
255 return;
256 }
257 if (!texttool_text_is_active(text)) {
258 return;
259 }
260
262 if (!sel) {
263 return;
264 }
265
266 line = text->curl->line;
267 i = text_find_identifier_start(line, text->curc /* - skipleft */);
268 over = text->curc - i;
269
270 // for (i = 0; i < skipleft; i++)
271 // txt_move_left(text, 0);
272 BLI_assert(memcmp(sel->name, &line[i], over) == 0);
273 const char *buf = sel->name + over;
274 txt_insert_buf(text, buf, strlen(buf));
275
276 // for (i = 0; i < skipleft; i++)
277 // txt_move_right(text, 0);
278
280}
281
284/* -------------------------------------------------------------------- */
288static int text_autocomplete_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
289{
291 Text *text = CTX_data_edit_text(C);
292
293 st->doplugins = true;
295
297
299
305 ED_undo_push(C, op->type->name);
306 return OPERATOR_FINISHED;
307 }
308
311 }
313 return OPERATOR_CANCELLED;
314}
315
316static int text_autocomplete_modal(bContext *C, wmOperator *op, const wmEvent *event)
317{
318 /* NOTE(@ideasman42): this code could be refactored or rewritten. */
320 ScrArea *area = CTX_wm_area(C);
322
323 int draw = 0, tools = 0, swallow = 0, scroll = 1;
324 int retval = OPERATOR_RUNNING_MODAL;
325
326 if (st->doplugins && texttool_text_is_active(st->text)) {
328 tools |= TOOL_SUGG_LIST;
329 }
330 }
331
332 switch (event->type) {
333 case MOUSEMOVE: {
334 if (space_text_do_suggest_select(st, region, event->mval)) {
335 draw = 1;
336 }
337 swallow = 1;
338 break;
339 }
340 case LEFTMOUSE:
341 if (event->val == KM_PRESS) {
342 if (space_text_do_suggest_select(st, region, event->mval)) {
343 if (tools & TOOL_SUGG_LIST) {
347 ED_undo_push(C, op->type->name);
348 swallow = 1;
349 draw = 1;
350 }
351 retval = OPERATOR_FINISHED;
352 }
353 else {
354 if (tools & TOOL_SUGG_LIST) {
356 }
357 retval = OPERATOR_CANCELLED;
358 }
359 draw = 1;
360 }
361 break;
362 case EVT_ESCKEY:
363 if (event->val == KM_PRESS) {
364 draw = swallow = 1;
365 if (tools & TOOL_SUGG_LIST) {
367 }
368 else {
369 draw = swallow = 0;
370 }
371 retval = OPERATOR_CANCELLED;
372 }
373 break;
374 case EVT_RETKEY:
375 case EVT_PADENTER:
376 if (event->val == KM_PRESS) {
377 if (tools & TOOL_SUGG_LIST) {
381 ED_undo_push(C, op->type->name);
382 swallow = 1;
383 draw = 1;
384 }
385 retval = OPERATOR_FINISHED;
386 }
387 break;
388 case EVT_LEFTARROWKEY:
389 case EVT_BACKSPACEKEY:
390 if (event->val == KM_PRESS) {
391 if (tools & TOOL_SUGG_LIST) {
392 if (event->modifier & KM_CTRL) {
394 retval = OPERATOR_CANCELLED;
395 draw = 1;
396 }
397 else {
398 /* Work out which char we are about to delete/pass */
399 if (st->text->curl && st->text->curc > 0) {
400 char ch = st->text->curl->line[st->text->curc - 1];
401 if ((ch == '_' || !ispunct(ch)) && !text_check_whitespace(ch)) {
402 get_suggest_prefix(st->text, -1);
404 txt_move_left(st->text, false);
405 draw = 1;
406 }
407 else {
409 retval = OPERATOR_CANCELLED;
410 draw = 1;
411 }
412 }
413 else {
415 retval = OPERATOR_CANCELLED;
416 draw = 1;
417 }
418 }
419 }
420 }
421 break;
423 if (event->val == KM_PRESS) {
424 if (tools & TOOL_SUGG_LIST) {
425 if (event->modifier & KM_CTRL) {
427 retval = OPERATOR_CANCELLED;
428 draw = 1;
429 }
430 else {
431 /* Work out which char we are about to pass */
432 if (st->text->curl && st->text->curc < st->text->curl->len) {
433 char ch = st->text->curl->line[st->text->curc];
434 if ((ch == '_' || !ispunct(ch)) && !text_check_whitespace(ch)) {
435 get_suggest_prefix(st->text, 1);
437 txt_move_right(st->text, false);
438 draw = 1;
439 }
440 else {
442 retval = OPERATOR_CANCELLED;
443 draw = 1;
444 }
445 }
446 else {
448 retval = OPERATOR_CANCELLED;
449 draw = 1;
450 }
451 }
452 }
453 }
454 break;
455 case EVT_PAGEDOWNKEY:
456 scroll = SUGG_LIST_SIZE - 1;
458 case WHEELDOWNMOUSE:
459 case EVT_DOWNARROWKEY:
460 if (event->val == KM_PRESS) {
461 if (tools & TOOL_SUGG_LIST) {
463 if (!sel) {
465 }
466 else {
467 while (sel && scroll--) {
468 if (sel != texttool_suggest_last() && sel->next) {
470 sel = sel->next;
471 }
472 else {
475 }
476 }
477 }
479 swallow = 1;
480 draw = 1;
481 }
482 }
483 break;
484 case EVT_PAGEUPKEY:
485 scroll = SUGG_LIST_SIZE - 1;
487 case WHEELUPMOUSE:
488 case EVT_UPARROWKEY:
489 if (event->val == KM_PRESS) {
490 if (tools & TOOL_SUGG_LIST) {
492 while (sel && scroll--) {
493 if (sel != texttool_suggest_first() && sel->prev) {
495 sel = sel->prev;
496 }
497 else {
499 sel = texttool_suggest_last();
500 }
501 }
503 swallow = 1;
504 draw = 1;
505 }
506 }
507 break;
509 case EVT_LEFTSHIFTKEY:
510 break;
511#if 0
512 default:
513 if (tools & TOOL_SUGG_LIST) {
515 draw = 1;
516 }
517#endif
518 }
519
520 if (draw) {
521 ED_area_tag_redraw(area);
522 }
523
524 if (swallow) {
525 // retval = OPERATOR_RUNNING_MODAL;
526 }
527
529 if (retval != OPERATOR_RUNNING_MODAL) {
531 }
532 return retval;
533 }
535 return OPERATOR_FINISHED;
536}
537
539{
540 GHash *gh = static_cast<GHash *>(op->customdata);
541 if (gh) {
542 BLI_ghash_free(gh, nullptr, MEM_freeN);
543 op->customdata = nullptr;
544 }
545
546 /* other stuff */
547 {
549 st->doplugins = false;
551 }
552}
553
555{
557}
558
560{
561 /* identifiers */
562 ot->name = "Text Auto Complete";
563 ot->description = "Show a list of used text in the open document";
564 ot->idname = "TEXT_OT_autocomplete";
565
566 /* api callbacks */
571
572 /* flags */
573 /* Undo is handled conditionally by this operator. */
575}
576
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:815
int text_check_identifier_unicode(unsigned int ch)
Definition text.cc:2358
void txt_move_left(struct Text *text, bool sel)
Definition text.cc:869
void txt_move_right(struct Text *text, bool sel)
Definition text.cc:912
int text_check_identifier_nodigit_unicode(unsigned int ch)
Definition text.cc:2363
bool text_check_whitespace(char ch)
Definition text.cc:2369
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:2377
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:50
#define ATTR_FALLTHROUGH
BLI_INLINE void * BLI_ghashIterator_getValue(GHashIterator *ghi) ATTR_WARN_UNUSED_RESULT
Definition BLI_ghash.h:303
#define GHASH_ITER(gh_iter_, ghash_)
Definition BLI_ghash.h:322
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.c:731
void BLI_ghash_insert(GHash *gh, void *key, void *val)
Definition BLI_ghash.c:707
void BLI_ghash_free(GHash *gh, GHashKeyFreeFP keyfreefp, GHashValFreeFP valfreefp)
Definition BLI_ghash.c: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.c:29
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_RUNNING_MODAL
void ED_area_tag_redraw(ScrArea *area)
Definition area.cc:708
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:104
Read Guarded memory(de)allocation.
@ OPTYPE_BLOCKING
Definition WM_types.hh:164
@ KM_PRESS
Definition WM_types.hh:284
@ KM_CTRL
Definition WM_types.hh:256
ATTR_WARN_UNUSED_RESULT const BMLoop * l
unsigned int U
Definition btGjkEpa3.h:78
SIMD_FORCE_INLINE const btScalar & w() const
Return the w value.
Definition btQuadWord.h:119
int len
uint top
void MEM_freeN(void *vmemh)
Definition mallocn.cc:105
SpaceText_Runtime * runtime
struct Text * text
struct SuggItem * prev
struct SuggItem * next
char(* format_identifier)(const char *string)
char * line
struct TextLine * prev
TextLine * curl
short val
Definition WM_types.hh:724
int mval[2]
Definition WM_types.hh:728
uint8_t modifier
Definition WM_types.hh:739
short type
Definition WM_types.hh:722
const char * name
Definition WM_types.hh:990
bool(* poll)(bContext *C) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1042
const char * idname
Definition WM_types.hh:992
int(* modal)(bContext *C, wmOperator *op, const wmEvent *event) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1036
int(* invoke)(bContext *C, wmOperator *op, const wmEvent *event) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1022
const char * description
Definition WM_types.hh:996
void(* cancel)(bContext *C, wmOperator *op)
Definition WM_types.hh:1028
struct wmOperatorType * type
void TEXT_OT_autocomplete(wmOperatorType *ot)
static int text_autocomplete_invoke(bContext *C, wmOperator *op, const wmEvent *)
static GHash * text_autocomplete_build(Text *text)
static void text_autocomplete_free(bContext *C, wmOperator *op)
static void confirm_suggestion(Text *text)
static int text_autocomplete_modal(bContext *C, wmOperator *op, const wmEvent *event)
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)
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:264
void space_text_update_character_width(SpaceText *st)
#define SUGG_LIST_SIZE
#define TXT_BODY_LEFT(st)
#define TOOL_SUGG_LIST
void text_update_line_edited(TextLine *line)
Definition text_ops.cc:309
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:4125