/* * ***** BEGIN GPL LICENSE BLOCK ***** * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * ***** END GPL LICENSE BLOCK ***** */ /** \file blender/editors/space_text/text_autocomplete.c * \ingroup sptext */ #include #include #include "MEM_guardedalloc.h" #include "DNA_text_types.h" #include "BLI_blenlib.h" #include "BLI_ghash.h" #include "BKE_context.h" #include "BKE_text.h" #include "BKE_screen.h" #include "BKE_suggestions.h" #include "WM_api.h" #include "WM_types.h" #include "ED_screen.h" #include "UI_interface.h" #include "text_format.h" #include "text_intern.h" /* own include */ /* -------------------------------------------------------------------- */ /* Public API */ int text_do_suggest_select(SpaceText *st, ARegion *ar) { SuggItem *item, *first, *last /* , *sel */ /* UNUSED */; TextLine *tmp; int l, x, y, w, h, i; int tgti, *top; int mval[2] = {0, 0}; if (!st || !st->text) return 0; if (!texttool_text_is_active(st->text)) return 0; first = texttool_suggest_first(); last = texttool_suggest_last(); /* sel = texttool_suggest_selected(); */ /* UNUSED */ top = texttool_suggest_top(); if (!last || !first) return 0; /* Count the visible lines to the cursor */ for (tmp = st->text->curl, l = -st->top; tmp; tmp = tmp->prev, l++) ; if (l < 0) return 0; text_update_character_width(st); if (st->showlinenrs) { x = st->cwidth * (st->text->curc - st->left) + TXT_OFFSET + TEXTXLOC - 4; } else { x = st->cwidth * (st->text->curc - st->left) + TXT_OFFSET - 4; } y = ar->winy - st->lheight_dpi * l - 2; w = SUGG_LIST_WIDTH * st->cwidth + U.widget_unit; h = SUGG_LIST_SIZE * st->lheight_dpi + 0.4f * U.widget_unit; // XXX getmouseco_areawin(mval); if (mval[0] < x || x + w < mval[0] || mval[1] < y - h || y < mval[1]) return 0; /* Work out which of the items is at the top of the visible list */ for (i = 0, item = first; i < *top && item->next; i++, item = item->next) ; /* Work out the target item index in the visible list */ tgti = (y - mval[1] - 4) / st->lheight_dpi; if (tgti < 0 || tgti > SUGG_LIST_SIZE) return 1; for (i = tgti; i > 0 && item->next; i--, item = item->next) ; if (item) texttool_suggest_select(item); return 1; } void text_pop_suggest_list(void) { SuggItem *item, *sel; int *top, i; item = texttool_suggest_first(); sel = texttool_suggest_selected(); top = texttool_suggest_top(); i = 0; while (item && item != sel) { item = item->next; i++; } if (i > *top + SUGG_LIST_SIZE - 1) *top = i - SUGG_LIST_SIZE + 1; else if (i < *top) *top = i; } /* -------------------------------------------------------------------- */ /* Private API */ static void text_autocomplete_free(bContext *C, wmOperator *op); static GHash *text_autocomplete_build(Text *text) { GHash *gh; int seek_len = 0; const char *seek; texttool_text_clear(); texttool_text_set_active(text); /* first get the word we're at */ { const int i = text_find_identifier_start(text->curl->line, text->curc); seek_len = text->curc - i; seek = text->curl->line + i; // BLI_strncpy(seek, seek_ptr, seek_len); } /* now walk over entire doc and suggest words */ { TextLine *linep; gh = BLI_ghash_str_new(__func__); for (linep = text->lines.first; linep; linep = linep->next) { size_t i_start = 0; size_t i_end = 0; size_t i_pos = 0; while (i_start < linep->len) { /* seek identifier beginning */ i_pos = i_start; while ((i_start < linep->len) && (!text_check_identifier_nodigit_unicode(BLI_str_utf8_as_unicode_and_size_safe(&linep->line[i_start], &i_pos)))) { i_start = i_pos; } i_pos = i_end = i_start; while ((i_end < linep->len) && (text_check_identifier_unicode(BLI_str_utf8_as_unicode_and_size_safe(&linep->line[i_end], &i_pos)))) { i_end = i_pos; } if ((i_start != i_end) && /* check we're at the beginning of a line or that the previous char is not an identifier * this prevents digits from being added */ ((i_start < 1) || !text_check_identifier_unicode(BLI_str_utf8_as_unicode(&linep->line[i_start - 1])))) { char *str_sub = &linep->line[i_start]; const int choice_len = i_end - i_start; if ((choice_len > seek_len) && (seek_len == 0 || STREQLEN(seek, str_sub, seek_len)) && (seek != str_sub)) { // printf("Adding: %s\n", s); char str_sub_last = str_sub[choice_len]; str_sub[choice_len] = '\0'; if (!BLI_ghash_lookup(gh, str_sub)) { char *str_dup = BLI_strdupn(str_sub, choice_len); BLI_ghash_insert(gh, str_dup, str_dup); /* A 'set' would make more sense here */ } str_sub[choice_len] = str_sub_last; } } if (i_end != i_start) { i_start = i_end; } else { /* highly unlikely, but prevent eternal loop */ i_start++; } } } { GHashIterator gh_iter; /* get the formatter for highlighting */ TextFormatType *tft; tft = ED_text_format_get(text); GHASH_ITER (gh_iter, gh) { const char *s = BLI_ghashIterator_getValue(&gh_iter); texttool_suggest_add(s, tft->format_identifier(s)); } } } texttool_suggest_prefix(seek, seek_len); return gh; } /* -- */ static void get_suggest_prefix(Text *text, int offset) { int i, len; const char *line; if (!text) return; if (!texttool_text_is_active(text)) return; line = text->curl->line; i = text_find_identifier_start(line, text->curc + offset); len = text->curc - i + offset; texttool_suggest_prefix(line + i, len); } static void confirm_suggestion(Text *text) { SuggItem *sel; int i, over = 0; const char *line; if (!text) return; if (!texttool_text_is_active(text)) return; sel = texttool_suggest_selected(); if (!sel) return; line = text->curl->line; i = text_find_identifier_start(line, text->curc /* - skipleft */); over = text->curc - i; // for (i = 0; i < skipleft; i++) // txt_move_left(text, 0); BLI_assert(memcmp(sel->name, &line[i], over) == 0); txt_insert_buf(text, sel->name + over); // for (i = 0; i < skipleft; i++) // txt_move_right(text, 0); texttool_text_clear(); } /* -- */ static int text_autocomplete_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) { SpaceText *st = CTX_wm_space_text(C); Text *text = CTX_data_edit_text(C); st->doplugins = true; op->customdata = text_autocomplete_build(text); if (texttool_suggest_first()) { ED_area_tag_redraw(CTX_wm_area(C)); if (texttool_suggest_first() == texttool_suggest_last()) { confirm_suggestion(st->text); text_update_line_edited(st->text->curl); text_autocomplete_free(C, op); return OPERATOR_FINISHED; } else { WM_event_add_modal_handler(C, op); return OPERATOR_RUNNING_MODAL; } } else { text_autocomplete_free(C, op); return OPERATOR_CANCELLED; } } static int doc_scroll = 0; static int text_autocomplete_modal(bContext *C, wmOperator *op, const wmEvent *event) { SpaceText *st = CTX_wm_space_text(C); ScrArea *sa = CTX_wm_area(C); ARegion *ar = BKE_area_find_region_type(sa, RGN_TYPE_WINDOW); int draw = 0, tools = 0, swallow = 0, scroll = 1; Text *text = CTX_data_edit_text(C); int retval = OPERATOR_RUNNING_MODAL; (void)text; if (st->doplugins && texttool_text_is_active(st->text)) { if (texttool_suggest_first()) tools |= TOOL_SUGG_LIST; if (texttool_docs_get()) tools |= TOOL_DOCUMENT; } switch (event->type) { case LEFTMOUSE: if (event->val == KM_PRESS) { if (text_do_suggest_select(st, ar)) swallow = 1; else { if (tools & TOOL_SUGG_LIST) { texttool_suggest_clear(); } if (tools & TOOL_DOCUMENT) { texttool_docs_clear(); doc_scroll = 0; } retval = OPERATOR_FINISHED; } draw = 1; } break; case MIDDLEMOUSE: if (event->val == KM_PRESS) { if (text_do_suggest_select(st, ar)) { confirm_suggestion(st->text); text_update_line_edited(st->text->curl); swallow = 1; } else { if (tools & TOOL_SUGG_LIST) { texttool_suggest_clear(); } if (tools & TOOL_DOCUMENT) { texttool_docs_clear(); doc_scroll = 0; } retval = OPERATOR_FINISHED; } draw = 1; } break; case ESCKEY: if (event->val == KM_PRESS) { draw = swallow = 1; if (tools & TOOL_SUGG_LIST) { texttool_suggest_clear(); } else if (tools & TOOL_DOCUMENT) { texttool_docs_clear(); doc_scroll = 0; } else draw = swallow = 0; retval = OPERATOR_CANCELLED; } break; case RETKEY: case PADENTER: if (event->val == KM_PRESS) { if (tools & TOOL_SUGG_LIST) { confirm_suggestion(st->text); text_update_line_edited(st->text->curl); swallow = 1; draw = 1; } if (tools & TOOL_DOCUMENT) { texttool_docs_clear(); doc_scroll = 0; draw = 1; } retval = OPERATOR_FINISHED; } break; case LEFTARROWKEY: case BACKSPACEKEY: if (event->val == KM_PRESS) { if (tools & TOOL_SUGG_LIST) { if (event->ctrl) { texttool_suggest_clear(); retval = OPERATOR_CANCELLED; } else { /* Work out which char we are about to delete/pass */ if (st->text->curl && st->text->curc > 0) { char ch = st->text->curl->line[st->text->curc - 1]; if ((ch == '_' || !ispunct(ch)) && !text_check_whitespace(ch)) { get_suggest_prefix(st->text, -1); text_pop_suggest_list(); } else { texttool_suggest_clear(); retval = OPERATOR_CANCELLED; } } else { texttool_suggest_clear(); retval = OPERATOR_CANCELLED; } } } if (tools & TOOL_DOCUMENT) { texttool_docs_clear(); doc_scroll = 0; } } break; case RIGHTARROWKEY: if (event->val == KM_PRESS) { if (tools & TOOL_SUGG_LIST) { if (event->ctrl) { texttool_suggest_clear(); retval = OPERATOR_CANCELLED; } else { /* Work out which char we are about to pass */ if (st->text->curl && st->text->curc < st->text->curl->len) { char ch = st->text->curl->line[st->text->curc + 1]; if ((ch == '_' || !ispunct(ch)) && !text_check_whitespace(ch)) { get_suggest_prefix(st->text, 1); text_pop_suggest_list(); } else { texttool_suggest_clear(); retval = OPERATOR_CANCELLED; } } else { texttool_suggest_clear(); retval = OPERATOR_CANCELLED; } } } if (tools & TOOL_DOCUMENT) { texttool_docs_clear(); doc_scroll = 0; } } break; case PAGEDOWNKEY: scroll = SUGG_LIST_SIZE - 1; ATTR_FALLTHROUGH; case WHEELDOWNMOUSE: case DOWNARROWKEY: if (event->val == KM_PRESS) { if (tools & TOOL_DOCUMENT) { doc_scroll++; swallow = 1; draw = 1; } else if (tools & TOOL_SUGG_LIST) { SuggItem *sel = texttool_suggest_selected(); if (!sel) { texttool_suggest_select(texttool_suggest_first()); } else { while (sel && scroll--) { if (sel != texttool_suggest_last() && sel->next) { texttool_suggest_select(sel->next); sel = sel->next; } else { texttool_suggest_select(texttool_suggest_first()); sel = texttool_suggest_first(); } } } text_pop_suggest_list(); swallow = 1; draw = 1; } } break; case PAGEUPKEY: scroll = SUGG_LIST_SIZE - 1; ATTR_FALLTHROUGH; case WHEELUPMOUSE: case UPARROWKEY: if (event->val == KM_PRESS) { if (tools & TOOL_DOCUMENT) { if (doc_scroll > 0) doc_scroll--; swallow = 1; draw = 1; } else if (tools & TOOL_SUGG_LIST) { SuggItem *sel = texttool_suggest_selected(); while (sel && scroll--) { if (sel != texttool_suggest_first() && sel->prev) { texttool_suggest_select(sel->prev); sel = sel->prev; } else { texttool_suggest_select(texttool_suggest_last()); sel = texttool_suggest_last(); } } text_pop_suggest_list(); swallow = 1; draw = 1; } } break; case RIGHTSHIFTKEY: case LEFTSHIFTKEY: break; #if 0 default: if (tools & TOOL_SUGG_LIST) { texttool_suggest_clear(); draw = 1; } if (tools & TOOL_DOCUMENT) { texttool_docs_clear(); doc_scroll = 0; draw = 1; } #endif } if (draw) { ED_area_tag_redraw(sa); } // if (swallow) { // retval = OPERATOR_RUNNING_MODAL; // } if (texttool_suggest_first()) { if (retval != OPERATOR_RUNNING_MODAL) { text_autocomplete_free(C, op); } return retval; } else { text_autocomplete_free(C, op); return OPERATOR_FINISHED; } } static void text_autocomplete_free(bContext *C, wmOperator *op) { GHash *gh = op->customdata; if (gh) { BLI_ghash_free(gh, NULL, MEM_freeN); op->customdata = NULL; } /* other stuff */ { SpaceText *st = CTX_wm_space_text(C); st->doplugins = false; texttool_text_clear(); } } static void text_autocomplete_cancel(bContext *C, wmOperator *op) { text_autocomplete_free(C, op); } void TEXT_OT_autocomplete(wmOperatorType *ot) { /* identifiers */ ot->name = "Text Auto Complete"; ot->description = "Show a list of used text in the open document"; ot->idname = "TEXT_OT_autocomplete"; /* api callbacks */ ot->invoke = text_autocomplete_invoke; ot->cancel = text_autocomplete_cancel; ot->modal = text_autocomplete_modal; ot->poll = text_space_edit_poll; /* flags */ ot->flag = OPTYPE_BLOCKING; }